determine routename, controller, action area in net core controller - c#

I am trying to write a class to determine the next step when navigating through an online journey in a net core mvc app.
I would like this journey to be configurable - allowing the step order to be altered based on some configuration.
I figured the easiest way to do this would be to hold a list of 'steps' which would have the controller and action (possibly area and routename), locate the current step in this, and simply generating a redirect based on whatever is next in the list.
I am having trouble getting all of this. I can get the current controller and action fine, using the ControllerContext.RouteData.Values["controller"]/["action"] with no problem, but getting the routename appears to be a bit trickier.
the RouteData also hold a list of Routers, but it is not clear which one of these is the one used for the current request.
is it possible to get the route name? or am I barking up the wrong tree..? or do i need to effectively run the current url back through the MVC routing and grab what I need from that.
we are currently using conventional routing set in the UseMvc(routes=>{... method in the startup.. with a number of routes in, currently split between different 'products'.
For instance:
UseMvc(routes =>{
routes.MapRoute(
name: "prod1",
template: "product1/{controller=AboutYou}/{action=Index}/{id?}");
routes.MapRoute(
name: "prod2",
template: "product2/{controller=AboutYou}/{action=Index}/{id?}");
});

Related

ASP.NET Core 2.0 URLs

I'm developing a large ASP.NET Core 2 web application but I still get confused with URLs.
When fist learning, I thought URL's took on the name of the View, but discovered they come from the Controller method.
Here is an example to convey my issue:
I have a controller method named Daysheet, which returns a view and model with the same name.
In the Daysheet view, I call various controller methods from Javascript to perform specific actions. One of them is called AssignStaff which takes two integer parameters.
In the AssignStaff method I again return the Daysheet view with model, but now my URL is "AssignStaff"!
I can't just do a redirect because the whole Daysheet model is not being passed to the AssignStaff method.
I have many situations like this where after calling an action, I end up with another URL that I don't want.
UPDATE/EDIT
Thanks for assistance and apologies if my explanation is confusing. I simply have a view called Daysheet that uses a model. I want to call various controller methods to perform various actions, but I want to stay on the "Daysheet" view/URL.
As mentioned, I can't just redirect because in the action method I no longer have the whole model from the Daysheet view. Also, if I redirect I can't pass the whole model because that causes an error saying the header is too long. I think my only choice may be to use ajax for the actions so that the URL doesn't change.
When you just do Return View("") name in a Controller Action, the URL will be the name of the Action you are using.
If you want to redirect to some specific Action, that will help to make sure the Url matches to where you are. You might want to read more about it here.
To do so, use:
RedirectToAction()
The URLs your application responds to are called "routes", and they are either created by convention or explicitly. The default is by convention, of course, which is a URL in the form of /{controller=Home}/{action=Index}. Index is the default action if that portion of the route is left off, so a request to /foo will by convention map to FooController.Index. HomeController is the default controller, so an empty path (e.g. http://sample.com) will by convention invoke HomeController.Index.
Razor Pages have their own conventions. These do somewhat follow the file system, but exclude the Pages part of the path. So a Razor Page like Pages/Foo/MyRazorPage.cshtml, will load up under /Foo/MyRazorPage.
There there is the Route attribute, which allows you to specify a totally custom route. This attribute can be applied to a controller class and individual actions in the class. For example:
[Route("foo")]
public class MyAwesomeController
{
[Route("bar")]
public IActionResult MyAwesomeAction()
{
return View();
}
}
With that, a request to /foo/bar will actually invoke MyAwesomeController.MyAwesomeAction.

WebAPI Controller Methods that Differ By Value Types

I have an existing API I'm moving over to WebAPI, so I'm not free to change the URL. Breaking existing clients is not an option for me.
Knowing that, the original API would accept either a Guid (an ID) or a string (a name) for a given action method. The old API handler would decipher the URL parameter and send the request to a controller action designed to accept the given parameter type.
As an example:
Get(Guid id)
versus
Get(string name)
With WebAPI, the parameter binding is greedy across value types, so depending on which is first in the controller source file, that action is the one invoked. For my needs, that's not working. I was hoping the binder would realize the conversion to a Guid would fail for a name and then select the more generic string-based action. No dice. The Guid simply comes across as a null value (interestingly since it's a value type, but that's what I'm getting in the debugger at a certain point in the processing).
So my question is how best to handle this? Do I need to go as far as implementing a custom IHttpActionSelector? I tried the attribute routing approach (with constraints), but that isn't working quite right (bummer as it looks cool). Is there a mechanism in WebAPI that accounts for this I don't (yet) know about? (I know I can hack it in by testing the string for Guid-ness and invoking the other controller method, but I'm hoping for a more elegant, WebAPI-based solution...)
I'd spent a lot of time trying to fit attribute-based routing in, but I've not got that to work. However, I did solve my particular issue using route constraints. If you register the more-constrained route first, WebAPI (like MVC) will apply the constraints and skip over more-constrained routes until it finds one that it can select, if any.
So, using my example, I'd set up routes like so:
_config.Routes.MapHttpRoute(name: "ById",
routeTemplate: "product/{id}",
defaults: new { controller = "ProductDetails" },
constraints: new { id = #"^\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?$" });
_config.Routes.MapHttpRoute(name: "ByName",
routeTemplate: "product/{name}",
defaults: new { controller = "ProductDetails" });
The first route accepts a constraint in the form of a regular expression for a Guid. The second accepts all other values, and the controller will deal with non-product names (returns a 404). I tested this in the self-hosted WebAPI server and it works fantastically.
I am sure attribute-based routing would be more elegant, but until I get that to work, it's routing the old way for me. At least I found a reasonable WebAPI-based solution.

ASP.NET MVC: How does a controller distingush between parameters in the URL and sent by POST

I would like to better understand how a controler method knows when a parameter it recives should be retrived from the post data or the url.
Take the following example:
URL: /ModelController/Method/itemID
// Where itemID is the id (int) of the item in the database
POST: objectOrArray: {JSON Object/Array}
The controller would look something like this:
[HttpPost]
public ActionResult InputResources(int? id, Object objectOrArray)
Now, somehow the method is smart enough to look for the first parameter, the id, in the site URL and the Object in the HTTPPost.
Whilst this works, I don't know why, and as a result I sometimes run into unpredictable and erratic behaviour. For example, I seem to have found (although I am not 100% sure) that removing the ? from int? id makes the controller method inmediately assume it should look for the id in the HTTPPost and not the URL.
So I would like to clarify the following points:
What exactly is it that tells the method where to look for the data? (The [HttpPost] attribute preciding the method?)
Do naming conventions come into play? (for example removint the ? or not using id as the variable name?)
Does the order in which the variables are placed have an inpact? (ie placing the Object before the id)
I know I can more or less figure out some of this stuff by trial and error, but I would like a qualified explanation rather than to continiue to work of assumption based on observation.
Thank you
Chopo
Take a look at the default route from Global.asax:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The first thing that MVC will try to do is map your method parameters to values in the POST. If it doesn't find a match, it will cascade through other possibilities, including the route values.
Here's the order the model binder uses:
Previously bound action parameters, when the action is a child action
Form fields (Request.Form)
The property values in the JSON Request body (Request.InputStream), but only when the request is an AJAX request
Route data (RouteData.Values)
Querystring parameters (Request.QueryString)
Posted files (Request.Files)
The reason you're seeing the behaviour you describe is because POST data comes before route values. It seems that MVC can't bind a nullable int to a POST value, so it skips over it and keeps going until it reaches the RouteData mapping, at which point, it finds a match and gets the value from the route. When you make the parameter non-nullable, it is suddenly able to bind it to the POST value, which has higher precedence than RouteData, so it does.
Source: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx

MVC URL path extensions

I was just wondering whether its possible to have something like this: I have an Area named Admin and a Controller named 'Edit'. Within this controller I have my Index() which simply lists a bunch of hyperlinks that is treated by the 'Brand' action.
Therefore my url so far is: Admin/Edit/{Brand}.
My question is whether it is possible to have for example: Admin/Edit/{Brand}/Create (as well as edit and delete). This isn't to delete brands, its just to create things within those brands?
I approach that my approach may be misguided and this may necessitate being split into multiple controllers or whatever so don't think that I would like a workaround to make it work this way.
You could define the following route in your area registration:
context.MapRoute(
"Admin_default",
"Admin/{controller}/{brand}/{action}",
new { action = "Index" }
);
And if you wanted to have other controllers than Edit in this area which have the default route, you could register 2 routes but you will have to define a constraint for the {brand} token or the routing engine won't be able to disambiguate between a brand and a controller action name.

Why does RedirectToRoute("Default") not redirect to the root?

Given these routes:
routes.MapRoute("Test", "test", new { controller = "Test", action = "Index" });
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If I call RedirectToRoute("Default") from the Index action of the TestController it redirects to /test but I expected it to redirect to /
I checked the result of calling RedirectToRoute("Default") before returning it during a debugging session.
RedirectToRouteResult result = RedirectToRoute("Default");
It has a property RouteName with a value "Default" and a property RouteValues with no elements (Count = 0). I checked using Reflector, and null is passed internally as the RouteValueDictionary.
Again, I would expect that given the defaults for the route defined in my application, it would redirect to Index view on the HomeController.
Why doesn't it redirect to /?
The RouteValueDictionary filled in to the current action is being used to fill in the Controller, Action, ID. This is usually the functionality you want and lets you do things like <%:Html.ActionLink("MyAction")%> without needing to specify your controller in a view.
To force it to the complete fall back default just use:
RedirectToRoute("default", null");
The second argument is for your routevalues and by specifying it you override the preexisting values.
However I would say the preferred way would be to have a Home route which will not take any parameters. This means your redirects will not need a load of nulls floating around and a call to RedirectToRoute("Home") is also nice and explicit.
Edit
This is a really old answer and as a couple of people have mentioned doesn't seem to have been working for them. For what it's worth I now don't use this pattern and explicitly override controller and area when I need to break out. Not only does it apparently work better but when you come back to the code it's good to see explicitly that you're coming out of this context and mean to blank out certain routevalues.
For the record I have also tended more towards named route based URL generation over convention based generation.
I don't think that "null" parameter Chao mentions has ever worked for me. Perhaps it did in prior MVC versions, but now in MVC 5.2 this is the only way I can get it to work:
RedirectToRoute("Default", new { controller = "", action = "" });
Without looking at the actual code to see what is going on it can be a bit tricky to know why, but this forum post does shed some light. The answerer says that under the covers it might be creating a RouteValueDictionary which would contain the controller and action you are currently in, which in your case would be a controller of Test and an action of Index. Then, when you call RedirectToRoute("Default"), the controller and the action in the RouteValueDictionary will be used when matching the default route and you will be taken to the Index action in your Test controller.
Now you could always do a Redirect("/") to take you to the main page of your site.

Categories