Routing with dashes and non-english characters - asp.net-mvc

I have a specific routing need that I can't get to work. I've found quite a few answers here on StackOverflow that takes me a bit on the way, but not all the way.
Im naming my controllers and actions in the standard C# way, i.e. the first letter of every word is uppercase:
MyController.MyAction()
To reach this action method, I'd like all of these urls to work:
/my-controller/my-action
/my-cöntroller/my-äction
/MyController/MyAction
/MyCöntroller/MyÄction
(the two last ones are not super important though...)
So there's two things here:
Dashes may be used to separate the words (for readability and SEO
purposes).
Some non-english characters can be used (all replacements
specified - no "magic").
I want to create the links with helpers like this:
#Html.ActionLink("My link text", "MyController", "MyAction")
i.e. the standard way, and this will create the following link:
/my-controller/my-action
Hopefully this could be done without making my routing configuration too messy (e.g. with one route for every action or something), or putting attributes on all action methods, but if thats the only solution I'd like to know.
What I've tried so far is implementing a custom route class overriding GetRouteData() and GetVirtualPath(). It got me closer but not all the way, but I might do something wrong
I had an idea for solving the problem with non-english characters by doing the replacement before the routing is performed, but I haven't found a way to do this yet (see this question).
I'd be really greatful if someone could help me with this, or at least point me in the right direction! :)
Edit: Note that the example urls above are just to describe what I want. In reality there is a lot of urls that must be handled, so I'd prefer some generic solution and not one involving one route for every action or something like that.

Create your resource file
Name Value
myaction1 my-äction
myaction2 my-action
mycontroller1 my-cöntroller
mycontroller2 my-controller
Add following routes in RegisterRoute method in global.asax
routes.MapRoute(
name: "route1",
url: Resources.Actions.mycontroller1 + "/" + Resources.Actions.myaction1 ,
defaults: new { controller = "mycontroller1", action = "myaction1" }
);
routes.MapRoute(
name: "route2",
url: Resources.Actions.mycontroller2 + "/" + Resources.Actions.myaction2,
defaults: new { controller = "mycontroller2", action = "myaction2" }
);
In controller,
[HttpGet]
public ActionResult myaction1()
{
return View("");
}
[HttpGet]
public ActionResult myaction2()
{
return View("");
}
In _Layout.cshtml
#Html.ActionLink(Resources.Actions.myaction1, "myaction1", "mycontroller1")
#Html.ActionLink(Resources.Actions.myaction2, "myaction2", "mycontroller2")

Related

Mvc: Same ActionResult with different name depending url

I would like to know what's the best way to achieve this.
I have an ActionResult in my controller, actually it has news name, now I need to internationlize my website and I can't have the same news name, it must to change depending the country where it's visited.
for example, now I need something like.
www.something.com/en/us/news for english version
www.something.com/co/es/noticias for spanish version
you have the point for the next countries.
I don't think I need to create x methods depending x urls that make exactly the same, but I don't know how to achieve it in a really efficient way ... thanks
You could create a new class TranslatedRoute and TranslationProvider to map the different translations to the same action. Then you can plug these into the routing system and override the default mapping.
Here's a good blog post which describes the idea: http://blog.maartenballiauw.be/post/2010/01/26/Translating-routes-%28ASPNET-MVC-and-Webforms%29.aspx
How does your routing work now? Maybe something like this answer would work, if you don't already use it. Perhaps something variation of this, with the parts of the URL in a different order, to suit your needs. For example, the controller doesn't necessarily have to come first in the route (or at all, in this case just always using the same controller name). Make some sort of map that gets you the word "news" in each different language, using the language code as a key.
// populate this map somewhere - language code to word for "news" (and any other name of the controllers that you have)
var newsControllerMap = new Dictionary<string, string>();
newsControllerMap["en"] = "news"; // etc.
// ...
// inside of the RouteConfig class (MVC 4) or RegisterRoutes() method in Global.asax.cs (MVC 3)
// just making an assumption that whatever class/entity you use ("LanguageAndCountry" in this case) also has a country code to make this easier. Obviously this would be refactored to have better naming/functionality to make sense and meet your needs.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
LanguageAndCountryRepository langRepo = new LanguageAndCountryRepository();
var languagesandCountries = langRepo.GetAllLanguagesWithCountries();
foreach (LanguageAndCountry langAndCountry in languagesandCountries)
{
routes.MapRoute(
"LocalizationNews_" + langAndCountry.LanguageAbbreviation,
langAndCountry.LanguageAbbreviation + "/" + langAndCountry.CountryCode + "/" + newsControllerMap[langAndCountry.LanguageAbbreviation],
new { lang = language.LanguageAbbreviation, country = langAndCountry.CountryCode, controller = "News", action = "Index"});
// map more routes to each controller you have, each controller having a corresponding map to the name of the controller in any given language
}

What do the curly braces mean in a route in MVC?

I'm setting up my routes with an MVC project but im a little confused about the curly braces...
If I have...
routes.MapRoute( "Music", "Music/{name}", new { } );
What is the purpose of the curly braces around name, does this get passed to something? Or does this map to something if I pass a default object in?
They are parameter names that are used in routing requests. For example the default route defines three of them:
{controller}/{action}/{id}
controller and action parameters are for finding your controller action. id parameter can be used as an input in those actions.
When you define a custom route you have to provide controller and action parameters. If they are not defined in your URL, you should provide default values so MVC knows what action to run when a request matches that route.
routes.MapRoute("Music",
"Music/{name}",
new { controller="Music", action="SomeAction" });
Other parameters like id or name like you defined can be used to provide input to actions. In your example, name parameter is passed to matching action like this:
public ActionResult SomeAction(string name)
{
//do something
}
The curlybraces indicate a kind of named wildcard.
The "Music/Index" route will only match the URL Music/Index and nothing else
The "Music/{Name}" route will match any URLs starting with Music, and having anything after the slash. It will match both the URLs Music/metallica and Music/madonna.
With the curly brace, you'll be able to pick up "metallica" or "madonna" from the above URLS as routevalues.
As a final example: With ASP.NET MVC, there's always a standard route. {controller}/{action}/{id}. This route will catch URLs like Music/genre/rock or Product/edit/5.
The resulting routevalues for these two will be:
controller=music, action=genre and id=rock for the first one
controller=product, action=edit and id=5 for the last one.
I'll try to provide a less contrived example.
Routes in ASP.NET MVC are placed into a dictionary, and when there's an incoming request, the MVC pipeline looks at the request and tries to determine what Controller and Action to route it to.
So let's say I have the following controllers: Home, Forum, and Article
And while we're at it, let's say I have the following actions: View, Edit, Create on both the Forum and Article controllers.
Those braces allow me to create one route for both:
routes.MapRoute("Viewing",
{controller}/{action}/{id},
new {controller = "Article", action="" }, //The article controller has precedence
new { controller = "Article|Forum" } //contrived for this example
);
Those braces mean that whatever controller they put in (as long as it's Article or Forum based on the Constraints), the same route works. This keeps me from having to have a route for each and every action in the Forum and Article controller.
I could have just as easily made two routes:
routes.MapRoute("Articles",
article/{action}/{id},
new {controller = "Article" } //The article controller has precedence
);
routes.MapRoute("Forums",
forum/{action}/{id},
new { controller = "forum" }
);
But there's duplication there that doesn't need to be there.
Routes are also pretty tricky things, in that order matters. The top route will be evaluated before the bottom route. If it matches the top route's structure, it will go to that action, even if that's not the right action.
Phil Haack has a Route Debugger that helps with this. And I've also taken his source code and modified it so that you can make it a control and put it on all your pages as a partial (and hopefully you will also put code on there that would only allow internal folks to see it).

How to route legacy type urls in ASP.NET MVC

Due to factors outside my control, I need to handle urls like this:
http://www.bob.com/dosomething.asp?val=42
I would like to route them to a specific controller/action with the val already parsed and bound (i.e. an argument to the action).
Ideally my action would look like this:
ActionResult BackwardCompatibleAction(int val)
I found this question: ASP.Net MVC routing legacy URLs passing querystring Ids to controller actions but the redirects are not acceptable.
I have tried routes that parse the query string portion but any route with a question mark is invalid.
I have been able to route the request with this:
routes.MapRoute(
"dosomething.asp Backward compatibility",
"{dosomething}.asp",
new { controller = "MyController", action = "BackwardCompatibleAction"}
);
However, from there the only way to get to the value of val=? is via Request.QueryString. While I could parse the query string inside the controller it would make testing the action more difficult and I would prefer not to have that dependency.
I feel like there is something I can do with the routing, but I don't know what it is. Any help would be very appreciated.
The parameter val within your BackwardCompatibleAction method should be automatically populated with the query string value. Routes are not meant to deal with query strings. The solution you listed in your question looks right to me. Have you tried it to see what happens?
This would also work for your route. Since you are specifying both the controller and the action, you don't need the curly brace parameter.
routes.MapRoute(
"dosomething.asp Backward compatibility",
"dosomething.asp",
new { controller = "MyController", action = "BackwardCompatibleAction"}
);
If you need to parametrize the action name, then something like this should work:
routes.MapRoute(
"dosomething.asp Backward compatibility",
"{action}.asp",
new { controller = "MyController" }
);
That would give you a more generic route that could match multiple different .asp page urls into Action methods.
http://www.bob.com/dosomething.asp?val=42
would route to MyController.dosomething(int val)
and http://www.bob.com/dosomethingelse.asp?val=42
would route to MyController.dosomethingelse(int val)

How do you change the controller text in an ASP.NET MVC URL?

I was recently asked to modify a small asp.net mvc application such that the controler name in the urls contained dashes. For example, where I created a controller named ContactUs with a View named Index and Sent the urls would be http://example.com/ContactUs and http://example.com/ContactUs/Sent. The person who asked me to make the change wants the urls to be http://example/contact-us and http://example.com/contact-us/sent.
I don't believe that I can change the name of the controller because a '-' would be an illegal character in a class name.
I was looking for an attribute that I could apply to the controller class that would let me specify the string the controller would use int the url, but I haven't found one yet.
How can I accomplish this?
Simply change the URL used in the route itself to point to the existing controller. In your Global.asax:
routes.MapRoute(
"Contact Us",
"contact-us/{action}/",
new { controller = "ContactUs", action = "Default" }
);
I don't believe you can change the display name of a controller. In the beta, the controller was created using route data "controller" with a "Controller" suffix. This may have changed in RC/RTM, but I'm not sure.
If you create a custom route of "contact-us/{action}" and specify a default value: new { controller = "ContactUs" } you should get the result you are after.
You need to configure routing. In your Global.asax, do the following:
public static void RegisterRoutes(RouteCollection routes)
{
...
routes.MapRoute(
"route-name", "contact-us/{action}", // specify a propriate route name...
new { controller = "ContactUs", action = "Index" }
);
...
As noted by Richard Szalay, the sent action does not need to be specified. If the url misses the .../sent part, it will default to the Index action.
Note that the order of the routes matter when you add routes to the RouteCollection. The first matched route will be selected, and the rest will be ignored.
One of the ASP.NET MVC developers covers what Iconic is talking about. This was something I was looking at today in fact over at haacked. It's worth checking out for custom routes in your MVC architecture.
EDIT: Ah I see, you could use custom routes but that's probably not the best solution in this case. Unless there's a way of dealing with the {controller} before mapping it? If that were possible then you could replace all "-" characters.

ASP.NET MVC routing

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 })

Resources