Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pre-Append Route if not available

We have implemented a localized version of an ASP.NET MVC website which has a URL structure as following:

url://{language}-{culture}/{controller}/{action}/{id}

In this way we can generate URLs by language which are properly crawled by Google bot:

  1. http://localhost/en-US/Home
  2. http://localhost/fr-FR/Home

The translation is achieved in two places. First we modified the default route of MVC with this one:

routes.MapRoute(
    name: "Default",
    url: "{language}-{culture}/{controller}/{action}/{id}",
    defaults: new
    {
        controller = "Home", 
        action = "Index", 
        id = UrlParameter.Optional,
        language = "en",
        culture = "US"
    }
);

Then we have created an action filter which switch to the current language available in the URL and if not available to the default one:

   public class LocalizationAttribute : ActionFilterAttribute
    {        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {

            string language = (string)filterContext.RouteData.Values["language"] ?? "en";
            string culture = (string)filterContext.RouteData.Values["culture"] ?? "US";

            Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));
            Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));

        }
    }
}

The problem occurs if a user enter http://localhost/Whatever. ASP.NET MVC returns "Route not found". How can I pass a default parameter for the language if the user forgets to pass one? I though that by setting the default value into route config would be enough, but it doesn't work

like image 874
Raffaeu Avatar asked Sep 17 '15 10:09

Raffaeu


1 Answers

You just need another route to handle the case where there is no first segment.

routes.MapRoute(
    name: "Default-Localized",
    url: "{language}-{culture}/{controller}/{action}/{id}",
    defaults: new
    {
        controller = "Home", 
        action = "Index", 
        id = UrlParameter.Optional
    }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new
    {
        language = "en",
        culture = "US",
        controller = "Home", 
        action = "Index", 
        id = UrlParameter.Optional
    }
);

Matching the URL pattern and building the route values collection (based on defaults or the placeholders that can override them) are 2 different steps that are handled by the Route class. Populating route values doesn't happen unless the URL pattern matches first.

Do note that if you use an action filter to set the locale of the current thread that localization won't be available inside of the model binder. A way around that is to use an IAuthorizationFilter instead of ActionFilterAttribute.

using System.Globalization;
using System.Threading;
using System.Web.Mvc;

public class LocalizationFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var values = filterContext.RouteData.Values;

        string language = (string)values["language"] ?? "en";
        string culture = (string)values["culture"] ?? "US";

        CultureInfo ci= CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));

        Thread.CurrentThread.CurrentCulture = ci;
        Thread.CurrentThread.CurrentUICulture = ci;
    }
}

And then add it as a global filter.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new LocalizationFilter());
        filters.Add(new HandleErrorAttribute());
    }
}
like image 172
NightOwl888 Avatar answered Oct 20 '22 15:10

NightOwl888