Get a content edit URL as a string without UrlHelpers or Orchard tokens - orchardcms

I'm trying to get the edit URL of a content as a string from backend, the catch is I'm inside a workflow activity, so I can't use Url.Action... or Url.ItemEditLink... or other UrlHelpers as if it were a controller or a view. Also, although I'm inside a workflow, the contents I need it for are not part of the workflowContext or the activityContext, so I can't use those or tokens either.
A solution could be to get the content metadata and the site baseUrl and try to build it manually, but I think this way is prone to errors.
Thanks.

This is how I build a Uri in an activity:
public class MyClass : Task
{
private readonly RequestContext _requestContext;
...
public MyActivity(RequestContext requestContext, ...)
{
_requestContext = requestContext;
...
}
...
public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext)
{
var content = ... get using ID
var helper = new UrlHelper(_requestContext);
var baseurl = new Uri(_orchardServices.WorkContext.CurrentSite.BaseUrl);
Uri completeurl = new Uri(baseurl, helper.ItemDisplayUrl(content));
yield return T("Done");
}
}
Turns out that I actually do build the Uri semi-manually, but I haven't had issues with this method. You may be able to use just the ItemDisplayUrl for navigation inside of Orchard; I had to get the full URL because the string gets sent to an outside program (Slack).

Related

How to store the uri of image from registerForActivityResult() and send it to another function

The image i've selected from gallery disappear after the configuration changes, so i wanted to get the uri of the image so i can pass it to onSaveInstanceState but i don't know how to do so.
val selectImageFromGalleryResult = registerForActivityResult(
ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
profileImage.setImageURI(uri)
}
}
selectBtn.setOnClickListener {
profileImage.visibility = View.VISIBLE
selectImageFromGalleryResult.launch("image/*")
}
If i create a global variable,
private lateinit var imageUri: Uri
i get error saying it has not been initialized if i get the value from the above function and try to send it to onSaveInstanceState
Uri is Parcelable itself, you can directly put it into the Bundle. To help with saving it to state, you may need to create member variable in your activity
private var profileImageUri: Uri? = null
and once you obtain Uri from ActivityResultContracts.GetContent(), then store it in profileImageUri
val selectImageFromGalleryResult = registerForActivityResult(
ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
profileImageUri = uri
profileImage.setImageURI(uri)
}
}
and save this profileImageUri in Bundle at onSaveInstanceState()
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
outState.putParcelable("profileImageUri", profileImageUri)
super.onSaveInstanceState(outState, outPersistentState)
}
and to make sure on configuration change the image view load the uri, put below code in your onCreate() function
profileImageUri = savedInstanceState?.getParcelable("profileImageUri")
profileImageUri?.let {
profileImage.setImageURI(it)
}
Additional: if you use ViewModel, it will give you more concise way. Just can create an object to save the Uri in the ViewModel and no longer need to create boilerplate code above to survive the Uri on configuration changes

struggling with an ASP.NET MVC5 routing issue

So, I have an MVC5 site that uses the default routing template {controller}/{action}/{id} and this works fine. Most everything in the site requires a login (i.e. [Authorize] attribute is used almost everywhere), and this works fine.
Well, now I have a need to allow anonymous access to select pages when a certain kind of link pattern is used: App/{token}/{action}. The {token} is a random string associated with something in my database. I can issue and deactivate these tokens at will.
I got this new App/{token}/{action} routing working by implementing a custom RouteBase that parses the incoming URL for these tokens, and, crucially, adds the the token value to the RouteData.DataTokens so that my App controller can make use of it without needing an explicit action argument for it. So, I added this new route to the route table ahead of the default routing like this:
// new route here
routes.Add("AppToken", new AnonAppAccessRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here is the problem/question: adding this now route has now made my default route stop working -- everything is now going through AnonAppAccessRoute which of course is meant to work only for a few things. I don't understand how to make my AnonAppAccessRoute apply only to URLs with a certain pattern. The MapRoute method accepts a URL pattern, but Adding a route doesn't seem to let you put a filter on it. What am I missing? I've looked around quite a bit at various blogs and documentation about routing, but I've not found good info about using the DataTokens collection (which I feel is important to my approach), and I'm not seeing a good explanation of the difference between Adding a route explicitly vs calling MapRoute.
Here's the code of my custom RouteBase:
public class AnonAppAccessRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string[] pathElements = httpContext.Request.Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (pathElements.Length > 0)
{
string token = TryGetArrayElement(pathElements, 1);
if (!string.IsNullOrEmpty(token))
{
result = new RouteData(this, new MvcRouteHandler());
result.DataTokens.Add("appToken", token);
result.Values.Add("controller", "App");
result.Values.Add("action", TryGetArrayElement(pathElements, 2, "Index"));
}
}
return result;
}
private string TryGetArrayElement(string[] array, int index, string defaultValue = null)
{
try
{
return array[index];
}
catch
{
return defaultValue;
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
I got this to work by dropping the custom RouteBase and instead used this MapRoute call like this:
routes.MapRoute(
name: "AppAnon",
url: "App/{token}/{action}",
defaults: new { controller = "App", action = "Index" }
);
Then, in my App controller, I did this in the Initialize override:
protected AppToken _appToken = null;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
string token = requestContext.RouteData.Values["token"]?.ToString();
_appToken = Db.FindWhere<AppToken>("[Token]=#token", new { token });
if (!_appToken?.IsActive ?? false) throw new Exception("The token is not found or inactive.");
}
This way, my "token" is available to all controller actions via the _appToken variable, and already validated. I did not need to use RouteData.DataTokens. Note that my Db.FindWhere statement is ORM-specific and not really related to the question -- it's just how I look up a database record.

How to return a different Razor Page without redirect?

I am porting a Asp.Net MVC application to Razor Pages.
In some of the controllers of the MVC application it makes use of return View("someOtherView", someModelForOtherView);
How do I port this to Razor Pages?
What I need to do is to transfer the request over to another Razor Page and pass the prepared PageModel to it (the other page does not need to execute OnMethod() but simply render its html.
Or, in other words, I only need to swap the template file that should be rendered with another one.
I cannot use Redirect as there must not be another roundtrip via the browser.
I doubt this is (easily) possible. From the github request that Lerner linked above, it's noted Razor Pages weren't designed to do that.
The closest workaround I was able to achieve was to turn my destination Razor Page into a View. (Hence, no code-behind.) Obviously that will only be possible if your destination page is never directly accessed via URL. For example, if you want to redirect to /Pages/MyPage, and you still need to be able to access the url http://example.com/MyPage, this won't work.
But, say all you want is a generic error or status page. Those don't have to be directly-accessible through URL. This works well for that.
Here's a couple extension methods on PageModel to do it, one that accepts models and one that doesn't:
public static ViewResult View(this PageModel pageModel, string viewName) {
return new ViewResult() {
ViewName = viewName,
ViewData = pageModel.ViewData,
TempData = pageModel.TempData
};
}
public static ViewResult View<TModel>(this PageModel pageModel, string viewName, TModel model) {
var viewDataDictionary = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary()) {
Model = model
};
foreach (var kvp in pageModel.ViewData) viewDataDictionary.Add(kvp);
return new ViewResult {
ViewName = viewName,
ViewData = viewDataDictionary,
TempData = pageModel.TempData
};
}
FYI, the reason for having to recreate the view dictionary is because the one in your pageModel is going to have a model type specific to the current Page, not to the View you're directing to, and you can't change the Model within a ViewDataDictionary to a different type. MVC would complain and throw an exception.
Usage:
public IActionResult OnGet(string id) {
// check if id is good here
if (idIsNoGood) return this.View("InvalidId", new ErrorModel...);
else {
return Page();
}
}
The above will look for InvalidId.cshtml view, which can be in the same folder as your page, the root /Pages/ folder, or /Pages/Shared/. And it'll still use your Layout too, like any other page.
Just make sure your cshtml file doesn't have a #page directive at the top; this won't work for a Razor page, only a View.
Example InvalidId.cshtml:
#model MyProject.Models.ErrorModel
<h1>Invalid Request</h1>
<p>#Model.Message</p>

ASP.NET MVC routing without controller or action name in URL

My URL structure is like http://website.com/city-state/category e.g. /miami-fl/restaurants or /los-angeles-ca/bars. In order to send it to the correct controller, I have a class derived from RouteBase, which splits the request path and figures out the city, state and category. This is working fine for incoming URLs.
public class LegacyUrlRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData routeData = new RouteData();
routeData.RouteHandler = new MvcRouteHandler();
string url = httpContext.Request.Path.ToLower(); //e.g. url = /los-angeles
string[] path = url.TrimStart('/').Split('/'); //
if (path.Length > 1)
{
string[] locale = path[0].Split('-');
if (locale.Length > 1) //This is a city page, so send it to category controller
{
string stateName = locale[locale.Length - 1];
string cityName = path[0].Replace(stateName, "").TrimEnd('-');
routeData.Values.Add("controller", "Category");
routeData.Values.Add("action", "City");
routeData.Values.Add("categoryname", path[1]);
routeData.Values.Add("city", cityName);
routeData.Values.Add("state", stateName);
}
}
}
}
However, when I try to use Html.ActionLink to create a link, it doesn't pick up from this class.
#Html.ActionLink("Restaurants in Miami", "Index", "Category", new {categoryname="Restaurants", state="FL", city="Miami"})
gives me a url of /category/index?categoryname=restaurants&state=fl&city=miami.
How do I generate accurate links for my URL structure.
If you want your outgoing URLs to function, you must implement GetVirtualPath, which converts a set of route values into a URL. It should typically be the mirror image of your GetRouteData method.
The simplest way to implement it would just be to make a data structure that maps your route values to URLs and then use it in both methods as in this example.

ServiceStack - Redirecting at root based on query params [duplicate]

This question already has answers here:
Create route for root path, '/', with ServiceStack
(3 answers)
Closed 3 years ago.
I've got a Fallback DTO that looks like the following:
[FallbackRoute("/{Path*}")]
public class Fallback
{
public string Path { get; set; }
}
Now, in my Service I would like to redirect to an HTML5 compliant URL, and this is what I've tried:
public object Get(Fallback fallback)
{
return this.Redirect("/#!/" + fallback.Path);
}
It is working all fine and dandy, except for the fact that query parameters are not passed along with the path. Using Request.QueryString does not work as no matter what I do it is empty. Here's what my current (non-working) solution looks like:
public object Get(Fallback fallback)
{
StringBuilder sb = new StringBuilder("?");
foreach (KeyValuePair<string, string> item in Request.QueryString)
{
sb.Append(item.Key).Append("=").Append(item.Value).Append("&");
}
var s = "/#!/" + fallback.Path + sb.ToString();
return this.Redirect(s);
}
TL;DR: I want to pass on query strings along with fallback path.
EDIT: It turns out I had two problems; now going to mysite.com/url/that/does/not/exist?random=param correctly redirects the request to mysite.com/#!/url/that/does/not/exist?random=param& after I changed the above loop to:
foreach (string key in Request.QueryString)
{
sb.Append(key).Append("=").Append(Request.QueryString[key]).Append("&");
}
But the fallback is still not being called at root, meaning mysite.com/?random=param won't trigger anything.
In essence, what I want to do is to have ServiceStack look for query strings at root, e.g., mysite.com/?key=value, apply some logic and then fire off a redirect. The purpose of this is in order for crawler bots to be able to query the site with a _escaped_fragment_ parameter and then be presented with an HTML snapshot prepared by a server. This is in order for the bots to be able to index single-page applications (more on this).
I'm thinking perhaps the FallbackRoute function won't cover this and I need to resort to overriding the CatchAllHandler.
I managed to find a solution thanks to this answer.
First create an EndpointHostConfig object in your AppHost:
var config = new EndpointHostConfig
{
...
};
Then, add a RawHttpHandler:
config.RawHttpHandlers.Add(r =>
{
var crawl = r.QueryString["_escaped_fragment_"];
if (crawl != null)
{
HttpContext.Current.RewritePath("/location_of_snapshots/" + crawl);
}
return null;
});
Going to mysite.com/?_escaped_fragment_=home?key=value will fire off a redirection to mysite.com/location_of_snapshots/home?key=value, which should satisfy the AJAX crawling bots.
N.B. It's possible some logic needs to be applied to the redirection to ensure that there won't be double forward slashes. I have yet to test that.

Resources