ASP.NET MVC routing - asp.net-mvc

Up until now I've been able to get away with using the default routing that came with ASP.NET MVC. Unfortunately, now that I'm branching out into more complex routes, I'm struggling to wrap my head around how to get this to work.
A simple example I'm trying to get is to have the path /User/{UserID}/Items to map to the User controller's Items function. Can anyone tell me what I'm doing wrong with my routing here?
routes.MapRoute("UserItems", "User/{UserID}/Items",
new {controller = "User", action = "Items"});
And on my aspx page
Html.ActionLink("Items", "UserItems", new { UserID = 1 })

Going by the MVC Preview 4 code I have in front of me the overload for Html.ActionLink() you are using is this one:
public string ActionLink(string linkText, string actionName, object values);
Note how the second parameter is the actionName not the routeName.
As such, try:
Html.ActionLink("Items", "Items", new { UserID = 1 })
Alternatively, try:
Items

Can you post more information? What URL is the aspx page generating in the link? It could be because of the order of your routes definition. I think you need your route to be declared before the default route.

Firstly start with looking at what URL it generates and checking it with Phil Haack's route debug library. It will clear lots of things up.
If you're having a bunch of routes you might want to consider naming your routes and using named routing. It will make your intent more clear when you re-visit your code and it can potentially improve parsing speed.
Furthermore (and this is purely a personal opinion) I like to generate my links somewhere at the start of the page in strings and then put those strings in my HTML. It's a tiny overhead but makes the code much more readable in my opinion. Furthermore if you have or repeated links, you have to generate them only once.
I prefer to put
<% string action = Url.RouteUrl("NamedRoute", new
{ controller="User",
action="Items",
UserID=1});%>
and later on write
link

Html.ActionLink("Items", "User", new { UserID = 1 })

Related

Specify HTTP VERB for Url.Action?

I am using AttributeRouting: http://attributerouting.net/
If I have two actions with the same name, but the route for the GET is different than the route for the POST, why is Url.Action generating a URL that matches on my GET action, even though my action method is pointing to the one that is a post?
I have tried passing the verb as an
Url.Action("CreateEdit", new { method = "POST" })
However this returns the string /enrollments/create instead of /enrollments/createedit which corresponds to the POST.
I wouldn't expect this to work but tried it as some route configurations use this technique:
Url.Action("CreateEdit", new { HttpMethodConstraint = new HttpMethodConstraint("POST") })
However both of these don't solve the problem, and the URL is still for my GET route.
I have verified that I can post to a hardcoded URL of /enrollments/createedit so the POST action does indeed have that route, just to rule out the possibility that the POST Action's default [POST] is defaulting to the expected route.
Problem is that I usually avoid hardcoded URL's and use Url.Action or Html.BeginForm (which also exhibits the same issue) so that all URLs are derived from action routes.
Here are how I have defined my actions:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST] //default to "createedit"
public ActionResult CreateEdit()
...
Note this question is not about making the actual request, but is generating route urls.
How do you indicate to Url.Action that it should use the route from the [POST] instead of the GET. There is no overload that take constraints or HTTP Verb? Note that Html.BeginForm exhibits that same issue when generating the action URL attribute, which is clearly a POST oriented method.
It also does not work if I simplify my action routes to(although the above are my goal routes):
[GET("create")]
public ActionResult CreateEdit()
...
[POST("createedit")] //default to "createedit"
public ActionResult CreateEdit()
...
Url.Action("CreateEdit", new { method = "POST" }) returns "/enrollments/create" obviously because method = 'POST' is not the correct way to specify the desired route for Url.Action. Is there a way to tell Url.Action or Html.BeginForm to specify that the POST route is desired? The method param of BeginForm effects the method="post" html attribute, but the action attribute is still generated with the wrong /enrollments/create URL. I've seen area="Admin" used to specify Areas in route parameters. Is there some magic property that can be added to specify the verb? If there is not, then clearly there is no way to get the right URL consistently and I will probably need to rename one of my actions if I need to maintain the desired routes. My hope was there was some route value I could add analogous to new { area = "SomeArea" }. method = "POST" was just my wild guess at what might work.
The request pipeline respects route constraints, if they are both the same route, then it works fine. So if both were [POST("creatededit")] and [GET("create"] then it would work fine because the URLs for both are the same, and then when the actual request is made the distinction is made by the pipeline due to the HttpMethodContraint.
If I use parameters to make the routes distinct, it works but only if I eliminate the [GET("create")]
If Url.Action has no known/supported/documented way to take a route property then the answer may simply be that Url.Action doesn't have any way for you to tell it the desired VERB.
If someone asks: Is there a way to specify the area for Url.Action?
The answer is simply: Url.Action("SomeAction", new { area ="SomeArea" })
The answer is not: muck around with your routes so you don't have two actions of the same name in different areas(or any number of other things that don't address the question).
The answer is not: You shouldn't have actions of the same name in different areas. (It might be a valid point, but it doesn't create a useful resource for others who are in a situation where they absolutely must generate a URL for a different area).
I don't understand what is so complicated about that.
I'm not asking how to make a POST request. I know how to do that, but I don't intend to hardcode URLs into my views. That turns into a maintenance nightmare.
I could change my routes if my boss wasn't so set on SOE. So I can't eliminate the [GET("create")] route.
I can resolve by renaming an action like so:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST("createedit")]
public ActionResult CreateEditPost()
...
That is a bit dirty, but works. It still doesn't answer the question.
If there is a way to specify the VERB for Url.Action, I think the answer would be a useful resource. Maybe not to as many people as Url.Action("SomeAction", new { area ="SomeArea" }) but it would be documenting a feature of Url.Action that for me has been elusive. Maybe it doesn't support it. Perhaps I will need to delve into the source code.
I goofed in my original answer. I wasn't thinking it through really and probably answered too quickly. Here's what it boils down to:
Url.Action is tied pretty inherently to the controller/action style routing. When there's two matching actions (overloads where one is a GET version and the other is a POST), the URL should be the same, and so the GET or really first version is returned. See, AttributeRouting just sort of layers on by letting you customize the outward facing URL, but internally, Url.Action is simply trying to find a route that will get you to the requested action. Once it finds a match, it assumes that's good enough, and especially pre-MVC5, it should have been.
MVC5 introduced attribute routing as a first-class citizen, but from what I've seen, this edge case (where the GET and POST versions of the same action have different URLs and you want the POST version in particular) has not been covered.
That said, I see a couple of potential workarounds:
Use different action names. If your POST action is named something like CreateEditPost, then you can very easily do Url.Action("CreateEditPost") and get the right URL. Since your routes are not affected directly by the action name, it doesn't really matter what it's called.
Use named routes. If you name your routes, then you can use Url.RouteUrl instead and request exactly the route your want. For example:
[POST("createedit", RouteName = "CreateEditPost")]
public ActionResult CreateEdit()
{
...
}
Then:
#Url.RouteUrl("CreateEditPost")

Uniform way of passing an array in querystring in ASP.NET MVC 4

I have a controller's action:
public ActionResult Find(IEnumerable<string> ids) { ... }
I'm succesfully invoking it by route ../Find?ids=01&ids=02&ids=03. DefaultModelBinder easily binds the parameter with no extra configuration.
However, when I'm trying to do outbound routing, i.e. use code like this in my View
#Url.Action("Find", "Grid", new {ids=new List<string>{01,02,03}})
.. I'm getting a nice ..?ids=System.Collections.Generic.List1[System.String] querystring in my URL. I've already found some answers which suggest writing an custom binding/routing, but I believe such a simple task should be solved much easier without any extra code.
P.S.: I'd be perfectly happy to change my querystring format, I just want to rely on some default framework behavior to accomplish the task: pass an array in querystring.
Short version: what is the most simple way to render a link to a controller's action with an array in querystring?
its not elegant way... but simple and fast. Try
#Url.Action("Find", "Grid", new {ids= string.Join("&ids=", YourList)})

MVC custom routing function

This is a more specific version of another of my questions: Restful MVC Web Api Inheritance, I hope an answer to this will help me answer that.
Im using ASP.NET web api,
I want to be able to route something like this: [{object}/{id}]/{controller}/{id}.
so i want an array of objects with optional /{id} ending with the 'api endpoint'.
I want to be able to route these:
/houses
/houses/3
suburbs/3/houses
council/5/suburbs/houses
city/8/council/suburbs/houses
ETC
TO
get(List<restRoute>parents, int id){
...
}
restRoute would be an object with a string for the object and an optional int (or guid etc) for the id
Does anyone know where i can start?
I don't want to route every single one individually.
I had also such problems with routing from the box in ASP.NET MVC. Its good way to be used as common routing, but is not so flexible for custom routs.
In WCF Web Api (ASP.NET web api in CTP version) was used attribute based routing.
I think its more flexible, but as negative point - each method should have routing attribute.
Take a look at this blog post:
http://www.strathweb.com/2012/05/attribute-based-routing-in-asp-net-web-api/
It describes how to implement attribute based routing using ASP.NET Web Api. Because such approach is more flexible for routes you can map to methods, it can be helpful for you.
You could use the {*anything} Variable Segmented URL pattern in your route and handle the splitting up and figuring out of what part of the url corresponds to what bit of data in your method:
Global.asax:
routes.MapRoute(
"Special", // name
"{*allthethings}", // parameters
new { controller = "Special", action = "Sauce" } // defaults
);
SpecialController:
public ActionResult Sauce()
{
string data = RouteData.Values["allthethings"].ToString();
string[] items = data.Split('/');
foreach (string item in items)
{
// do whatever you need to figure out which is what!
}
return View();
}
If you wanted to be a bit cleverer about it you could create your own custom RouteHandler to do the splitting. Something like David Ebb's PK routehandler would probably do the trick, with some customisation to fit your requirements in the processing of the route. You could use this to split up the "allthethings" parameter and turn it into your List<RestRoute> format before passing the request on to the Controller

How route for generation outgoing URL is chosen in ASP.NET MVC?

Good day!
I'm using ASP.NET MVC 2 and T4MVC and it seems some code magic is happening
When I add this to routes table:
routes.MapRoute(
"Login",
"login/",
MVC.Profile.Login()
);
How does framework know that I want this rule to apply when I write something like this in the view to generate outgoing URL:
<%: Url.Action(MVC.Profile.Login() %>
What if I have multiple different rules (with different params) for the same controller/action pair? Which one will be chosen? Is there anywhere a good description of this behavior?
Thanks in advance!
What I would suggest to help you understand how this work is to separate the magic that T4MVC does from what MVC itself does under the cover.
When you write this with T4MVC:
routes.MapRoute(
"Login",
"login/",
MVC.Profile.Login()
);
It's equivalent to writing this with straight MVC:
routes.MapRoute(
"Login",
"login/",
new { controller = "Profile", action = "Login" }
);
And in the view:
Url.Action(MVC.Profile.Login())
Is the same as
Url.Action("Login", "Profile")
T4MVC gives you the benefit of strong typing/intellisense, but in the end what it does is the same as with straight MVC.
Hopefully this helps clear things up a bit :)
It matches the route patterns in the order you define them. Thats why you have the default pattern as the last one. As soon as it finds a matching pattern, it stops looking.
Edit
Parameters are ignored during route matching. Once a controller method has been selected, mvc uses model binding to assign the parameters to the method variables.
If you can explain what type of url structure you are looking to use, we can probably help you more.
Your example is not valid MVC, you would normally pass the controller name, action and any other parameters, then the routing engine would use all that information to determine which route to use, the more routes you have defined, the more information it will probably require to determine the one YOU want to match

Serving HTML or ASPX files with ASP.NET MVC

I want to implemented URL like :
www.domain.com/Business/Manufacturing/Category/Product1
This will give the details of specific product(such as specifications).
Since the list of Categories & Products in each Category are limited, I thought it is not worth to use database for products.
So I want to implement it using the HTML or ASPX files. Also I want the URL to be without the files extention(i.e. URL's should not have .html or .aspx extension)
How can I implement it with ASP.NET MVC? Should I use nested folder structure with HTML/ASPX files in it, so that it corresponds to URL? Then how to avoid extensions in URL?
I am confused with these thoughts
Asp.net Mvc uses the routing library from Microsoft. So it is very easy to get this kind of structure without thinking about the folder structure or the file extensions. With asp.new mvc you do not point a request at a specific file. Instead you point at a action that handles the request and use the parameters to determine what to render and send to the client. To implement your example you can do something like this:
_routes.MapRoute(
"Product",
"Business/Manufacturing/Category/Product{id}",
new {controller = "Product", action = "Details", id = ""}
);
This route will match the url you described and execute the action named "Details" on a controller named "ProductController" (if you are using the default settings). That action can look something like this:
public ActionResult Details(int id) {
return View(string.Format("Product{0}", id);
}
This action will then render views depending on what id the product have (the number after "Product" in the end of your example url). This view should be located in the Views/Product folder if you use the default settings. Then if you add a view named "Product1.aspx" in that folder, that is the view that will be rendered when you visit the url in your example.
All tough it is very possible to do it that way I would strongly recommend against it. You will have to do a lot of duplicated work even if you only have a few products and use partial views in a good way to minimize the ui duplications. I would recommend you use a database or some other kind of storage for you products and use a single view as template to render the product. You can use the same route for that. You just edit your action a little bit. It can look something like this:
public ActionResult Details(int id) {
var product = //get product by id from database or something else
return View(product);
}
This way you can strongly type your view to a product object and you will not have that much duplication.
The routing engine is very flexible, and when you have played around with it and learned how it works you will be able to change your url in just about any way you want without changing any other code or moving any files.
If you're not ready to dive into ASP.Net MVC, you can still get the nice URLs by using URL Rewriting for ASP.Net. That'd be simpler if you're already familiar with ASP.Net WebForms. The MSDN article on URL Rewriting should be a good start:
http://msdn.microsoft.com/en-us/library/ms972974.aspx
I'd be really sure you won't eventually have more products before deciding not to use a database. Both MVC and WebForms would allow you to make one page that dyamically shows a product and still have a nice URL- plus you might save yourself development time down the road. Something to think about.

Resources