Make URL language-specific (via routes)

2019-01-22 15:32发布

问题:

Its hard to find an answer to this specific question, so ill pop it up here:

We want to build up our urls completely language specific, meaning

www.mysite.com/EN

www.mysite.com/DE

this is done in the RouteConfig with

url: "{language}/{controller}/{action}/{id}"

But then the tricky part:

www.mysite.com/EN/Categories

www.mysite.com/DE/Kategorien

I.e. make the controllername appear in a multiple languages, but point to the same controller. Is this even possible?

回答1:

Well, this is partially possible (the language part only). The controller name in a different language is definitely an interesting point, but I think it would be hard to achieve that. Think about what it would look like for Bidi languages like Arabic and Hebrew. It would probably be a good idea to use controller in different languages but you would create a havoc for yourself and I believe that you would have to change the underlying MVC structure to allow for this.

The language changing part is easy and could be done like below.

What you might want to look at is the globalization. Basically the language part corresponds to the current threads UI culture. What you need is the following:

  1. Define a route, something like this:

    var lang = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
    
    routes.MapRoute(
        name: "Default",
        url: "{language}/{controller}/{action}/{id}",
        defaults: new { language = lang, controller = "Home", 
                        action = "Index", id = UrlParameter.Optional }
     );
    
  2. Register Application_AcquireRequestState and define it something like this:

    protected void Application_AcquireRequestState()
    {
        var routes = RouteTable.Routes;
    
        var httpContext = Request.RequestContext.HttpContext;
        if (httpContext == null) return;
    
        var routeData = routes.GetRouteData(httpContext);
    
        var language = routeData.Values["language"] as string;
        var cultureInfo = new CultureInfo(language);
    
        System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo;
        System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo;
    }
    

    Although CurrentUICulture is what you need switch languages load information from a resource file, you should also change the CurrentCulture to the same CultureInfo;

  3. Last, make sure you have corresponding resource files and a fallback resource file.

I used Name property of CultureInfo so you German would be de-DE, English en-US, etc. This should do the trick.

In case you need more information I could upload a sample MVC project for you to examine.

UPDATE: One crude way of doing what you want is to invert the order of route segments to something like this:

routes.MapRoute(
    name: "NewDefault",
    url: "{language}/{id}/{action}/{controller}",
    defaults: new { language = lang, controller = "Home", action = "Index", id = "Category"}
);

This way you could make a following request http://www.your_url.com/de/Kategorien. In this case Kategorien maps to the id rather than a controller. You controller stays in English or German (depending how you named it) but user sees a different language. In the background your view might be something like this:

public ActionResult Index(string id, string categoryName)
{
    // Fetch data based on category name (categoryName)
    return View();
}

You could pass additional information as parameters, but you will need to adjust your route to look something like: {language}/{category}/{subcategory}/{action}/{controller}

Just know that this can become pain in the neck in the long run and if you try this, then make sure you document it well.



回答2:

I think there is more usefull solution istead of translating controller name, Prameter-less routing is very costmizable, modern and SEO frindly.

The main idea is to store your route in database, with related controller, action and parameters. Then you can match that when user request any url.

var httpContext = Request.RequestContext.HttpContext;
if (httpContext == null) return;

var routeData = routes.GetRouteData(httpContext);

var slug = routeData.Values["slug"] as string;

var urlRecord = urlRecordService.GetBySlugCached(slug);

//process URL
switch (urlRecord.EntityName.ToLowerInvariant()) {
  case "product":
    {
      data.Values["controller"] = "Product";
      data.Values["action"] = "ProductDetails";
      data.Values["productid"] = urlRecord.EntityId;
      data.Values["SeName"] = urlRecord.Slug;
    }
    break;
  case "category":
    {
      data.Values["controller"] = "Catalog";
      data.Values["action"] = "Category";
      data.Values["categoryid"] = urlRecord.EntityId;
      data.Values["SeName"] = urlRecord.Slug;
    }
    break;
    .....
}

This method is used in Nop-commerce you can download the source code and check this article NopCommerce ID-Less URL Structure

The problem of this solution are: you should mentain your urls and performance.