这种方法存在多个问题,但是归结为工作流程问题。
- 您
CultureController
的唯一目的是将用户重定向到网站上的另一个页面。请记住,RedirectToAction
会将HTTP 302响应发送到用户的浏览器,这将告诉它在服务器上查找新位置。这是跨网络不必要的往返。 - 您正在使用会话状态存储用户的区域性(如果URL中已有该区域性)。在这种情况下,完全不需要会话状态。
- 您正在
HttpContext.Current.Request.UserLanguages
从用户那里读取,这可能与他们在URL中要求的区域性有所不同。
第三个问题主要是由于微软和Google在如何处理全球化方面存在根本不同的看法。
微软的(原始)观点是,每种文化都应使用相同的URL
UserLanguages,而浏览器的URL 应该确定网站应显示的语言。
Google的观点是,每种文化都应托管在不同的URL上。如果您考虑一下,这更有意义。希望每个在搜索结果(SERP)中找到您的网站的人都能够以其母语搜索内容。
网站的全球化应该被视为 内容 而不是个性化-您是在向 一群
人而不是个人传播一种文化。因此,使用ASP.NET的任何个性化功能(例如会话状态或cookie)来实现全球化通常是没有意义的-
这些功能会阻止搜索引擎索引本地化页面的内容。
如果您可以简单地通过将用户路由到新的URL来使用户具有不同的文化,则无需担心-
您不需要单独的页面供用户选择其文化,只需在标题中包含一个链接或页脚更改现有页面的区域性,然后所有链接将自动切换到用户选择的区域性(因为MVC
自动重用当前请求中的路由值)。
解决问题
首先,摆脱
CultureController和
Application_AcquireRequestState方法中的代码。
CultureFilter
现在,由于文化是一个跨领域的问题,因此应在中设置当前线程的文化
IAuthorizationFilter。这样可以确保
ModelBinder在MVC中使用之前先设置区域性。
using System.Globalization;using System.Threading;using System.Web.Mvc;public class CultureFilter : IAuthorizationFilter{ private readonly string defaultCulture; public CultureFilter(string defaultCulture) { this.defaultCulture = defaultCulture; } public void onAuthorization(AuthorizationContext filterContext) { var values = filterContext.RouteData.Values; string culture = (string)values["culture"] ?? this.defaultCulture; CultureInfo ci = new CultureInfo(culture); Thread.CurrentThread.CurrentCulture = ci; Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(ci.Name); }}您可以通过将其注册为全局过滤器来全局设置过滤器。
public class FilterConfig{ public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CultureFilter(defaultCulture: "nl")); filters.Add(new HandleErrorAttribute()); }}语言选择
您可以通过链接到当前页面的相同动作和控制器,并将其作为选项包含在页面的页眉或页脚中来简化语言选择
_Layout.cshtml。
@{ var routevalues = this.ViewContext.RouteData.Values; var controller = routevalues["controller"] as string; var action = routevalues["action"] as string;}<ul> <li>@Html.Actionlink("Nederlands", @action, @controller, new { culture = "nl" }, new { rel = "alternate", hreflang = "nl" })</li> <li>@Html.Actionlink("English", @action, @controller, new { culture = "en" }, new { rel = "alternate", hreflang = "en" })</li></ul>如前所述,页面上的所有其他链接将自动从当前上下文传递一种区域性,因此它们将自动停留在同一区域性中。在这些情况下,没有理由明确地传递文化。
@Actionlink("about", "about", "Home")使用上述链接,如果当前URL为
/Home/Contact,则生成的链接将为
/Home/about。如果当前网址为
/en/Home/Contact,则链接将生成为
/en/Home/about。
默认文化
最后,我们深入您的问题。无法正确生成默认区域性的原因是,路由是2向映射,并且无论您是匹配传入的请求还是生成传出的URL,始终都会赢得第一个匹配项。构建网址时,第一个匹配项是
DefaultWithCulture。
通常,您可以简单地通过反转路由顺序来解决此问题。但是,在这种情况下,这将导致传入路由失败。
因此,您遇到的最简单的选择是建立一个自定义路由约束,以在生成URL时处理默认区域性的特殊情况。提供默认区域性时,您只需返回false,这将导致.NET路由框架跳过该
DefaultWithCulture路由并移至下一个注册的路由(在本例中为
Default)。
using System.Text.Regularexpressions;using System.Web;using System.Web.Routing;public class CultureConstraint : IRouteConstraint{ private readonly string defaultCulture; private readonly string pattern; public CultureConstraint(string defaultCulture, string pattern) { this.defaultCulture = defaultCulture; this.pattern = pattern; } public bool Match( HttpContextbase httpContext, Route route, string parameterName, RoutevalueDictionary values, RouteDirection routeDirection) { if (routeDirection == RouteDirection.UrlGeneration && this.defaultCulture.Equals(values[parameterName])) { return false; } else { return Regex.IsMatch((string)values[parameterName], "^" + pattern + "$"); } }}剩下的就是将约束添加到路由配置中。您还应该删除
DefaultWithCulture路由中的区域性默认设置,因为无论如何,只要URL中提供了区域性,您都只希望使其匹配。
Default另一方面,该路由应具有一种文化,因为无法通过URL传递该路由。
routes.LowercaseUrls = true;routes.MapRoute( name: "Errors", url: "Error/{action}/{pre}", defaults: new { controller = "Error", action = "Other", pre = UrlParameter.Optional } );routes.MapRoute( name: "DefaultWithCulture", url: "{culture}/{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") } );routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional });属性路由
注意: 本部分仅在使用MVC 5时适用。如果使用早期版本,则可以跳过此部分。
对于AttributeRouting,您可以通过自动为每个动作创建2条不同的路线来简化操作。您需要对每条路线进行一些微调,并将它们添加到使用的相同类结构中
MapMvcAttributeRoutes。不幸的是,Microsoft决定将这些类型设为内部类型,因此需要Reflection实例化并填充它们。
RouteCollectionExtensions
在这里,我们只使用MVC的内置功能来扫描我们的项目并创建一组路由,然后为区域性插入一个附加的路由URL前缀,然后再将
CultureConstraint实例添加到我们的MVC
RouteTable中。
还创建了一个单独的路由来解析URL(与AttributeRouting进行路由的方式相同)。
using System;using System.Collections;using System.Linq;using System.Reflection;using System.Web.Mvc;using System.Web.Mvc.Routing;using System.Web.Routing;public static class RouteCollectionExtensions{ public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, object constraints) { MapLocalizedMvcAttributeRoutes(routes, urlPrefix, new RoutevalueDictionary(constraints)); } public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, RoutevalueDictionary constraints) { var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc"); var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc"); FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance); var subRoutes = Activator.CreateInstance(subRouteCollectionType); var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes); // Add the route entries collection first to the route collection routes.Add((Routebase)routeEntries); var localizedRouteTable = new RouteCollection(); // Get a copy of the attribute routes localizedRouteTable.MapMvcAttributeRoutes(); foreach (var routebase in localizedRouteTable) { if (routebase.GetType().Equals(routeCollectionRouteType)) { // Get the value of the _subRoutes field var tempSubRoutes = subRoutesInfo.GetValue(routebase); // Get the PropertyInfo for the Entries property PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries"); if (entriesInfo.PropertyType.GetInterfaces().Contains(typeof(IEnumerable))) { foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(tempSubRoutes)) { var route = routeEntry.Route; // Create the localized route var localizedRoute = CreateLocalizedRoute(route, urlPrefix, constraints); // Add the localized route entry var localizedRouteEntry = CreateLocalizedRouteEntry(routeEntry.Name, localizedRoute); AddRouteEntry(subRouteCollectionType, subRoutes, localizedRouteEntry); // Add the default route entry AddRouteEntry(subRouteCollectionType, subRoutes, routeEntry); // Add the localized link generation route var localizedlinkGenerationRoute = CreatelinkGenerationRoute(localizedRoute); routes.Add(localizedlinkGenerationRoute); // Add the default link generation route var linkGenerationRoute = CreatelinkGenerationRoute(route); routes.Add(linkGenerationRoute); } } } } } private static Route CreateLocalizedRoute(Route route, string urlPrefix, RoutevalueDictionary constraints) { // Add the URL prefix var routeUrl = urlPrefix + route.Url; // Combine the constraints var routeConstraints = new RoutevalueDictionary(constraints); foreach (var constraint in route.Constraints) { routeConstraints.Add(constraint.Key, constraint.Value); } return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler); } private static RouteEntry CreateLocalizedRouteEntry(string name, Route route) { var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized"; return new RouteEntry(localizedRouteEntryName, route); } private static void AddRouteEntry(Type subRouteCollectionType, object subRoutes, RouteEntry newEntry) { var addMethodInfo = subRouteCollectionType.GetMethod("Add"); addMethodInfo.Invoke(subRoutes, new[] { newEntry }); } private static Routebase CreatelinkGenerationRoute(Route innerRoute) { var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.linkGenerationRoute, System.Web.Mvc"); return (Routebase)Activator.CreateInstance(linkGenerationRouteType, innerRoute); }}然后,只需调用此方法即可
MapMvcAttributeRoutes。
public class RouteConfig{ public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // Call to register your localized and default attribute routes routes.MapLocalizedMvcAttributeRoutes( urlPrefix: "{culture}/", constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") } ); routes.MapRoute( name: "DefaultWithCulture", url: "{culture}/{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]{2}") } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional } ); }}


