Anyone knows a nice solution to localize routes in ASP.NET MVC? What I'd like to achieve is that these two urls point to the same action/resource:
- http://example.org/Products/Categories (en)
- http://example.org/Produkte/Kategorien (de)
Also there should be the possibility to generate the routes according to the current culture (or the default, if there are no translations available). Alternately, if I was able to specify just one culture so that only one of the two links above would work, that'd be also viable.
I tried a very nice approach by Maarten Balliauw, but his 开发者_如何学JAVAsolution unfortunately doesn't work with Html.RenderAction(...)
.
Of course I could just add routes for all translations like
routes.MapRoute(
"Products_Categories",
"Produkte/Kategorien",
new { controller = "Products", action = "Categories" }
);
but that would end up in an enormous amount of routes and it'd be very unflexible. Any better solution would be appreciated :-) The more flexible the better.
You can try the awesome AttributeRouting project that I just found! You can get it through NuGet.
This might be a viable way to manage all your routes - or some variation of it such as defining the routes in an XML file
http://www.iansuttle.com/blog/post/ASPNET-MVC-Store-Routes-in-the-Database.aspx
you'll still end up with a large number of routes but managing them would be a hell of a lot easier
A simple solution for both attribute and conventional-based routing can be found at https://github.com/boudinov/mvc-5-routing-localization
You can get these working with /de prefix, as it is the preferred schema. You need to have 'Products' and 'Categories' translated to German in a resource file:
- http://example.org/Products/Categories
http://example.org/de/Produkte/Kategorien
[LocalizedRoute("~/Products/Categories", translateUrl: true] ActionResult Index(...)
Or working answer to the original question, without the language prefix, you can get like this:
- http://example.org/Products/Categories
http://example.org/Produkte/Kategorien
[LocalizedRoute("~/Products/Categories", translateUrl: false, explicitCulture: "en")] [LocalizedRoute("~/Produkte/Kategorien", translateUrl: false, explicitCulture: "de")] ActionResult Index(...)
I'm not sure if it is the the right approach, at least not the way you have posted. There should be something in the url that defines the culture, something like:
- http://example.org/en/Products/Categories
- http://example.org/de/Produkte/Kategorien
Otherwise you may end up with a url that is ambiguous between two or more languages/cultures.
MvcCodeRouting has the option to format or translate routes, see this page.
Try using a custom route. This is much more flexible than the alternatives presented here.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Routing;
using System.Web;
using System.Web.Mvc;
public class ProductRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string virutalPath = httpContext.Request.Url.AbsolutePath.Substring(1).ToLowerInvariant();
// Call the database here to retrieve the productId based off of the virtualPath
var productId = Product.GetProductIdFromVirtualPath(virutalPath);
if (productId != Guid.Empty)
{
result = new RouteData(this, new MvcRouteHandler());
result.Values["controller"] = "Product";
result.Values["action"] = "Details";
result.Values["id"] = productId;
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null;
string controller = Convert.ToString(values["controller"]);
string action = Convert.ToString(values["action"]);
if (controller == "Product")
{
string path = string.Empty;
if (action == "Details")
{
Guid productId = (Guid)values["id"];
// Call the database here to get the Virtual Path
var virtualPath = Product.GetVirtualPathFromProductId(productId);
}
if (!String.IsNullOrEmpty(virtualPath))
{
result = new VirtualPathData(this, virtualPath);
}
}
return result;
}
}
You can use the route by adding it directly to your routes table in Global.asax, like this:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"home", // Route name
"", // URL with parameters
new { controller = "Home", action = "Index" } // Parameter defaults
);
// Add your custom route like so
routes.Add(new ProductRoute());
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
Then on your localized product table, simply have a field that contains the path to lookup (without the leading slash). Of course, you didn't mention how you are storing your data, so you will have to come up with that on your own.
Also, you will need to handle your localization by parsing it out of the virtualPath. It SHOULD be there according to Google. You should handle the initial culture based on the headers passed and redirect (302) to the url of the culture that is selected. Then the user should be able to switch the language in which case you can put it in a cookie so their preference is remembered. However, search engines should be able to tell the culture from the URL without passing any headers.
This will handle the case for @Html.RenderAction() through the GetVirtualPath() method, and you can alter the logic if needed. I recommend you add caching because this will make every request hit the database as is. The route table is populated on app startup, but each route is executed on every request.
One more thing - to handle the "route doesn't match" scenario, simply return null and the router will move on to the next configured route. This allows you to configure as many custom routes as needed without mixing logic.
精彩评论