I'm creating a MVC5 web site that should support multiple languages. The structure of the app is complex so I'm using Attribute Routing only. RouteConfig.cs is very simple:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
}
I want to keep language information in URL by adding language identifier after site name. For English, which is default language, URL should remain "clean". Here are an example:
http://test.com/foo/1/bar/2
http://test.com/de/foo/1/bar/2
My first attempt was to use two RoutePrefix attributes for each controller:
[RoutePrefix("foo")]
[RoutePrefix("{lang}/foo")]
But MVC doesn't allow to use more than one RoutePrefix attribute for a controller.
So now I'm not using RoutePrefix attributes at all, and specify full route for each controller action:
[Route("foo/{a}/bar/{b}")]
[Route("{lang}/foo/{a}/bar/{b}")]
Is there any better way to handle lang route? Ideally I would like to specify in one place only, not for every controller.
PS. I'm setting current thread culture by parsing language route in custom filter.
I found the easiest solution (at least for ASP.NET MVC 5.2) is to use a default value for the optional parameter.
For example, if English is your default language, you can use a single attribute on the Controller:
[RoutePrefix("{lang=en}/foo")]
or a single attribute on the Action:
[Route("{lang=en}/foo/bar")]
Note that optional URI parameters (e.g. {lang?}) don't work if they are at the start of the URL.
If all else fails then I suggest you keep your default routes as is and store the language in a query parameter
For English, your default language, URL will remain "clean" as you intended.
http://test.com/foo/1/bar/2
But for other languages you include lang as query parameter
http://test.com/foo/1/bar/2?lang=De-DE
Then in your custom filter, check if the query parameter is present. If it is then change culture to match. Otherwise use default language.
Also: You should use 5 character encoding and not 2 char globalization ISO code. De-DE and not DE or fr-FR and not FR
You basically need three things:
A multi-language aware route to handle incoming URLs (if you're using MVC5 or above you could also go for Attribute-based routing instead, but I still prefer to use a global rule to handle this).
A LocalizationAttribute to handle these kinds of multi-language requests.
An helper method to generate these URLs within your application (Html.ActionLink and/or Url.Action extension methods).
See this answer for further details and code samples.
Code sample for multi-language Html.ActionLink extension
Code sample for multi-language Url.Action extension
(both are written in C# - affiliation disclaimer: I made those samples)
For additional info and further samples you can also read this blog post I wrote on this topic.
http://www.ryadel.com/en/html-actionlink-extension-method-written-c-handle-multi-language-routes-asp-net-mvc/
Related
I am working on the MVC application that loads controller at runtime from an external assembly with MEF and registers routes using custom route handler I have implemented with help of this tutorial:
http://haacked.com/archive/2010/01/17/editable-routes.aspx/
However recently I discovered a powerful features in MVC 5.1, called Attribute Routing.
I would like to know if that is possible to create a custom Attribute Routing that registers Attribute Routing at runtime?
No. This method basically just turns RouteConfig.cs into a runtime-compiled assembly instead of something that's pre-built by the time your application runs. Frankly, I think that is just about one of the most horrible ideas ever. I give credit to the author for being clever enough to figure out how to do something like that. It's a wonderful thought exercise, but as production code: OMFG, no way.
With Attribute Routing, all your routes are in your controllers, so to do the same thing would require making all your controllers individual runtime-compiled assemblies, and that goes beyond scary. Don't do that.
Yeah, this might sound odd but we have a third party library which enable a sort of service consumed by a JavaScript application. This third party library take care of calling the correct controller and action.
This works all very well but we now need to restrict the routes to this "ServiceController" and maybe one or two other controllers. In the current version (MVC2) we simply override the controller factory which statically checks for a valid controller. Yeah it worked but it was more a workaround which then stayed there forever. So is there any clean way for archiving this (by configuring the routes? Note that we don't use the authorize attribute. And we will be using MVC4 in the next release)
Add global filter and put logic into OnActionExecuting (http://msdn.microsoft.com/en-us/library/gg416513(v=vs.98).aspx)
Option 1: Check routeValues for a correct controller and action
var controllerName = filterContext.RouteData.Values["controller"];
var actionName = filterContext.RouteData.Values["action"];
Option 2: Check this value Request.Url
Actually you allow only the routes defined and nothing else, so the question is really a little odd.
I have a RouteCollection and a string with an URL.
Has anybody an idea how can I get the name of the route that would be executed?
Some method like RouteCollection.PredictRoute(url) that will return the route name or the physical file that would be executed.
I want to create some statistics from web server log files. It would be very nice to reuse the used RouteCollection for that statistic.
Many thanks!
Here is a snippet of code I used to test that URLs were being mapped to their correct controller and action. You should be able to inspect the route name as well.
RouteConfig.RegisterRoutes(routes);
RouteData routeData = routes.GetRouteData(context);
string controller = routeData.Values["controller"];
string action = routeData.Values["action"];
The variable routes is a RouteCollection object and context is a HttpContextBase object.
I assume you want to do this outside of your MVC application, where requests and contexts are not available (only the log files, as you mentioned).
Unfortunately, that is not very easy to do. In order to write such a matching method you need to reuse (or worse, rewrite) the MVC framework code for URL parsing found in various classes in namespace System.Web.Http.Routing. The source code is available on CodePlex, under Apache 2.0 license.
Instead, I think there's an easier way you can include the route names in the log files and use them later in statistics. Have a look below.
What you could to do is extend the default MvcHandler class and override its ProcessRequest method. In there, you can call the base class implementation and right after it do your stuff by using RequestContext.RouteData.
The class RouteData contains, besides other things, also the current route (which you defined in your route collection. That is accessible through the RouteData.Route property.
This works because MvcHandler is always delegated (unless you've created your own MVC infrastructure) by the MvcRouteHandler, to handle the processing for each request. It is the one that gets the controller data from the existing route data, instantiates an IController implementation, and ultimately executes it.
How do I configure an IgnoreRoute to ignore all files with a certain extension, regardless of what directory they're in?
If I do this, everything works and my Flash movie gets played:
routes.Ignore("community/123/plan/456/video/moviename.flv");
So the "123" and "456" sections are variable and can be any integer number. Obviously though, I don't want to do one of these for each movie NOR do I have a need to replace 123 and 456 with variable placeholders. This is only an example of one type of directory, there are Flash movies stored throughout the application so what I need is an IgnoreRoute value that will ignore files that have a .flv extension no matter where in the hierarchy they are.
I've tried the following:
routes.IgnoreRoute("{file}.flv");
routes.IgnoreRoute("(.*).flv(.*)"); // Yeah I know, I'm horrible at RegEx
The only thing I can get to work so far is specifically passing the full relative path to the FLV file. Any suggestions?
Check this article by Phil Haack: http://haacked.com/archive/2008/07/14/make-routing-ignore-requests-for-a-file-extension.aspx
Long story short, we didn’t want routing to attempt to route requests
for static files such as images. Unfortunately, this caused us a
headache when we remembered that many features of ASP.NET make
requests for .axd files which do not exist on disk.
To fix this, we included a new extension method on RouteCollection,
IgnoreRoute, that creates a Route mapped to the StopRoutingHandler
route handler (class that implements IRouteHandler). Effectively, any
request that matches an “ignore route” will be ignored by routing and
normal ASP.NET handling will occur based on existing http handler
mappings.
Hence in our default template, you’ll notice we have the following
route defined.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
This handles the standard .axd requests.
........
We only allow one catch-all route and it must happen at the end of the
URL. However, you can take the following approach. In this example, I
added the following two routes.
routes.IgnoreRoute("{*allaspx}", new {allaspx=#".*\.aspx(/.*)?"});
routes.IgnoreRoute("{*favicon}", new {favicon=#"(.*/)?favicon.ico(/.*)?"});
What I’m doing here is a
technique Eilon showed me which is to map all URLs to these routes,
but then restrict which routes to ignore via the constraints
dictionary. So in this case, these routes will match (and thus ignore)
all requests for favicon.ico (no matter which directory) as well as
requests for a .aspx file. Since we told routing to ignore these
requests, normal ASP.NET processing of these requests will occur.
I'd like to ignore multiple wildcard routes. With asp.net mvc preview 4, they ship with:
RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
I'd also like to add something like:
RouteTable.Routes.IgnoreRoute("Content/{*pathInfo}");
but that seems to break some of the helpers that generate urls in my program. Thoughts?
There are two possible solutions here.
Add a constraint to the ignore route to make sure that only requests that should be ignored would match that route. Kinda kludgy, but it should work.
RouteTable.Routes.IgnoreRoute("{folder}/{*pathInfo}", new {folder="content"});
What is in your content directory? By default, Routing does not route files that exist on disk (actually checks the VirtualPathProvider). So if you are putting static content in the Content directory, you might not need the ignore route.
This can be quite tricky.
When attempting to figure out how to map route data into a route, the system currently searches top-down until it finds something where all the required information is provided, and then stuffs everything else into query parameters.
Since the required information for the route "Content/{*pathInfo}" is entirely satisfied always (no required data at all in this route), and it's near the top of the route list, then all your attempts to map to unnamed routes will match this pattern, and all your URLs will be based on this ("Content?action=foo&controller=bar")
Unfortunately, there's no way around this with action routes. If you use named routes (f.e., choosing Html.RouteLink instead of Html.ActionLink), then you can specify the name of the route to match. It's less convenient, but more precise.
IMO, complex routes make the action-routing system basically fall over. In applications where I have something other than the default routes, I almost always end up reverting to named-route based URL generation to ensure I'm always getting the right route.