MVC 4:自定义路由和Html.Action不同步

所以我有这个自定义路由,它根据URL中的区域性设置路由表,但是当我调用Url.Action(…)时,它不会生成本地化的URL。 我有什么想法我做错了吗? 文化正在改变页面,我能够确定用户选择了哪种语言,但Url.Action没有生成本地化的URL。

这是自定义路由,它会更改路由表值(不确定是否采用这种标准方式):

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; if (dictionary != null) { return new RouteValueDictionary(dictionary); } else { return new RouteValueDictionary(values); } } } 

和这个帮助器类来设置线程文化:

 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; } } 

还有RouteConfig类,它从Global.asax调用并使用我的自定义路由类设置路由:

 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)" })); } } 

但是没有这种扩展方法,我无法生成基于文化的路由,即Url.Action不会根据自定义路由类创建的路由表生成URL:

 public static string Action2(this UrlHelper helper, string action) { var culture = CultureHelper.GetThreadCulture(); return helper.Action(action, new { culture = culture }); } 

要在ActionLink中构建URL,您还需要覆盖名为GetVirtualPath的反向查找方法。 这是我如何做到的一个例子(但我inheritance了RouteBase而不是Route ,所以你的可能需要以不同的方式完成)。

  public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { VirtualPathData result = null; if (requestContext.RouteData.IsAreaMatch(this.area)) { var tenant = this.appContext.CurrentTenant; // Get all of the pages var pages = this.routeUrlPageListFactory.GetRouteUrlPageList(tenant.Id); IRouteUrlPageInfo page = null; if (this.TryFindMatch(pages, values, out page)) { result = new VirtualPathData(this, page.VirtualPath); } } return result; } private bool TryFindMatch(IEnumerable pages, RouteValueDictionary values, out IRouteUrlPageInfo page) { page = null; Guid contentId = Guid.Empty; var action = Convert.ToString(values["action"]); var controller = Convert.ToString(values["controller"]); var localeId = (int?)values["localeId"]; if (localeId == null) { return false; } if (Guid.TryParse(Convert.ToString(values["id"]), out contentId) && action == "Index") { page = pages .Where(x => x.ContentId.Equals(contentId) && x.ContentType.ToString().Equals(controller, StringComparison.InvariantCultureIgnoreCase)) .Where(x => x.LocaleId.Equals(localeId)) .FirstOrDefault(); if (page != null) { return true; } } return false; } 

我发现由于我的几个路由需要本地化和内部我使用CultureInfo.LCID而不是文化字符串来识别文化,所以最好将文化解析代码放在Global.asax中的Application_BeginRequest事件中。 但如果您仅在内部使用文化字符串,则可能没有必要。

顺便说一句 – 我不认为在你的情况下使用cookie是必要的,因为文化可以直接从URL派生。 这似乎是不必要的开销,特别是当您考虑在每个请求(包括图像和javascript文件)上传输cookie时。 更不用说这样做的安全隐患 – 你至少应该加密cookie中的值。 这是一个示例 ,说明如何正确清理cookie数据。

它实际上是导致不正确行为的其他因素。 我有一个扩展方法,它生成切换语言的操作,并修改了路由数据。

 public static string CultureRoute(this UrlHelper helper, string culture = "ka") { var values = helper.RequestContext.RouteData.Values; string actionName = values["action"].ToString(); if (values.ContainsKey("culture")) { values["culture"] = culture; } else { values.Add("culture", culture); } return helper.Action(actionName, HttpContext.Current.Request.QueryString.ToRouteValues()); } 

我把它改成了这个并且它有效:

 public static string CultureRoute(this UrlHelper helper, string culture = "ka") { var values = helper.RequestContext.RouteData.Values; string actionName = values["action"].ToString(); return helper.Action(actionName, new { culture = culture }); } 

我不需要重写GetVirtualPath方法来使它工作。我认为大多数时候我们可以使用Route类并且只是覆盖GetRouteData ,但是想听听其他人的想法……