So I have this custom route, which sets up the route table based on culture in the URL, but when I call Url.Action(...), it does not generate the localized URL. Any ideas what I'm doing wrong? The culture is changing on the page and I am able to determine what language user has selected, but Url.Action is not generating localized URL..
This is the custom route, which changes the route table values (not sure if this standard way of doing it):
public class CultureRoute : Route
{
public CultureRoute(string url, object defaults, object contraints)
: base(url, new MvcRouteHandler())
{
base.Defaults = CreateRouteValueDictionary(defaults);
base.Constraints = CreateRouteValueDictionary(contraints);
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null)
{
var culture = routeData.Values["culture"].ToString();
var cookie = httpContext.Request.Cookies["culture"];
var areEqual = false;
if (cookie == null || cookie.Value == "" || !(areEqual = string.Equals(culture, cookie.Value, StringComparison.OrdinalIgnoreCase)))
{
routeData.Values["culture"] = culture;
httpContext.Response.Cookies.Add(new HttpCookie("culture", culture));
}
else if (!areEqual)
{
routeData.Values["culture"] = cookie.Value;
}
CultureHelper.SetCurrentCulture(culture);
}
return routeData;
}
private static RouteValueDictionary CreateRouteValueDictionary(object values)
{
var dictionary = values as IDictionary<string, object>;
if (dictionary != null)
{
return new RouteValueDictionary(dictionary);
}
else
{
return new RouteValueDictionary(values);
}
}
}
and this helper class to set the thread culture:
public class CultureHelper
{
public static void SetCurrentCulture(string culture)
{
var info = CultureInfo.CreateSpecificCulture(culture);
Thread.CurrentThread.CurrentCulture = info;
Thread.CurrentThread.CurrentUICulture = info;
}
public static string GetCurrentCulture(bool ignoreRouteData = false)
{
if (!ignoreRouteData)
{
var routeData = HttpContext.Current.Request.RequestContext.RouteData;
object culture;
if (routeData.Values.TryGetValue("culture", out culture))
{
return culture.ToString();
}
}
var cookie = HttpContext.Current.Request.Cookies["culture"];
if (cookie != null && cookie.Value != null)
{
return cookie.Value;
}
return GetThreadCulture();
}
public static string GetThreadCulture()
{
var culture = Thread.CurrentThread.CurrentCulture.Name;
if (culture.IndexOf('-') > -1)
{
culture = culture.Substring(0, 2);
}
return culture;
}
}
and also the RouteConfig class, which is called from the Global.asax and sets up routes using my custom route class:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("Partial", new CultureRoute(
"{culture}/{cotroller}/partial/{view}",
new { culture = "ka", controller = "home", action = "partial", view = "" },
new { culture = "(ka|en)" }));
routes.Add("Default", new CultureRoute(
"{culture}/{controller}/{action}/{id}",
new { culture = "ka", controller = "home", action = "index", id = UrlParameter.Optional },
new { culture = "(ka|en)" }));
}
}
but without this extension method, I am not able to generate culture based route i.e. Url.Action does not generate URL based on route table the custom route class creates:
public static string Action2(this UrlHelper helper, string action)
{
var culture = CultureHelper.GetThreadCulture();
return helper.Action(action, new { culture = culture });
}
For it to build the URL in ActionLink, you need to override the reverse look-up method named
GetVirtualPath
as well. Here is an example of how I did it (but I am inheritingRouteBase
instead ofRoute
, so yours may need to be done differently).I found that since several of my routes need to be localized and internally I am using the CultureInfo.LCID rather than a culture string to identify culture, that it was better to put the culture parsing code in the
Application_BeginRequest
event in Global.asax. But that may not be necessary if you are only using the culture string internally.BTW - I don't think that using a cookie is necessary in your case since the culture can be derived directly from the URL. It seems like unnecessary overhead, especially when you consider that cookies are transferred on every request (including images and javascript files). Not to mention the security implications of doing it this way - you should at the very least encrypt the value in the cookie. Here is an example that shows how to properly sanitize cookie data.
It was actually something else causing the incorrect behavior. I had an extension method that was generating actions to switch the language and it modified the route data.
I changed it to this and it works:
I do not need to override
GetVirtualPath
method to get it to work.. I think most of the time we can get away withRoute
class and just overridingGetRouteData
, but would like to hear what others think...