ASP.NET MVC的本地化支持(三)
选择语言的链接
让用户在浏览器的URL里更改语言不是个好的方法 ,我们需要在页面顶部给些链接方便用户随时改变语言。在MVC里,简单的方法就是创建一个HtmlHelper类的生成各种语言的链接的扩展方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.Routing;
6 using System.Web.Mvc;
7 using System.Web.Mvc.Html;
8 using System.Threading;
9
10 namespace ShowLocal.Models
11 {
12 public static class SwitchLanguageHelper
13 {
14 public class Language
15 {
16 public string Url { get; set; }
17 public string ActionName { get; set; }
18 public string ControllerName { get; set; }
19 public RouteValueDictionary RouteValues { get; set; }
20 public bool IsSelected { get; set; }
21
22 public MvcHtmlString HtmlSafeUrl
23 {
24 get
25 {
26 return MvcHtmlString.Create(Url);
27 }
28 }
29 }
30
31 public static Language LanguageUrl(this HtmlHelper helper, string cultureName,
32 string languageRouteName = "lang", bool strictSelected = false)
33 {
34 // 设置输入的语言为小写
35 cultureName = cultureName.ToLower();
36 // 从view context中获取retrieve the route values from the view context
37 var routeValues = new RouteValueDictionary(helper.ViewContext.RouteData.Values);
38 // copy the query strings into the route values to generate the link
39 var queryString = helper.ViewContext.HttpContext.Request.QueryString;
40 foreach (string key in queryString)
41 {
42 if (queryString[key] != null && !string.IsNullOrWhiteSpace(key))
43 {
44 if (routeValues.ContainsKey(key))
45 {
46 routeValues[key] = queryString[key];
47 }
48 else
49 {
50 routeValues.Add(key, queryString[key]);
51 }
52 }
53 }
54 var actionName = routeValues["action"].ToString();
55 var controllerName = routeValues["controller"].ToString();
56 // set the language into route values
57 routeValues[languageRouteName] = cultureName;
58 // generate the language specify url
59 var urlHelper = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);
60 var url = urlHelper.RouteUrl("Localization", routeValues);
61 // check whether the current thread ui culture is this language
62 var current_lang_name = Thread.CurrentThread.CurrentUICulture.Name.ToLower();
63 var isSelected = strictSelected ?
64 current_lang_name == cultureName :
65 current_lang_name.StartsWith(cultureName);
66 return new Language()
67 {
68 Url = url,
69 ActionName = actionName,
70 ControllerName = controllerName,
71 RouteValues = routeValues,
72 IsSelected = isSelected
73 };
74 }
75
76 public static MvcHtmlString LanguageSelectorLink(this HtmlHelper helper,
77 string cultureName, string selectedText, string unselectedText,
78 IDictionary<string, object> htmlAttributes, string languageRouteName = "lang", bool strictSelected = false)
79 {
80 var language = helper.LanguageUrl(cultureName, languageRouteName, strictSelected);
81 var link = helper.RouteLink(language.IsSelected ? selectedText : unselectedText,
82 "Localization", language.RouteValues, htmlAttributes);
83 return link;
84 }
85
86 }
87 }
我创建了一个存储语言信息的链接的类,它可以用于呈现语言关联,如果我们需要它可以是下拉框,图片链接,或者其他的任何我们想要的。
这个LanguageUrl方法从当前进入的请求获取路由值集合和查询字符串和切换语言部分。然后生成当前网址页面上的语言,所以当我们点击当前页的链接的时候他会提供一个我们点击的页面的指定语言的版本。
@*此处需要在页面上引入命名空间 @using ShowLocal.Models; *@ @Html.Partial("_LogOnPartial") @Html.LanguageSelectorLink("en-US", "[English]", "English", null) @Html.LanguageSelectorLink("zh-CN", "[中文]", "中文", null)
登陆页报错了
一些Web应用程序只能登录后查看,如一些公司内部的客户关系管理页面.这意味着在任何应用程序的任何action之前如果用户没有登录则需要被重定向登录页面,现在我们在我们的示例程序里看看,在HomeController类前加个Authorize属性后浏览页面。
糟糕了!系统给了我们个404错误,说找不到/Account/LogOn了。这个是因为我们的路由。我们注册了两个路由规则,路由Localization有4个模式,而默认的路由则有3个模式。传入的请求会检查所有基于它们的规则的路由规则,一旦匹配则会被路由标识。例如如果URL是http://localhost/en-us/Home/Index则他匹配Localization路由(lang = en-US, controller = Home, action = Index, id 是可空的)。如果URL是http://localhost/则不匹配Localization路由,(lang不能为空或者null)他匹配了默认的路由(它允许所有的模式为空)。
我们看看URL http://localhost/Account/LogOn ,Account会被匹配lang而logon会被匹配Controller,因为action和id模式都是可选的,所以这个URL就与Localization匹配了,意味着Language=account,Controller=logon,action=index(默认)。而系统了没有任何名为Logon的Controller而且action为Index的,所以系统返回了个404错误。
因为登录URL被Asp.Net授权模块重定向,这意味着我们没有办法给URL加上语言,我们需要为它单独添加一个路由规则。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
2 routes.MapRoute(
3 "LogOn", // Route name
4 "Account/{action}", // URL with parameters
5 new { controller = "Account", action = "LogOn" } // Parameter defaults
6 );
7 routes.MapRoute(
8 "Localization", // Route name
9 "{lang}/{controller}/{action}/{id}", // URL with parameters
10 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
11 );
12 routes.MapRoute(
13 "Default", // Route name
14 "{controller}/{action}/{id}", // URL with parameters
15 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
16 );
在原有的两个规则前面新增了一个LogOn规则,它只接受Account控制器,所以http://localhost/Account/LogOn 可以被正确解析了。
上面是原文的代码,我在网上逛的时候发现了如下的路由规则:这个也可以达到这个目的,就是修改了Localization规则。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 routes.MapRoute(
2 "Localization",
3 "{lang}/{controller}/{action}/{id}",
4 new { controller = @"Home", action = @"Index", id = UrlParameter.Optional },
5 new { lang = @"\w{2,3}(-\w{4})?(-\w{2,3})?$" }
6 );
代码重构
这是个非常简单的程序,就像微软提供的所有例子,网站仅仅包含一个项目。在实际的工程中,我们也许不会把我们的程序放到一个项目里。我们需要把控制器,模型,数据访问,业务逻辑分别放到不同的项目里。所以让我们重构我们的例子,然后看看怎么本地化。
这里我只分离了控制器和模型还有资源文件(因为资源文件在App_GlobalResources里自动生成的类不是公开的,Controller里无法调用,所以需要把资源文件也分离出去),没有创建数据访问和业务逻辑层,因为它们与本地化无关。