asp net mvc url routing strategy and internationalization - asp.net-mvc

I have a website with urls like this :
www.mysite.com/controller/action/id
I need to create urls for others languages (english), so I have used ALEX ADAMYAN implementation of the MultiCultureMvcRouteHandler. So now I have urls like this :
www.mysite.com/en/controller/action/id
www.mysite.com/fr/controller/action/id
but I need to keep my old urls has default one, ie to have :
www.mysite.com/en/controller/action/id
www.mysite.com/controller/action/id
So I'm wondering how I can modify the routes, in his implementation, alex loops the routes and add the en/fr parameter to all routes so it overwrites former routes, then former urls are dropped, what I want to avoid. If I duplicate all the routes to keep one with fr/en and one without it doesn't works, may be this is because the order is lost in the route collection ?

I solved this problem looping through all my languages:
public static List<Language> Languages;
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
LanguageRepository langRepo = new LanguageRepository();
Languages = langRepo.GetAllLanguages();
foreach (Language language in Languages)
{
routes.MapRoute(
"Localization_" + language.LanguageAbbreviation,
language.LanguageAbbreviation + "/{controller}/{action}/{id}",
new { lang = language.LanguageAbbreviation, controller = "Home", action = "Index", id = UrlParameter.Optional });
}
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional });
}
Where langabbreviation likes "en" or "ru".
The last route is for "default" "withoutlanguage" url like "www.site.com/controller/action" and you need to set up the default lang abbr for it ("en" in my case).
Hope it will help.

Related

How to avoid "/Home" in the url

I am using the standard MVC template in VS 2013.
With the default set up, http://website/ will be routed to website/Home/Index.
How do I route all "actions" directly under website root url, eg http://website/xxx, to show the same content as http://website/Home/xxx? For example, how do I make http://website/About to execute the About action in the Home controller? If possible, the solution shouldn't be a Http redirect to http://website/Home/About because I don't want to show the "ugly" Home/ in the url.
I couldn't find an answer to this that covered all issues one would face with a public facing website without being a pain to upkeep while still maintaining flexibility.
I ended up coming up with the following. It allows for use of multiple controllers, doesn't require any upkeep, and makes all URLs lowercase.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
//set up constraints automatically using reflection - shouldn't be an issue as it only runs on appstart
var homeConstraints = #"^(?:" + string.Join("|", (typeof(Controllers.HomeController)).GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly).Select(x => (x.Name == "Index" ? "" : x.Name))) + #")$";
//makes all urls lowercase, which is preferable for a public facing website
routes.LowercaseUrls = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
//maps routes with a single action to only those methods specified within the home controller thanks to contraints
routes.MapRoute(
"HomeController",
"{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { action = homeConstraints }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
you can try out like the following one
routes.MapRoute(
name: "MyAppHome",
url: "{action}/{wa}",
defaults: new { controller = "Home", action = "Index", wa = UrlParameter.Optional, area = "Admin" },
namespaces: new string[] { "MyApp.Controllers" }
).DataTokens = new RouteValueDictionary(new { area = "Admin" });
Here, you may notice that the Home controller is hardcoded and is no longer to be supplied in the request. you can also make use of the RouteDebugger to play with routes.
HTH

ASP.NET MVC + WebForms - route conflict

I'm building a simple report Web app for rendering reports using ASP.NET MVC3 + WebForms. The reports themselves are rendered by the ReportViewer ASP.NET WebForms control, but I'd like use ASP.NET MVC to create the parameter entry.
I'd like to have that all requests follow the default routing scheme of '~/{controller}/{action}/{parameters}', except requests for ~/Report, which should go to the report rendering WebForm. What's the right way to do this?
Expanding a bit..
I have two routes in Global.asax.cs - the default one and one for the WebForms page.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapPageRoute("report-rendering", "Report", "~/Render.aspx");
}
The URLs get rendered fine, but the problem with this is that when the request comes in, the first route also eats the URLs for the second one, i.e. ~/Report?id=7 tries to call the Index method on the ReportController (which doesn't exist).
If I change it so that the 'report-rendering' route comes before the 'Default' route, like so:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapPageRoute("report-rendering", "Report", "~/Render.aspx");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Now calls to the Html.ActionLink() render incorrect URLs, i.e.
`#Html.ActionLink("Report list", "Index", "ReportList")`
Renders
`http://localhost:49910/Report?action=Index&controller=ReportList`
My current workaround puts the 'Default' route first, while adding a regex constraint to ignore requests for the 'Report' controller, like so:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new { controller = #"(?!report$).*" }
);
This doesn't feel clean. Again, What's the right way of doing this?
Also, I haven't yet decided how I'll pass the parameters to the rendering form: I could use both query parameters or POST them. I'm guessing that query params are more flexible. What's the best practice here?
EDIT:
While researching the answer by #LeftyX, seems like I've found an answer. To quote P. Haack from his Routing chapter in the Professional ASP.NET MVC 3 (Named Routes, Chapter 9, page 233):
... Use names for all your routes and
always use the route name when generating URLs. Most of the time, letting Routing sort out which
route you want to use to generate a URL is really leaving it to chance, which is not something that
sits well with the obsessive-compulsive control freak developer. When generating a URL, you generally
know exactly which route you want to link to, so you might as well specify it by name.
The mentioned section discusses a very similar situation to the one I described.
But since Html.ActionLink() doesn't have an overload with the route name parameter, does this mean I cannot reliably use it anywhere in the entire app if have a route like this?
This is the best solution I've figured out.
I've registered my route with MapPageRoute (I've put my Report page under a folder called Reports)
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapPageRoute(
"report-rendering",
"Report/{id}",
"~/Reports/Report.aspx"
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I've created my link using RouteLink so you can specify the route to use:
#Html.RouteLink("Report list", "report-rendering", new { id = 7 })
and I can get the id in my WebForm page like this:
protected void Page_Load(object sender, EventArgs e)
{
var id = Page.RouteData.Values["id"] as string;
}
Hope it helps.
UPDATE:
I've created an Extension Method to make your life easier:
public static class ExtensionMethods
{
public static MvcHtmlString WebFormActionLink(this HtmlHelper htmlHelper, string linkText, string ruoteName, object routeValues)
{
var helper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var anchor = new TagBuilder("a");
anchor.Attributes["href"] = helper.RouteUrl(routeName, routeValues);
anchor.SetInnerText(linkText);
return MvcHtmlString.Create(anchor.ToString());
}
}
The best would have been to use ActionLink instead of WebFormActionLink but I have problems with signatures and I am not an expert on this.

How to add route parameter before MVC choses a route

I am trying to add support for different languages to existing MVC 3 application. So far my links were
oldUrl -> myapp.com/Item/Details/5/some-title
And there are links to the website from different places on the web. But now I want to change the URL so the language is included:
newUrl -> kmyapp.com/en/Item/Details/5/some-title
However I want the oldUrl links to be valid. And the question is how do I do this... The easiest way would be to add another route, but I already have too many of them and it is getting kind of ugly, and when I create urls from a page that doesn't have the language the links also don't have the language so I have to do something else then.
Another way would be to override something that stands just before MVC tries to map the request to a specific route, but has already parsed the request url and filled the routeValues dictionary. So I can just check for the language and if it is not included in the route parameters I can add it on my own and call the implementation of the parent class to continue. Is that possible? What should I look at - UrlRoutingModule, MvcHttpHandler or something else? What can I override and how to tell MVC to use my version?
Any other ideas would be also appreciated!
There are of course many solutions. I'm going to show you two:
one that you control in your application
one that you control outside of your application (on IIS)
Solution one - Asp.net MVC routing
Provide routing that covers old and new routing:
routes.MapRoute(
"New",
"{lang}/{controller}/{action}/{id}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional },
new { lang = "en|de|it|es|fr" }
);
routes.MapRoute(
"NewEx",
"{lang}/{controller}/{action}/{id}/{title}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional, title = UrlParameter.Optional },
new { lang = "en|de|it|es|fr" }
);
routes.MapRoute(
"Old",
"{controller}/{action}/{id}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"OldEx",
"{controller}/{action}/{id}/{title}",
new { lang = "en", controller = "Home", action = "Index", id = UrlParameter.Optional, title = UrlParameter.Optional }
);
As you can see I've provided language default for the old routes as well since it's not present in the URL. Whether you want that or not is your own decision but this kind of routing makes it possible to not duplicate your controller actions. In any case you'd have to define a default language which can be provided this way.
There's a bigger question whether you still want to support old URLs or you'd rather redirect them (HTTP Redirect Permanent Status 301).
Permanent redirection
Change old routes to:
routes.MapRoute(
"Old",
"{controllerOld}/{actionOld}/{idOld}",
new { controller = "Redirect", action = "Permanent", id = UrlParameter.Optional }
);
routes.MapRoute(
"OldEx",
"{controllerOld}/{actionOld}/{idOld}/{titleOld}",
new { controller = "Redirect", action = "Permanent", id = UrlParameter.Optional, title = UrlParameter.Optional }
);
Then write a controller class that does redirection:
public class RedirectController : Controller
{
public ActionResult Permanent(string controllerOld, string actionOld, string idOld, string titleOld)
{
return RedirectToRoutePermanent(new {
lang = "en",
controller = controllerOld,
action = actionOld,
id = idOld,
title = titleOld
});
}
}
Solution two - IIS URL rewriting module
This solution relies on IIS URL Rewriting module, where you could rewrite any requests without language selection to your preferred default.
I'm not going to wrote how URL Rewriting works here, because there're plenty of web resources with detailed info about that.
Another way would be to override something that stands just before MVC tries to map the request to a specific route, but has already parsed the request url and filled the routeValues dictionary. So I can just check for the language and if it is not included in the route parameters I can add it on my own and call the implementation of the parent class to continue. Is that possible?
Yes It is possible.
You can Override GetRouteData in RouteBase which will have URL details in it.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string url = httpContext.Request.AppRelativeCurrentExecutionFilePath;
}
and in global.aspx add below code.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new MyUrlRoute()); // Add before your default Routes
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
For more detailed implementation - refer blog
url-manipulation-implementing-routebase.html

Routing in MVC not working

I have the following route:
routes.MapRoute(
"Property",
"{language}/property/{propertyUrlId}",
new { controller = "PropertyDetails", action = "Property" }
This is the Controller that should be called for that route:
public class PropertyDetailsController : Controller
{
public ActionResult Property(string language, string propertyUrlId)
{
etc.
And the following URL that should use that route:
http://domain.com/en-us/property/3
Instead, I get 404. Any ideas why?
Here are my routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Property",
"property/{propertyUrlId}",
//new { controller = "PropertyDetails", action = "Property" }, new { language = #"[a-zA-Z]{2}-[a-zA-Z]{2}" }
new { controller = "PropertyDetails", action = "Property" }
);
}
Didn't work with language, or with language/country, either.
You most likely have registered the default route before your Property route. Default route typically looks like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Just register your Property route BEFORE this default route and it will work.
Why it fails? (Assuming you are indeed registering default route first)
en-us -> is interpreted as controller
property -> is interpreted as action
Since you don't have a en-usController with a Property action -> 404
Use "en-us" as a segment of the URL is completely fine. I guess you have registered other routes as well. Try to bring this route to the top of others and at least on top of the default route.
I have tested the scenario, it works just fine for me.
Considering that you want to have the structure of the url as:
http://domain.com/en-us/property/3
use this routing:
routes.MapRoute(
"Property", // Route name
"{language}/property/{propertyUrlId}", // URL with parameters
new { controller = "PropertyDetails", action = "Property", propertyUrlId = UrlParameter.Optional } // Parameter defaults
);
if there is a default routing in your Global.asax file, like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Put the routint above this block of code.
And your Controller Action should look like this:
public ActionResult Property(int propertyUrlId)
{
return View();
}
First of all, there is no reason to break {language} apart into two chunks in the route. As some of you stated, this is fine:
routes.MapRoute(
"Property",
"{language}/property/{propertyUrlId}",
new { controller = "PropertyDetails", action = "Property" }
Second, I omitted some information which was crucial to the solving of this problem. It didn't occur to me to include this in my problem description, as I didn't know there was any relationship. The MVC project is in a solution which also contains a website (non-MVC) which is using the Sitecore CMS as its datastore. Sitecore was stripping out the language segment of the URL and storing it, itself. Once I learned that this was happening, I was able to deal with the problem.
I appreciate all the input, and I apologize for the confusion.

ASP.NET MVC URL routing doesn't give me pretty URLs

I have set up an ASP.NET MVC project, and everything is working great, but I do have one problem with the routing. My Global.asax looks like this:
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
So, nothing out of the ordinary. My problem is that when I link to a controller/action/params with an HTML.ActionLink like so:
<%= Html.ActionLink("My link", "SomeAction", "SomeController", new {param="someParam"})%>
it should generate (at least what makes sense in my head) a link such as: http://www.localhost/SomeController/SomeAction/someParam.
But instead it generates a link like this: http://localhost/SomeController/SomeAction?param=someParam
If i manually make a link that links to the expected result (SomeController/SomeAction/someParam) then the right controller and action are called, but the parameter defined in the action method is always null.
Any ideas?
try adding:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{param}", // URL with parameters
new { controller = "Home", action = "Index", param = "" } // Parameter defaults
);
I think that link will only use the default route like you expect if the parameter name is id instead of param. You'll have to create a different route if you want to provide some other parameter there.

Resources