草庐IT

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

coder 2023-07-08 原文

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

现在我想在 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/荷兰 /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 中设置 session 以更改 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");
      }
    }
  }

最佳答案

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

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

  • 第三个问题主要是因为微软和谷歌在如何处理全局化问题上存在根本不同的观点。

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

    谷歌的观点是every culture should be hosted on a different URL .如果你仔细想想,这更有意义。每个在搜索结果 (SERP) 中找到您网站的人都希望能够以他们的母语搜索内容。

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

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

    解决问题

    首先,去掉CultureController以及 Application_AcquireRequestState 中的代码方法。

    文化过滤器

    现在,由于文化是一个跨领域的关注点,设置当前线程的文化应该在 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());
        }
    }
    

    语言选择

    您可以通过链接到当前页面的相同操作和 Controller 并将其作为选项包含在 _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 }
    );
    

    属性路由

    NOTE: This section applies only if you are using MVC 5. You can skip this if you are using a previous version.



    对于 AttributeRouting,您可以通过为每个操作自动创建 2 个不同的路由来简化事情。您需要稍微调整每个路由并将它们添加到 MapMvcAttributeRoutes 相同的类结构中。使用。不幸的是,Microsoft 决定将类型设置为内部类型,因此需要使用反射来实例化和填充它们。

    路由集合扩展

    这里我们只是使用 MVC 的内置功能来扫描我们的项目并创建一组路由,然后为文化和 CultureConstraint 插入一个额外的路由 URL 前缀。在将实例添加到我们的 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 }
            );
        }
    }
    

    关于c# - 路由和 url 中的 ASP.NET MVC 5 文化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32764989/

    有关c# - 路由和 url 中的 ASP.NET MVC 5 文化的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

      刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

    8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

      我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

    9. ruby-on-rails - rails : save file from URL and save it to Amazon S3 - 2

      从给定URL下载文件并立即将其上传到AmazonS3的更直接的方法是什么(+将有关文件的一些信息保存到数据库中,例如名称、大小等)?现在,我既不使用Paperclip,也不使用Carrierwave。谢谢 最佳答案 简单明了:require'open-uri'require's3'amazon=S3::Service.new(access_key_id:'KEY',secret_access_key:'KEY')bucket=amazon.buckets.find('image_storage')url='http://www.ex

    10. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

      我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

    随机推荐