[自制小工具分享] 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             //动态类型/代理类 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.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 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

下一篇:

EF5 Db First + ORACLE  疑难杂症

时间: 2024-08-29 04:53:29

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

[自制小工具分享] 快速打开 (WIN + R 增强)

我的桌面永远都是乱糟糟的, 想找个找个程序很不容易, 逗鸡眼一样的逗半天,才能找得到. 特别是工作的时候, 想打开个软件, 翻来翻去, 耽误不少时间. 我相信8成长以上的猿类都和我一样, 很多常用的工具,MB急着用的时候,就是不知道在哪个位置. 好几个同学都看我 WIN + R , 然后乱输入一气, 居然把想要的东西给打开了, 很是惊诧. 其实一点都不难, 只是修改了注册表而已. 先看今天我要显摆的东西(PS : 第一版是两年前用WINFORM 写的, 硬盘坏掉后,源码丢失了, 只留了一个EXE

自制小工具监控wcf服务是否正常

由于项目中有2个使用netTcpBinding的wcf服务经常出现无法提供服务的问题,一直找原因也找不到导致影响严重,更换InstanceContextMode和ConcurrencyMode配置也不好使,出现问题后用户反馈,然后我这边在赶紧重启服务就好了,为此先写个小工具实现自动监控服务端程序是否正常如果不正常则实现自动重新启动服务程序功能,这方法只能治标不治本,对wcf了解的朋友可以给出些调试意见. 程序使用devexpress的chartcontrol控件事实滚动界面,看图: 折线图每次显

【自制小工具】代码生成器

陆陆续续接触过好几款代码生成工具,发现确实好用,但都会有那么点不完善的地方,所以索性就自己做一个吧. 界面非常简单,反正是自己用的,简单点用起来也方便 上图:左侧是服务器列表(该列表是通过语句获取的,不受 Sql Server 版本影响,除非语法变了     右侧代码生成区 : 功能上内置了 1. 导出实体类(包含属性,方法有 List 转 DataTable, DataTable 转 List,生成实体表) (支持列的筛选) 2. SELECT ,UPDATE ,INSERT,DELETE ,

C语言结合VBS脚本编写朗读小工具,做一个能够发音的C语言程序

大家好!我就是小编,又见面了啊,有句话说"相聚是缘".我们既然有缘相聚在此,希望你对我有了一定的认识和了解,也能够关注下小编,这样就不会迷路哦,希望在往后的日子我们大家能继续相互鼓励,共同成长.在花季和雨季有我们最深的情谊,也很高兴你能点开本文,希望大家以后在学习上相互帮助.好了,今天的主题是:C语言结合VB实现朗读功能. 具备的知识体系 基本上是C语言的九牛一毛啊 就这些: C语言基本框架 C语言输入输出 C语言文件操作 C语言system函数 VBS指令:CreateObject(

用C#Winform写个简单的批量清空文件内容和删除文件的小工具

用C#Winform写个简单的批量清空文件内容和删除文件的小工具 本文介绍这个简单得不能再简单的小项目.做这个项目,有以下目的. 1 当然是做个能用的工具 2 学习使用Github 关于用VS2013创建一个项目并添加到Github的教程,请参考(http://www.admin10000.com/document/4004.html).简单来说,就是先用VS创建项目:然后在Github网站上创建一个Respo(本项目的代码托管项目),记下(https://*.git)那个地址:最后用"提交&q

WPF做的迁移文件小工具

客户这边需要往服务器上传PDF文件.然后PDF文件很多,需要挑出来的PDF文件也不少.因此做了个小工具. 功能很简单,选定源文件夹,选定记录着要提取的文件的excel 文件.OK ,界面如下. XAML代码如下 <Window x:Class="文件迁移工具.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://sch

博客转发小工具1

[转]博客转发小工具1 有些朋友在转发别人博客的时候会问,博客怎么转发的啊?让我一段一段的复制吗?那图片怎么办?隐藏代码要一个一个的打开了复制? 对,很麻烦.费时费力.有的同学会说收藏不就可以了吗?收藏只是收藏了别人的地址,并没有收藏人家的全部内容.如果人家删除原文章,那就等于白收藏了. 我不知道 博客园有没有一键转发的功能,反正我是没找到的.于是,闲来无事,做了个博客转发小工具. 其实很简单,分三步. 一:取得页面内容 取页面内容需要用到HtmlAgilityPack.dll 详细用法可以百度

【转】博客转发小工具1

[转]博客转发小工具1 有些朋友在转发别人博客的时候会问,博客怎么转发的啊?让我一段一段的复制吗?那图片怎么办?隐藏代码要一个一个的打开了复制? 对,很麻烦.费时费力.有的同学会说收藏不就可以了吗?收藏只是收藏了别人的地址,并没有收藏人家的全部内容.如果人家删除原文章,那就等于白收藏了. 我不知道 博客园有没有一键转发的功能,反正我是没找到的.于是,闲来无事,做了个博客转发小工具. 其实很简单,分三步. 一:取得页面内容 取页面内容需要用到HtmlAgilityPack.dll 详细用法可以百度

【分享】小工具大智慧之Sql执行工具

工具概况 情况是这样的,以前我们公司有很多Sql用于完成一些很不起眼但又不得不完成的业务,出于方便就直接在Sql查询分析器里执行,按理说应该写一些专门的工具的,但是这些脚本很多,于是我就写了这样一个小工具,只要Sql可以解决的问题就能用到它了,那么它有什么优点呢 一.以前写好的Sql可以不用怎么更改,只需要将参数部分提取出来按照定好的规范改成一个模版即可,使用工具加载模版时,工具会自动将模块要求的参数提供给用户填写. 二.模版文件里可以配置好连接字符串,这样不用每次都连接数据库,也避免连接错误的