代码之家  ›  专栏  ›  技术社区  ›  Oskar Kjellin

ASP.NET MVC、本地化路由和用户的默认语言

  •  11
  • Oskar Kjellin  · 技术社区  · 15 年前

    我正在使用ASP.NET MVC本地化路由。所以当用户访问英语网站时 example.com/en/Controller/Action 瑞典的网站是 example.com/sv/Controller/Action .

    如何确保用户进入网站时,直接使用正确的语言?我知道如何得到我想要的语言,这不是问题。我过去常做的就是把这种文化融入 RegisterRoutes 方法。但是,由于我的页面处于集成模式,我无法从应用程序\启动获取请求。

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

    4 回复  |  直到 15 年前
        1
  •  9
  •   Pure.Krome    15 年前

    我就是这样做的。

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

    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

    (更多psedo代码)

    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。

    希望这有帮助。

        2
  •  7
  •   jdphenix    12 年前

    好啊。。另一个建议。

    为了让我明白,你要……

    • 每一个动作都需要知道语言代码是什么?
    • 如果提供的语言代码无效,则需要将其重置为有效的 违约 一个。

    如果是这样的话…这个答案由三部分组成:

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

    更新(基于评论)

    所以,如果你想知道路线 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框架足够聪明,知道(基于该路由)语言代码参数==SV,控制器==Action,操作(方法)==Index。

    注释 : 秩序 你的路线很重要。非常重要。当您注册您的路线时,此路线需要是第一条路线(在igonoroute之后)中的一条(如果不是)。


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

    注意我是如何装饰的 …不仅仅是每个动作。这意味着类中的所有操作都将受到actionfilter()的影响。

    另外,您可能希望在global.asax中添加一个不处理语言代码的新路由。硬编码默认值…

    像(也未经测试)

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

    这有帮助吗?

        3
  •  4
  •   iCollect.it Ltd    11 年前

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

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

    功能包括:

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

    步骤1:在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将拒绝这些序列作为潜在的安全冲突。

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

    其中language是新的区域性代码,existingurl是当前相对页面地址的urlencoded版本(以便我们返回到相同页面,并选择新语言)。

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

    返回URL所需的编码意味着您需要在 web.config 或现有的url参数将导致错误。

    在web.config中,找到 httpRuntime 标记(或添加) <system.web> 并添加以下内容(基本上删除该属性标准版本中的百分比):

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

    在web.config中,找到 <system.webserver> 并在其中添加以下内容:

    <security>
      <requestFiltering allowDoubleEscaping="true"/>
    </security>
    
        4
  •  -1
  •   Eduardo Molteni    15 年前

    你可以问 global.asax 如果您的站点的URL格式正确,则开始请求。 你也可以尝试使用routes,但是根据我的经验,如果你不确定第一个参数是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