【问题标题】:ASP.NET MVC, localized routes and the default language for the userASP.NET MVC、本地化路由和用户的默认语言
【发布时间】:2011-04-10 15:36:08
【问题描述】:

我正在使用 ASP.NET MVC 本地化路由。因此,当用户访问英文站点时,它是example.com/en/Controller/Action,而瑞典站点是example.com/sv/Controller/Action

如何确保用户在进入网站时直接使用正确的语言?我确实知道如何获得我想要的语言,这不是问题。我以前做的事情是将这种文化放入RegisterRoutes 方法中。但由于我的页面处于集成模式,我无法从 Application_Start 获取请求。

那么我应该如何确保路线从一开始就正确呢?

【问题讨论】:

    标签: c# asp.net-mvc localization routing


    【解决方案1】:

    您可以在global.asax BeginRequest 中询问该网址是否适合您的网站。 您也可以尝试使用路由来执行此操作,但根据我的经验,如果您不确定第一个参数是 lang,您的路由将非常不稳定。

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        Dim lang As String = "es"
        If not Request.Path.ToLower.StartsWith("sv/") and _
           not Request.Path.ToLower.StartsWith("en/")
            ''//ask the browser for the preferred lang
            Select Case Mid(Request.UserLanguages(0).ToString(), 1, 2).ToLower
              Case "en"
                 Response.Redirect("en/")
              Case "sv"
                 Response.Redirect("sv/")
              Case Else
                 Response.Redirect("sv/") ''//the default
            End Select
        end if
     end sub
    

    未经测试的代码。请原谅我的VB

    【讨论】:

    • 谢谢,我会在这个星期一测试并回复你是否适合我:)
    • 这似乎在创建一些无限循环.. /
    • 它似乎也在重写 CSS 文件等的路径
    【解决方案2】:

    我会这样做。

    ~~免责声明:伪代码~~

    global.asax

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.IgnoreRoute("{*favicon}",
            new { favicon = @"(.*/)?favicon.ico(/.*)?" });
    
        routes.MapRoute(
            "Question-Answer", // Route name
            "{languageCode}/{controller}/{action}", // URL with parameters
            new {controller = "home", action = "index"} // Parameter defaults
            );
    
    }
    

    注意:控制器和/或动作不需要是第一和第二。事实上,它们根本不需要存在,在 url with parameters 部分中。

    然后……

    HomeController.cs

    public ActionResult Index(string languageCode)
    {
       if (string.IsNullOrEmpty(languageCode) ||
          languageCode != a valid language code)
       {
           // No code was provided OR we didn't receive a valid code 
           // which you can't handle... so send them to a 404 page.
           // return ResourceNotFound View ...
       }
    
       // .. do whatever in here ..
    }
    

    奖励建议

    您还可以将Route Constraint 添加到您的路由中,因此它只接受languageCode 参数的某些字符串。 So stealing this dude's code ....

    (更多伪代码)...

    public class FromValuesListConstraint : IRouteConstraint
    {
        public FromValuesListConstraint(params string[] values)
        {
            this._values = values;
        }
    
        private string[] _values;
    
        public bool Match(HttpContextBase httpContext,
            Route route,
            string parameterName,
            RouteValueDictionary values,
            RouteDirection routeDirection)
        {
            // Get the value called "parameterName" from the 
            // RouteValueDictionary called "value"
            string value = values[parameterName].ToString();
    
            // Return true is the list of allowed values contains 
            // this value.
            return _values.Contains(value);
        }
    }
    

    表示你可以这样做......

    routes.MapRoute(
        "Question-Answer", // Route name
        "{languageCode}/{controller}/{action}", // URL with parameters
        new {controller = "home", action = "index"} // Parameter defaults
        new { languageCode = new FromValuesListConstraint("en", "sv", .. etc) }
        );
    

    你有它:)

    我为我的 MVC Api 版本控制做了类似的事情。

    GL :) 希望这会有所帮助。

    【讨论】:

    • 感谢您的建议!第一个问题是我不能在每一个动作中都做这个检查,我有很多。第二个的问题是,如果约束中的languageCode不匹配,我想重定向到默认值
    • @Oskar Kjellin:np - 我会在 10 分钟内放弃另一个答案 .. /me 破解了一些代码。 brb
    【解决方案3】:

    好的..另一个建议。

    为了确保我理解,你想要..

    • 每个 Action 都需要能够弄清楚 LanguageCode 是什么?
    • 如果提供的语言代码无效,则需要将其重置为有效的默认值

    如果是这样..这个答案分为三个部分:-

    1. 添加路线。 (这是我之前回答的剪切粘贴)。

    global.asax

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.IgnoreRoute("{*favicon}",
            new { favicon = @"(.*/)?favicon.ico(/.*)?" });
    
        routes.MapRoute(
            "Question-Answer", // Route name
            "{languageCode}/{controller}/{action}", // URL with parameters
            new {controller = "home", action = "index"} // Parameter defaults
            );
    
    }
    

    更新(基于 cmets)

    所以,如果你想要路由http://www.example.com/sv/account/logon,那么上面的路由就可以了。

    LanguageCode == sv(或 en 或 fr 或您支持的任何语言)

    account == 控制器:AccountController

    login == 行动。

    我说过controller = "home"action="index" 仅意味着如果没有提供这两个参数,则默认为这些值。所以,如果你转到http://www.example.com/sv/account/logon,那么 MVC 框架足够聪明,可以(基于该路由)知道 languageCode 参数 == sv、控制器 == 动作和动作(方法) == 索引。

    注意:路线的顺序很重要。至关重要。当您注册您的路线时,这条路线必须是第一条路线(在 IgonoreRoute 之后)中的一条(如果不是的话)。


    1. 您需要创建a custom ActionFilter,它将在操作执行之前被调用。这是我的快速尝试...

    .

    using System.Linq;
    using System.Web.Mvc;
    
    namespace YourNamespace.Web.Application.Models
    {
        public class LanguageCodeActionFilter : ActionFilterAttribute
        {
            // This checks the current langauge code. if there's one missing, it defaults it.
            public override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                const string routeDataKey = "languageCode";
                const string defaultLanguageCode = "sv";
                var validLanguageCodes = new[] {"en", "sv"};
    
                // Determine the language.
                if (filterContext.RouteData.Values[routeDataKey] == null ||
                    !validLanguageCodes.Contains(filterContext.RouteData.Values[routeDataKey]))
                {
                    // Add or overwrite the langauge code value.
                    if (filterContext.RouteData.Values.ContainsKey(routeDataKey))
                    {
                        filterContext.RouteData.Values[routeDataKey] = defaultLanguageCode;
                    }
                    else
                    {
                        filterContext.RouteData.Values.Add(routeDataKey, defaultLanguageCode);    
                    }
                }
    
                base.OnActionExecuting(filterContext);
            }
        }
    }
    
    1. 现在您需要创建一个 BaseController,您的所有控制器都从该控制器继承。然后,这将创建一个易于访问的 属性,您的所有操作都可以访问它......然后根据该值显示他们想要的任何内容。

    我们开始...(又是伪代码...)

    public abstract class BaseController : Controller
    {
        protected string LanguageCode
        {
            get { return (string) ControllerContext.RouteData.Values["LanguageCode"]; }
        }   
    }
    

    然后我们像这样装饰我们的控制器:)

    [LanguageCodeActionFilter]
    public class ApiController : BaseController
    {
        public ActionResult Index()
        {
            if (this.LanguageCode == "sv") ... // whatever.. etc..
        }
    }
    

    注意我是如何装饰的class .. 不只是每个动作。这意味着类中的所有操作都将受到 ActionFilter 的影响 :)

    此外,您可能希望在 global.asax 中添加一个新路由来处理 NO languageCode .. 并且硬编码默认该值 ...

    喜欢(也未经测试)...

    routes.MapRoute(
        "Question-Answer", // Route name
        "{controller}/{action}", // URL with parameters
        new {controller = "home", action = "index", languageCode = "sv"} // Parameter defaults
    );
    

    这有帮助吗?

    【讨论】:

    • 感谢您的努力!似乎我们正在做某事。困扰我的一件事是您没有使用与我相同的路线。很抱歉之前没有说明。我想要一条类似“page.com/sv/account/logon”的路线。
    • @Oskar Kjellin :为这个答案添加了额外的信息。请重新阅读并发布任何 cmets。 :)
    • 我面临的问题是我希望 page.com/home/index 重定向到 page.com/sv/home/index 如果那是国家.. :)
    • all /page.com/home/index 重定向到 sv 吗?
    • 这取决于我的代码希望用户使用哪种语言。我有一个像“GetLanguageCode”这样的函数来检查一些东西并返回代码
    【解决方案4】:

    我知道这是一个非常古老的问题,但刚刚解决了完整的相关问题,我想我会分享我的解决方案。

    以下是一个完整的解决方案,包括一些额外的技巧,可以轻松更改语言。它允许特定的文化,而不仅仅是特定的语言(但在这个例子中只保留了语言部分)。

    功能包括:

    • 在确定语言时回退到浏览器区域设置
    • 使用 cookie 在访问中保留语言
    • 用 url 覆盖语言
    • 支持通过链接更改语言(例如简单的菜单选项)

    第一步:在RouteConfig中修改RegisterRoutes

    这个新路由包含一个约束(正如其他人也建议的那样),以确保语言路由不会抓取某些标准路径。不需要默认语言值,因为它全部由 LocalisationAttribute 处理(参见步骤 2)。

        public static void RegisterRoutes(RouteCollection routes)
        {
            ...
    
            // Special localisation route mapping - expects specific language/culture code as first param
            routes.MapRoute(
                name: "Localisation",
                url: "{lang}/{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                constraints: new { lang = @"[a-z]{2}|[a-z]{2}-[a-zA-Z]{2}" }
            );
    
            // Default routing
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
    
        }
    

    第 2 步:创建本地化属性

    这将在处理控制器请求之前查看控制器请求,并根据 URL、cookie 或默认浏览器文化更改当前文化。

    // Based on: http://geekswithblogs.net/shaunxu/archive/2010/05/06/localization-in-asp.net-mvc-ndash-3-days-investigation-1-day.aspx
    public class LocalisationAttribute : ActionFilterAttribute
    {
        public const string LangParam = "lang";
        public const string CookieName = "mydomain.CurrentUICulture";
    
        // List of allowed languages in this app (to speed up check)
        private const string Cultures = "en-GB en-US de-DE fr-FR es-ES ro-RO ";
    
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // Try getting culture from URL first
            var culture = (string)filterContext.RouteData.Values[LangParam];
    
            // If not provided, or the culture does not match the list of known cultures, try cookie or browser setting
            if (string.IsNullOrEmpty(culture) || !Cultures.Contains(culture))
            {
                // load the culture info from the cookie
                var cookie = filterContext.HttpContext.Request.Cookies[CookieName];
                if (cookie != null)
                {
                    // set the culture by the cookie content
                    culture = cookie.Value;
                }
                else
                {
                    // set the culture by the location if not specified
                    culture = filterContext.HttpContext.Request.UserLanguages[0];
                }
                // set the lang value into route data
                filterContext.RouteData.Values[LangParam] = culture;
            }
    
            // Keep the part up to the "-" as the primary language
            var language = culture.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries)[0];
            filterContext.RouteData.Values[LangParam] = language;
    
            // Set the language - ignore specific culture for now
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(language);
    
            // save the locale into cookie (full locale)
            HttpCookie _cookie = new HttpCookie(CookieName, culture);
            _cookie.Expires = DateTime.Now.AddYears(1);
            filterContext.HttpContext.Response.SetCookie(_cookie);
    
            // Pass on to normal controller processing
            base.OnActionExecuting(filterContext);
        }
    }
    

    第 3 步:将本地化应用到所有控制器

    例如

    [Localisation]  <<< ADD THIS TO ALL CONTROLLERS (OR A BASE CONTROLLER)
    public class AccountController : Controller
    {
    

    第 4 步:更改语言(例如从菜单中)

    这是有点棘手的地方,需要一些解决方法。

    向您的帐户控制器添加一个 ChangeLanguage 方法。这将从“先前路径”中删除任何现有语言代码,以使新语言生效。

        // Regex to find only the language code part of the URL - language (aa) or locale (aa-AA) syntax
        static readonly Regex removeLanguage = new Regex(@"/[a-z]{2}/|/[a-z]{2}-[a-zA-Z]{2}/", RegexOptions.Compiled);
    
        [AllowAnonymous]
        public ActionResult ChangeLanguage(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                // Decode the return URL and remove any language selector from it
                id = Server.UrlDecode(id);
                id = removeLanguage.Replace(id, @"/");
                return Redirect(id);
            }
            return Redirect(@"/");
        }
    

    第 5 步:添加语言菜单链接

    菜单选项由一个链接组成,新语言被指定为路由参数。

    例如(剃刀示例)

    <li>@Html.ActionLink("English", "ChangeLanguage", "Account", new { lang = "en", id = HttpUtility.UrlEncode(Request.RawUrl) }, null)</li>
    <li>@Html.ActionLink("Spanish", "ChangeLanguage", "Account", new { lang = "es", id = HttpUtility.UrlEncode(Request.RawUrl) }, null)</li>
    

    返回的 URl 是当前页面,经过编码可以成为 URL 的 id 参数。这意味着您需要启用某些转义序列,否则 Razor 会拒绝这些转义序列作为潜在的安全违规行为。

    注意:对于非剃须刀设置,您基本上需要一个具有新语言和当前页面相对 URL 的锚点,路径如下: http://website.com/{language}/account/changelanguage/{existingURL}

    其中 {language} 是新的文化代码,{existingURL} 是当前相对页面地址的 URL 编码版本(这样我们将返回同一页面,并选择新语言)。

    第 6 步:在 URL 中启用某些“不安全”字符

    返回 URL 所需的编码意味着您需要在 web.config 中启用某些转义字符,否则现有 URL 参数将导致错误。

    在您的 web.config 中,在 &lt;system.web&gt; 中找到 httpRuntime 标记(或添加它)并将以下内容添加到其中(基本上删除该属性的标准版本中的 %):

      requestPathInvalidCharacters="&lt;,&gt;,&amp;,:,\,?"
    

    在您的 web.config 中,找到 &lt;system.webserver&gt; 部分并在其中添加以下内容:

    <security>
      <requestFiltering allowDoubleEscaping="true"/>
    </security>
    

    【讨论】:

    • 使用此解决方案验证日期(例如)怎么样?由于模型绑定发生在操作过滤器之前,它将使用服务器的默认区域性验证属性。还是我错过了什么?
    • @Marimba:刚刚在这里找到您的第二条评论,现在了解您的另一点。我们只使用上述内容来提供所有文本的本地化。互联网上的日期输入非常不稳定,通常我们总是使用自定义日期控件(例如 3 个下拉菜单和/或使用日期选择器)。谢谢,
    猜你喜欢
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 2015-01-31
    • 1970-01-01
    • 1970-01-01
    • 2017-07-08
    • 1970-01-01
    • 2011-04-29
    相关资源
    最近更新 更多