.NET MVC 2 多语言网站的实现
2010-10-24 12:26:10| 分类:.net mvc | 标签: |字号大中小 订阅
要求如下:1,用 Resources2,分离项目(Model , Controller 等分开成子项目)3,简单 因为要用 Resources ,所以很自然的就想到了 App_GlobalResource , 但是分离的项目怎么使用它呢?为了这个,我还对着GOOGLE搜了很多资料,不幸的是,没有结果。我也在论坛里,群里发贴子,但是高手们都无暇以顾,或是觉得这个问题太过于简单,不屑于回答这等初级的问题。 有什么想法,最好去试一下,不成功就当实践了。幸好我试了一把,要不然,现在都还纠结于这个问题。 新建一项目,这个项目里只放资源文件,如 UI.res (Public), UI.zh-CN.res (没有生成代码),就像在 web 项目里建 App_GlobalResource 一样,只不过,这里的 Resource 是放在一个单独的项目里的。 看看要实现的效果如何:第一步:添加 route
{lang}/{controller}/{action}/{id}在 Global.asax ,RegisterRoutes 方法里,你可以自己加些东西,比如: routes.MapRoute("Lang" ,"{lang}/{controller}/{action}/{id}" ,new { lang = "en" , controller = "Home" , action = "Index" , //lang = UrlParameter.Optional, id = UrlParameter.Optional}); 但是这样做,接着你会发现要怎么取这个 lang 啊?用 Global 的 Application_BeginRequest 截取?可以是可以,但是欠妥啊。 看这里,MVC 请求生命周期 当一个asp.net mvc应用程序提出请求,为了响应请求,包含一些请求执行流程步骤! 在asp.net mvc应用程序Http request和Http response 过程中,主要包含8个步骤: 1)RouteTable(路由表)的创建 2)UrlRoutingModule 请求拦截 3)Routing engine 确定route 4)route handler 创建相关的IHttpHandler实例 5)IHttpHandler实例确定Controller(控制器) 6)Controller执行 7)一个视图引擎创建 8) 视图呈现 这里,要用到第 4 步。把上面的代码改成如下,并放 routes.MapRoute 前面: routes.Add(new Route( "{lang}/{controller}/{action}/{id}", new RouteValueDictionary( new { lang = "en-US", controller = "Home", action = "Index", id = UrlParameter.Optional }), new MultiLangRouteHandler())); 注意这个 MultiLangRouteHandler MultiLangRouteHandler 类: public class MultiLangRouteHandler : MvcRouteHandler { protected override IHttpHandler GetHttpHandler(RequestContext requestContext) { string lang = requestContext.RouteData.Values["lang"].ToString(); //Thread.CurrentThread.CurrentCulture = new CultureInfo(lang); Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang); //return new MvcHandler(requestContext); return base.GetHttpHandler(requestContext); }}第二步,给个入口
,在 View 里或 Master 里加上: <div class="lang"> <% string controller = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString(); string action = ViewContext.Controller.ValueProvider.GetValue("action").RawValue.ToString(); %> <%= Html.ActionLink("中文", action , controller , new { lang = "zh-CN" }, null)%> <%= Html.ActionLink("English", action, controller, new {lang="en-US"}, null) %></div> 只需要这一个方这样写,其它的地方都不需要加 {lang="zh-CN"} 这样的东西,MVC 会自动得到当前的 lang ,并写到 Html.ActionLink 里。另外,那个 null 是必须的,要不然,生成的 Url 不正确。第三步,界面上普通文字的多语言
<%= Html.ActionLink(AsNum.MvcWeb.Lang.UI.LABEL_LOGIN , "Login" , "User")%><div class="title bg"><%= AsNum.MvcWeb.Lang.UI.LABEL_GALLERY %></div> 这里的 AsNum.MvcWeb.lang.UI 就是前面所说的那个 Resource 。第四步,DisplayName 的多语言化
在 Model 里,你可能这样写:[Required][DisplayName("User Name")][ScaffoldColumn(false)]public string UserName { get; set;} 然后,你在 View 里这样写:<%= Html.LabelFor(l=>l.User.UserName) %>它就会显示一个 User Name, 但是怎么让随当前的 CurrentUICulture 变化而变化呢?当然,你可以用<%= AsNum.MvcWeb.Lang.UI.LABEL_GALLERY %> 这样的写法来写,但是这样写就显的你太不牛B了,有点业余。你也许会试着用 DisplayName(lang.UI.UserName)这样的写法,但是编译不通过: 属性实参必须是属性形参类型的常量表达式、typeof 表达式或数组创建表达式 因为Attribute 不接受这样的参数. 我们定义个类 LocalizedDisplayName ,继承 DisplayName: public class LocalizedDisplayName : DisplayNameAttribute { private string _defaultName = ""; public Type ResourceType { get; set; } public string ResourceName { get; set; } public LocalizedDisplayName(string defaultName) { _defaultName = defaultName; } public override string DisplayName { get { PropertyInfo p = ResourceType.GetProperty(ResourceName); if( p != null ) return p.GetValue(null , null).ToString(); else return _defaultName; } } } 然后改一下 Model : [Required] //[DisplayName("User Name")] [LocalizedDisplayName("User Name",ResourceName = "USER_USER_NAME" , ResourceType = typeof(Lang.UI))] [StringLengthRange(2 , 20 , true , ErrorMessageResourceName = "MSG_USER_NAME_LEN", ErrorMessageResourceType=typeof(Lang.Validation))] [ScaffoldColumn(false)] public string UserName { get; set; }第五步,验证消息(Validation)的多语言化
如上面的:[StringLengthRange(2 , 20 , true , ErrorMessageResourceName = "MSG_USER_NAME_LEN", ErrorMessageResourceType=typeof(Lang.Validation))] 指定 ErrorMessageResourceName 和 ErrorMessageResourceType 就可以了,不过,好像这两个不能同时和 ErrorMessage 同时存在,我试了,同时存在的话,这个 Validation 不会执行。第六步,客户端的验证消息(Javascript Validation )的多语言化
如果不知道怎么实现 自定义 ValidationAttribute 的客户端验证,可以参考: MVC 添加自定义 Client Validation javascript 支持 就三步:a, 从ValidationAttribute 里派生一个类:public sealed class StringLengthRangeAttribute : ValidationAttribute b,从 DataAnnotationsModelValidator 里派生一个类:public class StringLengthRangeValidator : DataAnnotationsModelValidator<StringLengthRangeAttribute> { c,注册,在 Application_Start 里加:DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(StringLengthRangeAttribute), typeof(StringLengthRangeValidator)); DataAnnotationsModelValidator 派生类: public class StringLengthRangeValidator : DataAnnotationsModelValidator<StringLengthRange> { private int max , min; private bool trim; private string errMsg; public StringLengthRangeValidator(ModelMetadata metaData , ControllerContext ctx , StringLengthRange att) : base(metaData , ctx , att) { max = att.Max; min = att.Min; trim = att.Trim; errMsg = base.Attribute.FormatErrorMessage("") ?? att.ErrorMessage; } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { ModelClientValidationRule rule = new ModelClientValidationRule { ErrorMessage = errMsg , ValidationType = "StringLengthRange" }; rule.ValidationParameters.Add("min" , min); rule.ValidationParameters.Add("max" , max); rule.ValidationParameters.Add("trim" , trim); return new[] { rule }; } } 注意 errMsg = base.Attribute.FormatErrorMessage("") ?? att.ErrorMessage; 这个 base.Attribute.FormatErrorMessage("") 其实就是要取 ErrorMessageString ,但是它又是 protected 的。 public sealed class StringLengthRange : ValidationAttribute {。。。 public override string FormatErrorMessage(string name) { return ErrorMessageString ?? ErrorMessage; //return ErrorMessage; }。。。} 好了,至此,基本上所有的元素都支持多语言化了。