首页 > 代码库 > [自制小工具分享] 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 }
如上代码所示, 要求资源文件中的 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 多语言示例