Say I have set up a url structure as follows (ASP.NET MVC2)
http://localhost:XXXX/Product/
Click on link browse by color
http://localhost:XXXX/Product/Color/
Click on link browse red color items by type (i.e. pen's)
http://localhost:XXXX/Product/Color/Red/Pen
In the controller, I will need to do a select based on these criteria. Except when previously, I could go
public ActionResult ShowTypesForColor(string color)
but to do this one:
public ActionResult ShowItems(string type)
I also need the color that was selected.
How could I do this? Is splitting up the url string the only way?
edit: maybe i've gotten ahead of myself in the global.asax.cs
routes.MapRoute(null, "Product/Color/", new { controller = "Product", action = "ShowAllColors" });
routes.MapRoute(null, "Product/Color/{color}", new { controller = "Product", action = "ShowTypesForColor" });
routes.MapRoute(null, "Product/Color/{color}/{type}", new { controller = "Product", action = "ShowDetail" });
I don't think I can define the last one like that can I? with two {} values?
Your last route seems perfectly valid. It will map to action with signature like this:
ActionResult ShowDetails(string color, string type) {
return View(/*view params*/);
}
EDIT I think the order is wrong, so if the last route is not being fired, try doing this:
routes.MapRoute(null, "Product/Color/{color}/{type}", new { controller = "Product", action = "ShowDetail" });
routes.MapRoute(null, "Product/Color/{color}", new { controller = "Product", action = "ShowTypesForColor" });
routes.MapRoute(null, "Product/Color/", new { controller = "Product", action = "ShowAllColors" });
The order of MVC routes should be from most specific to least specific, otherwise the least specific route (/product/color/{color}) will match a url product/color/red/pen before the more specific /product/color/{color}/{type}
You can put multiple tokens in your route (e.g., {color} and {type}) but it's not going the work the way you have it there. Why have you defined "Color" as the second segment of your URL? Why not just do /Products/Red and /Products/Red/Pen? It's inconsistent to do .../Colors/Red and not .../Types/Pen so I'd just leave the "Colors" and "Types" qualifiers out altogether.
I'd define your ShowItems() method like this:
public ActionResult ShowItems(string color, string type)
this will allow you to have a route like /Products/Red/Pen where your route maps to this ShowItems() method. But you'll still need to differentiate that from the ShowTypesForColor() method where it also takes a first parameter of color. The routing framework will just treat type as null - for the route that has both tokens, make sure you have a route constraint specifying that neither color nor type can be null/empty (i.e., for the ShowItems() route).
Related
I have an area called News, and within that area I have a Post Controller. The post controller has the following action:
public ActionResult Index(int id, string name)
{
...
}
I have a route in the NewsAreaRegistration
context.MapRoute(
"News_post",
"News/{controller}/{id}/{name}",
new { action = "Index", controller = "Post",name = UrlParameter.Optional }, new { id = #"\d+"}
);
I have also tried without the name object attribute, with and without the id #"\d+ .. Thing is I have this on another site with the exact same setup, just so confused at why it's not working as expected.
Now firstly, the action will resolve:
http://example.com/News/Post/Index/3
When I want it to resolve to
http://example.com/News/Post/3
And then I also want this string parameter at the end so it should resolve to
http://example.com/News/Post/3/test-post
but instead resolves to
http://example.com/News/Post/Index/3?test-post
I am having a total nightmare with this routing stuff. I have tried to mess around with the Routing attributes but also have no luck there with areas... Any ideas guys?
So I fixed part of the issue by making the Route look like this:
context.MapRoute(
"News",
"News/Post/{id}/{name}",
new { action = "Index", controller = "Post" },
new { id = #"\d+" }
);
So I removed the UrlParamter.Optional for the name argument. I need to really get my head around how some of these routes work. At times it is so easy, but then others I can't get it to do the simplest things.
I am having trouble making #Url.Action work with Area's that have a non standard route structure.
For instance if I register this route in my Dashboard area:
context.MapRoute(
"Dashboard_default",
"Dashboard/{controller}/{action}/{id}",
new { controller = "View", action = "Display", id = UrlParameter.Optional }
);
and then in my layout view I call:
#Url.Action("Select", "View", new { area = "Dashboard" })
I get a proper url: /Dashboard/View/Select
However, if I change the route to include an optional secondary id like this:
context.MapRoute(
"Dashboard_default",
"Dashboard/{controller}/{action}/{id}/{secondaryid}",
new { controller = "View", action = "Display", id = UrlParameter.Optional, secondaryid = UrlParameter.Optional }
);
Then the same call to #Url.Action(...) doesn't return any url. If I specify those optional parameters with real values like so:
#Url.Action("Select", "View", new { area = "Dashboard", id = 1, secondaryid = 2 })
I do get a god return value of: /Dashboard/View/Select/1/2
The problem is that for some of my actions in this area don't need the id or secondary id and I want the url to be generated without them. If i set them to (int?)null it still doesn't work.
Am I doing something wrong? Shouldn't Url.Action(...) return the URL without the id and secondaryid tokens if I dont specify them in the routeValues parameter?
Having multiple optional parameters does funky things to your routes. Basically, the route engine cannot (has trouble) matching one or no optional parameters where there is a group of them. For more information, check out this blog post on the same issue.
Since you don't always need id or secondary id, just make a couple of routes to handle those cases.
context.MapRoute(
"Dashboard_IdAndSecondaryId",
"Dashboard/{controller}/{action}/{id}/{secondaryid}",
new { controller = "View", action = "Display"}
);
context.MapRoute(
"Dashboard_default_WithSecondaryId",
"Dashboard/{controller}/{action}/{secondaryid}",
new { controller = "View", action = "Display"}
);
context.MapRoute(
"Dashboard_default",
"Dashboard/{controller}/{action}/{id}/",
new { controller = "View", action = "Display", id = UrlParameter.Optional}
);
Now, when you send just an Id, just a secondaryId or both, you will have routes that will match. We can remove the optional parameter declarations in the first two routes, because in order to match that route, you would have to send the required parameters. Without sending the required parameters, you would want only the last route to match.
The last route is your default route when only Id or none is sent in the action link. I believe that this order works as well, keeping in mind you want your routes to go from most specific to least specific given that they are processed in order.
So, I've been trying for a few hours now to workaround something that should theoretically be very simple. Let's take this sample url:
http://sample.com/products/in/texas/dallas
This maps onto a specific route:
routes.MapRoute(
"products",
"products/in/{state}/{city}",
new { controller = "Products", action = "List", state = UrlParameter.Optional, city = UrlParameter.Optional });
In my action method, I can do lookups to make sure "texas" and "dallas" exist, and that "dallas" exists within Texas. That's all fine and dandy. However, in the situation where the city doesnt exist (either because the geo is incorrect, or mispelled), I want it to back up the state level. Example:
http://sample.com/products/in/texas/dallax
That should issue a redirect to
http://sample.com/products/in/texas
The "easy" way to do this was to simply issue a Redirect call like so:
return Redirect("/products/in/" + stateName);
However, I'm trying to decouple this from the URL structure; for example, if we ever decided to change how the route looks (say, change the pattern to products/around/{state}/{city}), then I would have to know that I need to make updates to this controller to fix the URL redirect.
If I can make a solution that just inspects the route values and can figure things out, then I don't have to worry if I change the route pattern, because the route values could still be figured out.
Ideally, I would have liked to do something like this:
return RedirectToRoute(new { controller = "Products", action = "List", state = state });
(Note, that is is a simplified example; the "required" route pieces like controller name and action method name would be determined by Generic argument and Expression inspection respectively).
That actually performs the redirect, HOWEVER, the route values from the current request get appended onto the redirect and thus you get in a redirect loop (note that I didn't include the city route value in the route object). How do I stop the "city" route value from being included in this redirect?
I've tried the following things to get rid of the route value:
Compose my own RouteValueDictionary / anonymous route data object and pass that to the overload of RedirectToRoute.
Create my own action result to inspect RouteTable.Routes and find the route myself, and do the replacement of the tokens myself. This seems the most "kludgy", and would seem to be re-inventing the wheel.
Make a method like RedirectWithout that takes a key value and calls RedirectToRouteResult.RouteValues.Remove(key) - which also didnt work.
I've also attempted to add a null value for the key I don't want to add; however, this alters the route to something that isnt correct - ie new { controller = "Products", action = "List", state = stateName, city = (string)null } issues a redirect to /Products/List?state=Texas which is not the right URL.
It all seems to stem from the RedirectToRoute taking the current request context to construct the virtual path data. Is there a workaround?
If you were using T4MVC, you should be able to do something like this:
return RedirectToAction(MVC.Products.List(state, null));
Have you tried this?
return RedirectToRoute(new
{
controller = "Products",
action = "List",
state = state,
city = null,
});
Reply to comments
Maybe MVC is confused because your optional parameter is not at the end. The below should work with the above RedirectToRoute that specifies city = null:
routes.MapRoute(
"products",
"products/in/{state}/{city}",
new
{
controller = "Products",
action = "List",
// state = UrlParameter.Optional, // only let the last parameter be optional
city = UrlParameter.Optional
});
You can then add another route to handle URL's where state is optional:
routes.MapRoute(null, // I never name my routes
"products/in/{state}",
new
{
controller = "Products",
action = "List",
state = UrlParameter.Optional
});
I have website that shows user submitted content in 30+ languages in front page,I am currently using paths like:
http://www.example.com?lang=en
or if it's not first page
http://www.example.com?lang=en&page=2
But that really isn't user or seo friendly.
Is there a way in mvc to route these values to something like
http://www.example.com/en
and
http://www.example.com/en/2
without adding new action in-between like in this case lang:
http://www.example.com/lang/en/2
Update. Here is what I came up from Alexeis answer, in case anybody will need same thing:
In case of just language:
routes.MapRoute("MyLang", "{lang}", new { controller = "Home", action = "Index" },new { lang = #"\D{2}"});
In case you need language and optionally page:
routes.MapRoute("MyLang", "{lang}/{page}", new { controller = "Home", action = "Index", page = UrlParameter.Optional }, new { lang = #"\D{2}", page = #"\d+"});
It should not catch any other paths unless you have Actions with only 2 letters.
You don't need "lang/" for the route to match culture. Simple "{lang}" will do as long as you order routes in a way so other routes are matched correctly. You may also consider constraints on routing parameters to limit number of conflicts.
routes.MapRoute("MyLang", "{lang}",
new { controller = "Home", action = "Home", }
class HomeController{
public ActionResult Home(string lang)
{
return View();
}
}
u can add a new route like this
routes.MapRoute("MyLang", "{action}/{page}",
new { controller = "ControllerName",page=0 }
this way the lang name will automatically be mapped to the action and page will be passed as a parameter provided u have action signature as
public ActionResult English(int page)
obsly the return type and action name can be changed
I'd like to have one route that gives the option of two urls but maps to one action. A good example would be for multilingual application. Lets take english and french for example.
This seems simple at first, technically you can do:
routes.MapRoute(
"the hi route english" ,
"welcome/sayhi/{id}" ,
new { controller = "Welcome" , action = "SayHi" , id = UrlParameter.Optional }
);
routes.MapRoute(
"the hi route french" ,
"bienvenu/direallo/{id}" ,
new { controller = "Welcome" , action = "SayHi" , id = UrlParameter.Optional }
);
But that means that you'll have to define two routes for every action. Or a little better solution, create a custom Route class that takes more params to handle bilingualism.
If I go option 1 or 2, It means I have to define every single routes of the WelcomeController because I cannot use {action} in my route.
Ideally, i'd like to be able to define at least action name via metadata and then grab it via reflection or something.
i.e.:
[ActionName( { "fr", "direallo" }, {"en", "sayhi"})]
public ActionResult SayHi(string id){
//check current thread culture...
}
I am not quite sure where to starts, any ideas? Tips?
Thank you,
You have several options starting points here, roughly they are (in order of implementation complexity):
A route per language (as you outlined above)
A regex route constraint e.g.
routes.MapRoute(
"the hi route",
"{controllerName}/{actionName}/{id}",
new { controller = "Welcome" , action = "SayHi" , id = UrlParameter.Optional },
new { controllerName = #"welcome|bienvenu", actionName = #"sayhi|direallo" }
);
You could create a base controller, which is inherited by a subclass per language and define a language specific action name for each base controller action method
You could create your own (or use the one provided in the answer to the comment by Justin Pihony) custom routing constraint