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:
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
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());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With