I took it as starting point and worked on it, the result: a custom route which is able to deal with the dirty stuff about prepending properly the language fragment and matching from the request url.
The idea is to inherit from the Route class instead of RouteBase, I want to take advantage of the base implementation which is responsible of doing the really dirty stuff, I won’t reinvent the wheel. So the methods to override are GetRouteData and GetVirtualPath.
The custom route must know the default language, it has it as parameter, some comments on GetRouteData method. First, the goal for this method is to determine if the current request must be parsed by us or let it go. Manually split by / and remove the ~ element, after that if there are more than one segment and the first segment doesn’t match the regular expression ^[a-z]{2}(-[A-Z]{2})?$ (commented in the earlier post), then ask to base implementation and if it replies affirmative, set the route value indexed by language parameter to the default language. Otherwise, a little trick with the base implementation, save the current url and prepend {lang}/ to the current and ask to base implementation to check if is valid and finally restore the url value. As you can see, here is when I’ve done the work of two routes in one!
public override RouteData GetRouteData(HttpContextBase httpContext) { var requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath; var segments = requestedURL.Split('/').Where(x => x != "~").ToArray(); // if request is not localized then call base if (segments.Length > 0 && !Regex.IsMatch(segments[0], @"^[a-z]{2}(-[A-Z]{2})?$")) { var result = base.GetRouteData(httpContext); if (result != null) result.Values[LangParam] = _defaultLang; return result; } // if request is localized then prepend the culture segment to the url var currentUrl = Url; Url = string.Format("{{" + LangParam + "}}/{0}", _url); var baseRoute = base.GetRouteData(httpContext); Url = currentUrl; // save and restore the current URL return baseRoute; }The next step to complete the route’s functionalities is GetVirtualPath which will generate the url according to route map data. Generate the url is a little harder than match the url, but not impossible. Let’s remember how the url generation occurs: if the dictionary received as parameter has all the necessary values for this route, the optional values may be taken from the current request (this will give us the idea of virtual directories commented in an earlier post). So here the important parameter is the language, find it either in current request or explicit values and remove it (the actual route doesn’t have this paramter). Call base implementation and restore each case if necessary (this is important, or the next routes generated since this will have altered values). Finally to prepend language segment if necessary and only if its value is different from the default language. I show here the code.
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { string lang = "", lnRequest = "", lnValues = ""; // look for the language param in current request context if (requestContext.RouteData.Values.ContainsKey(LangParam)) { // if found then save it in lang variable lnRequest = lang = (string)requestContext.RouteData.Values[LangParam]; // and remove it requestContext.RouteData.Values.Remove(LangParam); } // look for the language param in explicit values if (values.ContainsKey(LangParam)) { lnValues = lang = (string)values[LangParam]; values.Remove(LangParam); } // call base method... var virtualPath = base.GetVirtualPath(requestContext, values); // restore from current request context if necessary if (!string.IsNullOrWhiteSpace(lnRequest)) requestContext.RouteData.Values.Add(LangParam, lnRequest); // restore from explicit values if necessary if (!string.IsNullOrWhiteSpace(lnValues)) values.Add(LangParam, lnRequest); if (virtualPath == null) return null; // prepend language segment if necessary and only if different from the default language if (!string.IsNullOrWhiteSpace(lang) && !string.Equals(lang, _defaultLang, StringComparison.OrdinalIgnoreCase)) { virtualPath.VirtualPath = lang + "/" + virtualPath.VirtualPath; } return virtualPath; }Now, it’s time to invoke this brand-new route just implemented, I think it should be as similar as possible to use as the out of the box route, like this:
routes.AddLocalizedRoute( "es-ES", //The default Language "{controller}/{action}/{id}", // URL with parameters (Without any sign of i18n) new { controller = "SimpleUser", action = "Index", id = UrlParameter.Optional } // Parameter defaults );In order to achieve this I’ve implemented an extension for the class RouteCollection, like the original MapRoute which is an extension too.
No comments:
Post a Comment