之前写了一篇asp.net MVC多语言方案,那次其实是为American Express银行开发的。有许多都是刚开始接触,对其也不太熟悉。现在再回过头去看,自己做一个小网站,完全用asp.net mvc 3的技术。要实现多语言,并且要求可以动态换语言。在有数据输入的地方,其数据输入校验的界面也是不一样的,比如必须输入的字段,英文显示:required, 中文就显示:请输入,等等。这里的方法和之前的文章的方法略有不同。
1. 资源文件
多语言的资源文件还是一个单独的.net 工程,里面只放资源文件。可以建一个class library的工程。工程名字叫Resource。里面只加入资源文件.resx。资源文件加入时,一种语言一个文件,这个有基础的人都知道,不多说。
唯一要注意的是:要将Access Modifier置成Public。这样IDE会为其产生c#代码。其类是包以外的类也能访问的。
2. 在View中使用资源文件
在_ViewStart.cshtml中最前面加上这么一行。
@{CommonUtil.ResourceLoader.SetCurrentThreadCulture(Session);}
后面会介绍 ResourceLoader.SetCurrentThreadCulture的代码。此方法是根据Session["Culture"]来设置当前线程的Culture和UI Culture。在此处加上这么一行,将会对所有的本应用程序的view有影响。
见这个代码,这是本人这个小网站的layout,即相当于asp.net 2.0时代的master page。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>@ViewBag.Title</title> 5 <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> 6 <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> 7 </head> 8 <body> 9 <div class="wrap">10 <DIV class="header">11 <DIV id="logo" class="logo">12 <a href="@Url.Content("~/")"><img border="0" src="@Url.Content(Resource.Views.Shared.Layout.Logo)" /></a>13 </DIV>14 <DIV id="logo" class="logo">15 <img border="0" src="@Url.Content(Resource.Views.Shared.Layout.AnimAd)" />16 </DIV>17 <DIV id="Links" class="top_nav">18 <UL>19 <LI><A href="@Url.Content("~/English/")">English</A></LI>20 <LI><A href="@Url.Content("~/Chinese/")">中文</A></LI>21 <LI><A href="@Url.Content("~/ContactUs/")">@Resource.Views.Shared.Layout.ContactUS</A></LI>22 </UL>23 </DIV>24 </div>25 <div class="content">26 @RenderBody()27 </div>28 <div id ="FooterSection" class="footer">29 <p>© @Resource.Views.Shared.Layout.CopyRight </p>30 </div>31 </div>32 </body>33 </html>
见到使用资源文件的地方了吗?如:@Resource.Views.Shared.Layout.ContactUS,还有@Resource.Views.Shared.Layout.CopyRight,这两个是文本的多语言。还有图片的多语言,即在<img中的Resource.Views.Shared.Layout.Logo。即实现了两个语言版本的图片,用这个资源存储多个语言版本的图片路径。页面中显示哪一个,就根据当前用户的语言偏好。
3. 动态切换语言
注意到上面的代码里有一个English和中文的链接了吗。点这两个链接都会有相应的controller处理。这里的controller是这样的代码:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Threading;
using CommonUtil;
namespace ApplicationExpertShare.Controllers
{
public class EnglishController : Controller
{
//
// GET: /English/
public ActionResult Index()
{
System.Globalization.CultureInfo englishCulture = new System.Globalization.CultureInfo("en-US");
Session["Culture"] = englishCulture;
return this.Redirect(this.Request.UrlReferrer.ToString());
}
}
}
这里调用了一个ResourceLoader类来切换语言。中文的controller也一样:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Reflection;
using System.Threading;
using CommonUtil;
namespace ApplicationExpertShare.Controllers
{
public class ChineseController : Controller
{
//
// GET: /Chinese/
public ActionResult Index()
{
System.Globalization.CultureInfo chineseCulture = new System.Globalization.CultureInfo("zh-CN");
Session["Culture"] = chineseCulture;
return this.Redirect(this.Request.UrlReferrer.ToString());
}
}
}
那么究竟ResourceLoader类做了什么呢,竟然能动态切换语言?看代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Web;
using System.Web.Mvc;
namespace CommonUtil
{
public static class ResourceLoader
{
public static void SetCurrentThreadCulture(HttpSessionStateBase session)
{
if (session != null && session["Culture"] != null)
{
System.Threading.Thread.CurrentThread.CurrentCulture = (System.Globalization.CultureInfo)session["Culture"];
System.Threading.Thread.CurrentThread.CurrentUICulture = (System.Globalization.CultureInfo)session["Culture"];
}
}
}
}
它究竟干了什么呢?
第一步:判断Session是否空,还有Session["Culture"]是否空。因为这两个都有可能为空。
第二步:根据Session["Culture"]设置当前线程的Culture和UI Culture。
4. Model类的多语言
asp.net MVC少不了Model类,Model类在输入界面里还有input validation信息,这些input validation的信息也应该是能多语言的。比如最常见的必须输入字段,我们用英文就显示"Required",用中文就显示:“请输入”。如何做呢?见代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.DataAnnotations; 4 using System.Web.Mvc; 5 using Resource.Entity; 6 7 namespace DataEntity 8 { 9 public class UserAccount 10 { 11 #region private members 12 private int _iID; 13 private string _strName; 14 private string _strEmail; 15 private string _strPassword; 16 private string _strConfirmPassword; 17 private System.Decimal _Balance; 18 #endregion 19 20 #region Properties 21 public int ID 22 { 23 get 24 { 25 return _iID; 26 } 27 set 28 { 29 _iID = value; 30 } 31 } 32 33 [Required(ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName="Common_Required_ErrorMessage")] 34 [StringLength(30, ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "NAME_StringLength_ErrorMessage")] 35 [RegularExpression(@"[a-zA-Z].*", ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "NAME_RegularExpression_ErrorMessage")] 36 [Display(ResourceType=typeof(Resource.Entity.UserAccount), Name="NAME_DisplayName")] 37 public string NAME 38 { 39 get 40 { 41 return _strName; 42 } 43 set 44 { 45 _strName = value; 46 } 47 } 48 49 [Required(ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")] 50 [RegularExpression(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" 51 , ErrorMessageResourceType=typeof(Resource.Entity.UserAccount), ErrorMessageResourceName="EMAIL_RegularExpression_ErrorMessage")] 52 [Display(ResourceType=typeof(Resource.Entity.UserAccount), Name="EMAIL_DisplayName")] 53 public string EMAIL 54 { 55 get 56 { 57 return _strEmail; 58 } 59 set 60 { 61 _strEmail = value; 62 } 63 } 64 65 [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "PASSWORD_DisplayName")] 66 [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")] 67 [StringLength(32, ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "PASSWORD_StringLength", MinimumLength = 8)] 68 public string PASSWORD 69 { 70 get 71 { 72 return _strPassword; 73 } 74 set 75 { 76 _strPassword = value; 77 } 78 } 79 80 [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")] 81 [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "CONFIRMPASSWORD_DisplayName")] 82 [Compare("PASSWORD", ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "CONFIRMPASSWORD_CompareErrorMessage")] 83 public string CONFIRMPASSWORD 84 { 85 get 86 { 87 return _strConfirmPassword; 88 } 89 set 90 { 91 _strConfirmPassword = value; 92 } 93 } 94 95 [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")] 96 [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "OLDNAME_DisplayName")] 97 public string OldName { get; set; } 98 99 [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]100 [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "OLDEMAIL_DisplayName")]101 public string OldEmail { get; set; }102 103 [Required(ErrorMessageResourceType = typeof(Resource.Entity.UserAccount), ErrorMessageResourceName = "Common_Required_ErrorMessage")]104 [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "OLDPassword_DisplayName")]105 public string OldPassword { get; set; }106 107 [Display(ResourceType = typeof(Resource.Entity.UserAccount), Name = "Balance")]108 public decimal Balance { get; set; }109 #endregion110 }111 }
这个类显示了如何在DataAnnotations中使用资源文件的多语言信息。这些Attribute都接受一个ResourceType, 和一个Resource String的名字。这里可以再说一点,就是RegularExpressionAttribute, 可以用资源文件存储正则表达式,这样还可以实现不同的语言采用不同的数据验证规则。剩下的就是如何在Resource工程里规划好资源文件了。稍有基础的人都知道,就不多说了。
5. 结束语
一个全局的资源文件可以带来许多好处。比如集中管理资源。所有资源都在这个特定的工程里面。当然也有坏处,就是当资源越来越多时,就会难于管理。到时可以想一些折衷的办法。要么将资源文件放到两个资源工程里面;要么也可以允许一些local的资源文件。这些的问题只有遇到的时候才会找到合适的办法。需要根据实际情况来采取一些办法。
2012.5.13. 注:已经针对评论做了修改,现在已经可以支持多用户,而且多用户互相不影响。