首页 > 代码库 > [自制小工具分享] ResEditor 及 简单的 MVC 多语言示例

[自制小工具分享] ResEditor 及 简单的 MVC 多语言示例

ResEditor 的用处前提

1, MVC
2, 需要设置字段的显示名称 Display
3, 用资源文件

背景:
我们的项目是 MVC5 + ORACLE + EF Db First
需求分析师兼任数据库设计, 目前有141张表, 70% 的表,字段数在100个以上.

加 Display 特性一般由两个途径:
1,直接在实体类上添加
2,用伴随类.
但是实体类是由 EF 的TT模板自动生成的,虽然可以修改 TT 文件加上 Display 特性到属性上,但是字段的描述不适合直接拿来当Display
如果用伴随类, 需要新增 100 多个伴随类, 几千个属性, 完全手输,不实现.

下面这部份是炒旧菜, 早在2年前博文: MVC3 项目总结 中已经介绍过.
这里只是把它做了一点修改, 便于机械式的批量操作.

 1 public class ResDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider { 2  3     private ResourceManager ResMgr; 4  5     public ResDataAnnotationsModelMetadataProvider(ResourceManager resMgr) { 6  7         if (resMgr == null) 8             throw new ArgumentNullException("resMgr"); 9 10         this.ResMgr = resMgr;11     }12 13     private string GetString(Type containerType, string propertyName) {14         var key = string.Format("{0}{1}_{2}_DisplayName", containerType.Namespace.Replace(".", ""), containerType.Name, propertyName);15         return this.ResMgr.GetString(key);16     }17 18     /// <summary>19     /// 重写生成元数据的方法20     /// 只有页面上来通过Lambda表达式过来的元数据,才进行中英文转换21     /// (该过滤为了减少元数据的中英文转换次数)22     /// </summary>23     /// <param name="attributes"></param>24     /// <param name="containerType"></param>25     /// <param name="modelAccessor"></param>26     /// <param name="modelType"></param>27     /// <param name="propertyName"></param>28     /// <returns></returns>29     protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {30         if (containerType != null) {//请改之前先做好测试!这是最终的条件.31 32             //动态类型/代理类 EF33             var t = containerType.Assembly.IsDynamic ? containerType.BaseType : containerType;34 35             var v = this.GetString(t, propertyName);36             if (!string.IsNullOrWhiteSpace(v)) {37                 //这里,如果 v 是空字符串的话,居然会影响到 SmartModelBinder 的 ModelValidator.GetModelValidator38                 var dsp = new DisplayAttribute() {39                     Name = v40                 };41                 var attrs = attributes.ToList();42                 attrs.RemoveAll(a => a is DisplayAttribute);43                 attrs.Add(dsp);44                 return base.CreateMetadata(attrs, containerType, modelAccessor, modelType, propertyName);45             }46         }47 48         return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);49     }50 }
View Code

如上代码所示, 要求资源文件中的 Key 按一下格式设置:

{0}{1}_{2}_DisplayName
var key = string.Format("{0}{1}_{2}_DisplayName", containerType.Namespace.Replace(".", ""), containerType.Name, propertyName);

 

这个要求很高, 不适合手工直接在VS中编辑.
怎么办呢? 于是我就写了这个 ResEditor

当选定一个 ResX 文件后, 程序会自动尝试加载资源文件的多语言文件.
比如选择了 EntityRes.Resx , 会把同目录下的 EntityRes.en-US.resx / EntityRes.zh-TW.resx 等文件给找出来:

 1 private Dictionary<string, string> GetLangFiles(string path) { 2     var name = this.GetResFileName(path); 3     var dir = Path.GetDirectoryName(path); 4     var files = Directory.GetFiles(dir, string.Format("{0}.*.resx", name)); 5  6     var dic = new Dictionary<string, string>() {  7         {"Default", Path.Combine(dir, string.Format("{0}.resx", name)) } 8     }; 9 10     foreach (var f in files) {11         var lang = Regex.Match(f, @".(?<lang>[^. ]*?).resx").Groups["lang"].Value;12         dic.Add(lang, f);13     }14 15     return dic;16 }

 

然后跟据找到的语言, 动态生成一个实体类:

1 private void DefineType(IEnumerable<string> langs) {2     var tb = TypeBuilderHelper.Define("TTT", typeof(Record));3     langs.ToList().ForEach(l => {4         tb.DefineProperty(l.Replace("-", "_"), typeof(string));5     });6     this.TmpType = tb.CreateType();7 }

 

TypeBuilderHelper 是直接拿
http://www.codeproject.com/Articles/206416/Use-dynamic-type-in-Entity-Framework-SqlQuery
中的代码,做了少许修改:

 

 1 public static class TypeBuilderHelper { 2  3     public static TypeBuilder Define(string typeName, Type parentType = null) { 4         var typeBuilder = AppDomain.CurrentDomain 5             .DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run) 6             .DefineDynamicModule("Test") 7             .DefineType("DT", TypeAttributes.Public); 8         typeBuilder.DefineDefaultConstructor(MethodAttributes.Public); 9 10         if (parentType != null)11             typeBuilder.SetParent(parentType);12 13         return typeBuilder;14     }15 16     public static void DefineProperty(this TypeBuilder builder, string propertyName, Type propertyType) {17         const string PrivateFieldPrefix = "m_";18         const string GetterPrefix = "get_";19         const string SetterPrefix = "set_";20 21         // Generate the field.22         FieldBuilder fieldBuilder = builder.DefineField(23             string.Concat(PrivateFieldPrefix, propertyName),24                           propertyType, FieldAttributes.Private);25 26         // Generate the property27         PropertyBuilder propertyBuilder = builder.DefineProperty(28             propertyName, PropertyAttributes.HasDefault, propertyType, null);29 30         // Property getter and setter attributes.31         MethodAttributes propertyMethodAttributes =32             MethodAttributes.Public | MethodAttributes.SpecialName |33             MethodAttributes.HideBySig;34 35         // Define the getter method.36         MethodBuilder getterMethod = builder.DefineMethod(37             string.Concat(GetterPrefix, propertyName),38             propertyMethodAttributes, propertyType, Type.EmptyTypes);39 40         // Emit the IL code.41         // ldarg.042         // ldfld,_field43         // ret44         ILGenerator getterILCode = getterMethod.GetILGenerator();45         getterILCode.Emit(OpCodes.Ldarg_0);46         getterILCode.Emit(OpCodes.Ldfld, fieldBuilder);47         getterILCode.Emit(OpCodes.Ret);48 49         // Define the setter method.50         MethodBuilder setterMethod = builder.DefineMethod(51             string.Concat(SetterPrefix, propertyName),52             propertyMethodAttributes, null, new Type[] { propertyType });53 54         // Emit the IL code.55         // ldarg.056         // ldarg.157         // stfld,_field58         // ret59         ILGenerator setterILCode = setterMethod.GetILGenerator();60         setterILCode.Emit(OpCodes.Ldarg_0);61         setterILCode.Emit(OpCodes.Ldarg_1);62         setterILCode.Emit(OpCodes.Stfld, fieldBuilder);63         setterILCode.Emit(OpCodes.Ret);64 65         propertyBuilder.SetGetMethod(getterMethod);66         propertyBuilder.SetSetMethod(setterMethod);67     }68 69 }   

 


生成动态类型后, 在把资源文件通过 ResXResourceReader 读出来, 生成一个动态类型的数据集合, 作为数据源, 展示到界面上.

 1 private void ReadResx(Dictionary<string, string> dic) { 2  3     List<dynamic> results = new List<dynamic>(); 4  5     foreach (var d in dic) { 6         try { 7  8             using (var reader = new ResXResourceReader(d.Value)) { 9                 foreach (DictionaryEntry entry in reader) {10                     try {11 12                         if (entry.Key == null || entry.Value =http://www.mamicode.com/= null || entry.Key.GetType() != typeof(string) || entry.Value.GetType() != typeof(string))13                             continue;14 15                         var key = (string)entry.Key;16 17                         var o = results.FirstOrDefault(r => r.Key.Equals(key));18                         if (o == null) {19                             o = Activator.CreateInstance(this.TmpType);20                             o.Key = (string)entry.Key;21                             o.IsExists = true;22                             results.Add(o);23                         }24 25                         this.TmpType.GetProperty(d.Key.Replace("-", "_"))26                             .SetValue(o, (string)entry.Value);27                     } catch {28                     }29                 }30             }31         } catch (Exception ex) {32             MessageBox.Show(ex.Message);33         }34     }35 36     if (results.Count == 0)37         results.Add(Activator.CreateInstance(this.TmpType));38 39     App.Current.Dispatcher.Invoke(() => {40         this.Datas = new BindableCollection<dynamic>(results);41         this.NotifyOfPropertyChange(() => this.Datas);42 43         this.CV = CollectionViewSource.GetDefaultView(this.Datas);44         this.CV.Filter = new Predicate<object>(this.Filter);45         this.CV.GroupDescriptions.Add(new PropertyGroupDescription("Key") {46             Converter = new Converter1()47         });48         this.CV.SortDescriptions.Add(new SortDescription("Key", ListSortDirection.Ascending));49         this.NotifyOfPropertyChange(() => this.CV);50     }, DispatcherPriority.Send);51 }

 

保存的时候, 通过 ResXResourceWriter 将数据集合中的内容写回 Resx 文件.

 

上面这些全都是为了多语言做准备. 下面说说我的多语言处理方式

A, 自定义一个 MvcRouteHandler , 用来处理多语言:

public class MultiLangRouteHandler : MvcRouteHandler {    protected override System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext) {        string lang = requestContext.RouteData.Values["lang"].ToString();        try {            var culture = CultureInfo.GetCultureInfo(lang);            Thread.CurrentThread.CurrentUICulture = culture;        } catch {                    }        return base.GetHttpHandler(requestContext);    }}

 

它的作用就是拿来获取路由的 lang 值, 然后设置 CurrentUICulture.

B, 在路由配置中, 把默认的路由配置修改成:

 1 public class RouteConfig { 2     public static void RegisterRoutes(RouteCollection routes) { 3         routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 4  5         //routes.MapRoute( 6         //    name: "Default", 7         //    url: "{controller}/{action}/{id}", 8         //    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 9         //);10 11         routes.Add(new Route("{lang}/{controller}/{action}/{id}",12                         new RouteValueDictionary(new {13                             lang = "zh-CN",14                             controller = "Home",15                             action = "Index",16                             id = UrlParameter.Optional17                         }), new MultiLangRouteHandler()));18     }19 }

 

这样做之后, 一个简单的语言切换就完成了, 但是...
假如有如下实体:

public class Person {    public int ID { get; set; }    [Display(Name = "姓名1")]    [DisplayName("姓名2")]    public string Name { get; set; }    public string EnName { get; set; }    public DateTime? Birthday { get; set; }    public string Address { get; set; }    public string Email { get; set; }}

 

当语言为 英语的时候, 显示 EnName 的值, 为简体中文/繁体中文的时候, 显示 Name 的值, 怎么办呢? if..else.. ? 我猜你也会这样想.
现在不用了, 从MVC 4 开始, 加入了 DisplayModel, 这里我用它来变个花样, 在 Global 中添加:

 

 1 DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("en") { 2     ContextCondition = ctx => { 3         var data =http://www.mamicode.com/ RouteTable.Routes.GetRouteData(ctx); 4         var lang = (string)data.Values["lang"]; 5         return string.Equals(lang, "en-US", StringComparison.OrdinalIgnoreCase); 6     } 7 }); 8  9 DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("tw") {10     ContextCondition = ctx => {11         var data =http://www.mamicode.com/ RouteTable.Routes.GetRouteData(ctx);12         var lang = (string)data.Values["lang"];13         return string.Equals(lang, "zh-tw", StringComparison.OrdinalIgnoreCase);14     }15 });

 

即当 lang 为 en-US 的时候, 使用 DisplayModel en, tw 同理.
接着把 cshtml 文件复制一份出来, 我这里以 index.cshtml 为例, 复制的文件改名为 index.en.cshtml.

 1 <table border="1" cellpadding="5"> 2     @foreach (var p in Model) { 3         <tr> 4             <td>@p.Name</td> 5             <td>@p.Address </td> 6             <td>@p.Email</td> 7             <td>@Html.ActionLink( UIRes.Edit , "Edit", new { id = p.ID })</td> 8         </tr> 9     }10 </table>

复制出来的文件改成:

 1 <table border="1" cellpadding="5"> 2     @foreach (var p in Model) { 3         <tr> 4             <td>@p.EnName</td> 5             <td>@p.Address </td> 6             <td>@p.Email</td> 7             <td>@Html.ActionLink("Edit", "Edit", new { id = p.ID })</td> 8         </tr> 9     }10 </table>

 

除了把 Name 列换成 EnName 之外,两个文件的内容基本一样.

这样, 当匹配到 lang 为 en-US 的时候,就会用 index.en.cshtml , 而不是 index.cshtml
如果不存在 index.en.cshtml 也不要紧, 会取 index.cshtml 的.

 

ResEditor 源码:

http://files.cnblogs.com/xling/ResEditor.7z

 

多语言示例源码:

http://files.cnblogs.com/xling/MutLangTest.7z 

 

下一篇:

EF5 Db First + ORACLE  疑难杂症

[自制小工具分享] ResEditor 及 简单的 MVC 多语言示例