路由和 url 中的 ASP.NET MVC 5 文化

Posted

技术标签:

【中文标题】路由和 url 中的 ASP.NET MVC 5 文化【英文标题】:ASP.NET MVC 5 culture in route and url 【发布时间】:2015-12-22 06:30:52 【问题描述】:

我已经翻译了我的 mvc 网站,效果很好。如果我选择另一种语言(荷兰语或英语),内容就会被翻译。 这很有效,因为我在会话中设置了文化。

现在我想在 url 中显示所选的文化(=文化)。 如果它是默认语言,则不应在 url 中显示,只有当它不是默认语言时,才应在 url 中显示。

例如:

对于默认文化(荷兰语):

site.com/foo
site.com/foo/bar
site.com/foo/bar/5

对于非默认文化(英文):

site.com/en/foo
site.com/en/foo/bar
site.com/en/foo/bar/5

我的问题是我总是看到这个:

site.com/nl/foo/bar/5 即使我点击了英文(参见 _Layout.cs)。我的内容是用英文翻译的,但 url 中的路由参数保持在“nl”而不是“en”。

我该如何解决这个问题或我做错了什么?

我尝试在 global.asax 中设置 RouteData 但没有帮助。

  public class RouteConfig
  
    public static void RegisterRoutes(RouteCollection routes)
    
      routes.IgnoreRoute("resource.axd/*pathInfo");
      routes.IgnoreRoute("favicon.ico");

      routes.LowercaseUrls = true;

      routes.MapRoute(
        name: "Errors",
        url: "Error/action/code",
        defaults: new  controller = "Error", action = "Other", code = RouteParameter.Optional 
        );

      routes.MapRoute(
        name: "DefaultWithCulture",
        url: "culture/controller/action/id",
        defaults: new  culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional ,
        constraints: new  culture = "[a-z]2" 
        );// or maybe: "[a-z]2-[a-z]2

      routes.MapRoute(
          name: "Default",
          url: "controller/action/id",
          defaults: new  culture = "nl", controller = "Home", action = "Index", id = UrlParameter.Optional 
      );
    

Global.asax.cs:

  protected void Application_Start()
    
      MvcHandler.DisableMvcResponseHeader = true;

      AreaRegistration.RegisterAllAreas();
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
    

    protected void Application_AcquireRequestState(object sender, EventArgs e)
    
      if (HttpContext.Current.Session != null)
      
        CultureInfo ci = (CultureInfo)this.Session["Culture"];
        if (ci == null)
        
          string langName = "nl";
          if (HttpContext.Current.Request.UserLanguages != null && HttpContext.Current.Request.UserLanguages.Length != 0)
          
            langName = HttpContext.Current.Request.UserLanguages[0].Substring(0, 2);
          
          ci = new CultureInfo(langName);
          this.Session["Culture"] = ci;
        

        HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
        RouteData routeData = RouteTable.Routes.GetRouteData(currentContext);
        routeData.Values["culture"] = ci;

        Thread.CurrentThread.CurrentUICulture = ci;
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
      
    

_Layout.cs(我让用户更改语言的地方)

// ...
                            <ul class="dropdown-menu" role="menu">
                                <li class="@isCurrentLang("nl")">@html.ActionLink("Nederlands", "ChangeCulture", "Culture", new  lang = "nl", returnUrl = this.Request.RawUrl , new  rel = "alternate", hreflang = "nl" )</li>
                                <li class="@isCurrentLang("en")">@Html.ActionLink("English", "ChangeCulture", "Culture", new  lang = "en", returnUrl = this.Request.RawUrl , new  rel = "alternate", hreflang = "en" )</li>
                            </ul>
// ...

CultureController:(=我在 GlobalAsax 中设置会话以更改 CurrentCulture 和 CurrentUICulture)

public class CultureController : Controller
  
    // GET: Culture
    public ActionResult Index()
    
      return RedirectToAction("Index", "Home");
    

    public ActionResult ChangeCulture(string lang, string returnUrl)
    
      Session["Culture"] = new CultureInfo(lang);
      if (Url.IsLocalUrl(returnUrl))
      
        return Redirect(returnUrl);
      
      else
      
        return RedirectToAction("Index", "Home");
      
    
  

【问题讨论】:

【参考方案1】:

这种方法存在几个问题,但归结为工作流程问题。

    您有一个CultureController,其唯一目的是将用户重定向到站点上的另一个页面。请记住RedirectToAction 将向用户的浏览器发送 HTTP 302 响应,这将告诉它在您的服务器上查找新位置。这是不必要的网络往返。 当用户的文化已经在 URL 中可用时,您正在使用会话状态来存储它。在这种情况下,会话状态是完全没有必要的。 您正在阅读来自用户的 HttpContext.Current.Request.UserLanguages,这可能与他们在 URL 中请求的文化不同。

第三个问题主要是因为微软和谷歌对于如何应对全球化有着根本的不同看法。

Microsoft 的(原始)观点是,应为每种文化使用相同的 URL,并且浏览器的 UserLanguages 应确定网站应显示的语言。

Google 的看法是 every culture should be hosted on a different URL。如果您考虑一下,这会更有意义。每个在搜索结果 (SERP) 中找到您的网站的人都希望能够以他们的母语搜索内容。

网站的全球化应该被视为内容而不是个性化 - 您正在向群体人传播一种文化,而不是个人。因此,使用 ASP.NET 的任何个性化功能(例如会话状态或 cookie)来实现全球化通常没有意义 - 这些功能会阻止搜索引擎索引您的本地化页面的内容。

如果您只需将用户路由到新的 URL 就可以将他们带到不同的文化,那么就不用担心了 - 您不需要单独的页面让用户选择他们的文化,只需包含一个链接在页眉或页脚中更改现有页面的文化,然后所有链接将自动切换到用户选择的文化(因为 MVC automatically reuses route values from the current request)。

解决问题

首先,去掉CultureControllerApplication_AcquireRequestState方法中的代码。

文化过滤器

现在,由于文化是一个横切关注点,设置当前线程的文化应该在IAuthorizationFilter 中完成。这可确保在 MVC 中使用 ModelBinder 之前设置区域性。

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。如果当前 URL 为/en/Home/Contact,则生成链接为/en/Home/About

默认文化

最后,我们谈到了您问题的核心。您的默认文化没有正确生成的原因是因为路由是一个 2 路映射,无论您是匹配传入请求还是生成传出 URL,第一个匹配总是获胜。构建 URL 时,第一个匹配项是 DefaultWithCulture

通常,您只需颠倒路线的顺序即可解决此问题。但是,在您的情况下,这会导致传入路由失败。

因此,在您的情况下,最简单的选择是构建一个custom route constraint 以在生成 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/code",
  defaults: new  controller = "Error", action = "Other", code = 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 的内置功能来扫描我们的项目并创建一组路由,然后在将实例添加到我们的 MVC RouteTable 之前为文化和 CultureConstraint 插入一个额外的路由 URL 前缀。

还创建了一个单独的路由来解析 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 
        );
    

【讨论】:

谢谢!这不仅给出了答案,而且让我对内部运作有了更好的了解。我学到了一些额外的想法并使用了像 RouteConstraint 这样的新类!希望每一个答案都那么好! 您只需要确保为每个操作创建默认路由和本地化路由,并且通常需要在映射任何MapRoute 路由之前注册routes.MapMvcAttributeRoutes()。在这种情况下,您可以在routes.LowercaseUrls = true; 之后立即调用它。您需要使用IInlineConstraintResolver 来添加自定义约束as shown here。您还应该使用属性路由的Order 属性来确保它们以正确的顺序注册。 一个写得很好的和彻底的答案!谢谢。 @A.Sideris - 使用上述解决方案,搜索引擎将以它支持的每种语言为网站编制索引。许多(如果不是大多数)用户会通过使用他们的母语搜索来定位该站点,因此他们在访问该站点时不需要更改语言。所有为站点添加书签/存储 URL 的用户都将以他们自己的语言返回。唯一可能以与他们自己的语言不同的语言看到网站的流量是那些手动在浏览器中输入somesite.com 的流量,这可能只是网站总流量的一小部分。 @A.Sideris - 如果您还选择了合理的默认语言(对于网站的大部分流量),则需要手动更改为自己语言的用户数量可以忽略不计。请记住,一些“高级用户”会知道在 URL 的末尾添加 /en。您也可以使用subdomain (en.somesite.com) 而不是每种语言的备用路径,这可能会使其更直观。但我不会担心那些必须手动更改的人 - 只需使用标志图标而不是文本来轻松识别选择控件并将其放在可见位置即可。【参考方案2】:

默认文化修复

NightOwl888 的精彩帖子。虽然缺少一些东西 - 通过反射添加的正常(未本地化)URL生成属性路由也需要默认文化参数,否则您会在 URL 中获得查询参数。

?culture=nl

为了避免这种情况,必须进行以下更改:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Routing;
using System.Web.Routing;

namespace Endpoints.WebPublic.Infrastructure.Routing

    public static class RouteCollectionExtensions
    
        public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, object defaults, object constraints)
        
            MapLocalizedMvcAttributeRoutes(routes, urlPrefix, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints));
        

        public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string urlPrefix, RouteValueDictionary defaults, 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
                            //FIX: needed for default culture on normal attribute route
                            var newDefaults = new RouteValueDictionary(defaults);
                            route.Defaults.ToList().ForEach(x => newDefaults.Add(x.Key, x.Value));
                            var routeWithNewDefaults = new Route(route.Url, newDefaults, route.Constraints, route.DataTokens, route.RouteHandler);
                            var linkGenerationRoute = CreateLinkGenerationRoute(routeWithNewDefaults);
                            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);
        
    

并为属性路由注册:

    RouteTable.Routes.MapLocalizedMvcAttributeRoutes(
        urlPrefix: "culture/",
        defaults: new  culture = "nl" ,
        constraints: new  culture = new CultureConstraint(defaultCulture: "nl", pattern: "[a-z]2") 
    );

更好的解决方案

实际上,一段时间后,我需要添加 url 翻译,所以我深入挖掘,似乎没有必要进行所描述的反射黑客攻击。 ASP.NET 的人想了想,有更清洁的解决方案 - 您可以像这样扩展 DefaultDirectRouteProvider

public static class RouteCollectionExtensions

    public static void MapLocalizedMvcAttributeRoutes(this RouteCollection routes, string defaultCulture)
    
        var routeProvider = new LocalizeDirectRouteProvider(
            "culture/", 
            defaultCulture
            );
        routes.MapMvcAttributeRoutes(routeProvider);
    


class LocalizeDirectRouteProvider : DefaultDirectRouteProvider

    ILogger _log = LogManager.GetCurrentClassLogger();

    string _urlPrefix;
    string _defaultCulture;
    RouteValueDictionary _constraints;

    public LocalizeDirectRouteProvider(string urlPrefix, string defaultCulture)
    
        _urlPrefix = urlPrefix;
        _defaultCulture = defaultCulture;
        _constraints = new RouteValueDictionary()   "culture", new CultureConstraint(defaultCulture: defaultCulture)  ;
    

    protected override IReadOnlyList<RouteEntry> GetActionDirectRoutes(
                ActionDescriptor actionDescriptor,
                IReadOnlyList<IDirectRouteFactory> factories,
                IInlineConstraintResolver constraintResolver)
    
        var originalEntries = base.GetActionDirectRoutes(actionDescriptor, factories, constraintResolver);
        var finalEntries = new List<RouteEntry>();

        foreach (RouteEntry originalEntry in originalEntries)
        
            var localizedRoute = CreateLocalizedRoute(originalEntry.Route, _urlPrefix, _constraints);
            var localizedRouteEntry = CreateLocalizedRouteEntry(originalEntry.Name, localizedRoute);
            finalEntries.Add(localizedRouteEntry);
            originalEntry.Route.Defaults.Add("culture", _defaultCulture);
            finalEntries.Add(originalEntry);
        

        return finalEntries;
    

    private 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 RouteEntry CreateLocalizedRouteEntry(string name, Route route)
    
        var localizedRouteEntryName = string.IsNullOrEmpty(name) ? null : name + "_Localized";
        return new RouteEntry(localizedRouteEntryName, route);
    

有一个基于此的解决方案,包括这里的url翻译:https://github.com/boudinov/mvc-5-routing-localization

【讨论】:

您的 Route.Config 示例丢失 , pattern: "[a-z]2")

以上是关于路由和 url 中的 ASP.NET MVC 5 文化的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC教程五:ASP.NET MVC中的路由

asp.net core 系列 5 MVC框架路由(上)

ASP.net MVC 5 路由:加载基本 url 时出现 ajax 错误,但在最后添加 home/Index 时有效

混合 ASP.NET 和 MVC 路由

ASP.NET MVC 中的动态路由操作名称

ASP.NET MVC Url.Action 和路由名称值