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

时间:2010-09-10 09:08:27

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

我正在使用ASP.NET MVC本地化路由。因此,当用户访问英语网站时,example.com/en/Controller/Action和瑞典网站为example.com/sv/Controller/Action

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

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

4 个答案:

答案 0 :(得分:9)

我就是这样做的。

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

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

(更多pseduo代码)......

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 :)希望这会有所帮助。

答案 1 :(得分:7)

好的..另一个建议。

为了确保我明白,你想要......

  • 每个动作都需要能够弄清楚LanguageCode是什么?
  • 如果提供了无效的languageCode,则需要将其重置为有效的默认

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

  1. 添加路线。 (这是我之前的答案中的剪贴)。
  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
            );
    
    }
    

    更新(基于评论)

    因此,如果您想拥有路线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 paramters == sv,controller == action and action(method)== index。

    注意:您的路线的订单非常重要。非常重要。当您注册路线时,此路线必须是第一条路线(如果不是)的一条路线(在IgonoreRoute之后)。


    1. 您需要创建a custom ActionFilter,在执行操作之前将调用该{{3}}。这是我的快速尝试......
    2. 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。然后,这将创建一个易于访问的属性,您的所有操作都可以访问它...然后根据该值显示他们想要的任何内容。
      2. 这里我们去...(再次伪代码....)

        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中添加一个处理NO languageCode的新路由..而硬编码默认该值......

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

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

        这有帮助吗?

答案 2 :(得分:4)

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

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

功能包括:

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

步骤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:将本地化应用于所有控制器

e.g。

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

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

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

将ChangeLanguage方法添加到您的帐户控制器。这将从&#34;之前的路径&#34;中删除任何现有的语言代码。允许新语言生效。

    // 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步:添加语言菜单链接

菜单选项包含指定为路径参数的新语言的链接。

e.g。 (Razor例子)

<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步:启用某些&#34;不安全&#34;网址

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

在您的web.config中,在httpRuntime中找到<system.web>标记(或添加它)并向其中添加以下内容(基本上删除此属性标准版本中的%):

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

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

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

答案 3 :(得分:-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