分享

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

 贾朋亮博客 2015-02-26

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             //动态类型/代理类 EF
33             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.GetModelValidator
38                 var dsp = new DisplayAttribute() {
39                     Name = v
40                 };
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./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 property
27         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.0
42         // ldfld,_field
43         // ret
44         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.0
56         // ldarg.1
57         // stfld,_field
58         // ret
59         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 == 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.Optional
17                         }), 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 = 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 = 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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多