.NET MVC4 实训记录之六(利用ModelMetadata实现资源的自主访问)

  上一篇我们已经实现自定义资源文件的访问,该篇我们使用它配合ModelMetadata实现资源文件的自主访问。这样做是为了我们能更简单的用MVC原生的方式使用资源文件。由于我的文章旨在记录MVC项目的实现,因此不做框架底层实现方面的讲解(其实考虑到自己的能力,也不能为大家讲解的多么深入。如需要更深入的了解MVC底层实现,请自行搜索。在这里我推荐蒋金楠(Artech)老师的相关博文)。

  对于使用EF,我们不得不知道System.ComponentModel.DataAnnotations。DataAnnotations下定义了一系列的Attribute,用于我们的属性字段注解方案。例如DisplayAttribue,用于定义属性所显示的名称的文本信息。RequiredAttribute用于定义属性是否必填,以及必填校验失败后的提示信息。它们是我们最常用的注解属性中的两个,我们一般都使用它们来描述我们的字段在用户界面的显示效果。例如我们在UserProfile定义的UserName属性上引入如下Attribute:

1         [Column(Order = 1)]
2         [Required(ErrorMessage = "The User Name is required!")]
3         [Display(Name = "Filed: User Name")]
4         public string UserName { get; set; }
View Code

  在EditUser.cshtml视图中已如下方式显示该字段:

1 @using (Html.BeginForm("EditUser", "Account", FormMethod.Post))
2 {
3     <p>@Html.LabelFor(p=>p.UserName)</p>  
4     <p>@Html.TextBoxFor(p=>p.UserName)</p>
5     
6     <input type="submit" value="提交" />
7 }
View Code

  运行项目并打开该页面,直接点击提交按钮,会得到如下的显示效果:

  这说明MVC框架已经帮我们将字段注册的UI显示信息打印到当前页面。 可是,我们的项目本身的资源语言未定,或者说,需要多语言支持,这个方案就有些牵强。虽然System.ComponentModel.DataAnnotations已经提供了使用自定义资源的相关定义,但都是针对.resx或者已经进行编译过的资源类。无法使用我们自定义的XML资源文件。对此我们有两种方案对其进行改造,以使用我们自定义的资源文件。它们分别是:自定义CustmorDisplayAttribute,改造MVC框架。第一个方案的使用方式与上面例子相同,只不过我们需要为每个需要显示在页面上的Field都引入自定义的属性注释。后两者则更加便捷,无需对类型定义做出任何改变。在这里,我们不讨论第一种方案。

  第二种方案属于“高级方案”,也就是说从设计层面解决这个问题。

  针对这种方案,我们先要了解ModelMetadata,以及ModelMetadataProvider。在此仅附上相关的代码,不做深入讨论。

  首先,是要定义我们自己的ModelMetadataProvider。

 1     public class AppModelMetadataProvider : CachedDataAnnotationsModelMetadataProvider
 2     {
 3         protected override CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype(CachedDataAnnotationsModelMetadata prototype, Func<object> modelAccessor)
 4         {
 5             CachedDataAnnotationsModelMetadata metadata = base.CreateMetadataFromPrototype(prototype, modelAccessor);
 6             if (metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName))
 7             {
 8                 metadata.DisplayName = Resource.GetDisplay(string.Format("{0}.{1}", metadata.ContainerType.Name, metadata.PropertyName));
 9             }
10             return metadata;
11         }
12     }
View Code

  我们仅重写了CachedDataAnnotationsModelMetadataProvider类型中的CreateMetadataFromPrototype方法,在该方法中,我们将Model(这里的Model并非只是页面定义的强类型,它也可以是强类型的属性。当页面初始化阶段,需要获取当前页面的模型类型。因此,这个时候的Model就是页面绑定的强类型实例,其ContainerType为空。但当页面访问到此类型的属性,例如 p => p.UserName,此时的Model就是UserName,ContainerType则为页面绑定的强类型)的DisplayName属性进行修改。原本DisplayName会返回属性的DisplayAttribute注释中定义的资源内容,现在我们将其修改为从自定义资源文件中获取内容(注意:资源的键要严格按照“容器类型名.属性名”进行定义)。

  最后,在Global文件的Application_Start()中添加代码以使用我们自定义的Provider。

 1     protected void Application_Start()
 2         {
 3             AreaRegistration.RegisterAllAreas();
 4 
 5             WebApiConfig.Register(GlobalConfiguration.Configuration);
 6             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
 7             RouteConfig.RegisterRoutes(RouteTable.Routes);
 8             BundleConfig.RegisterBundles(BundleTable.Bundles);
 9             AuthConfig.RegisterAuth();           
10 
11             Bootstrapper.Initialise();  //初始化IOC容器
12 
13             ModelMetadataProviders.Current = new AppModelMetadataProvider();
14         }
View Code

  在视图文件中,我们修改成利用MVC自定义的HtmlHelper显示字段名。如下:

1 @using (Html.BeginForm("EditUser", "Account", FormMethod.Post))
2 {
3     @Html.ValidationSummary()
4     <div>
5         @Html.LabelFor(p => p.UserName)
6         @Html.TextBoxFor(p => p.UserName)       
7     </div>
8     <input type="submit" value="提交" />
9 }
View Code

  第二种方案已完成。运行项目,观察一下我们的页面变化。然后修改一下资源文件中的内容,资源文件的修改也能被立刻应用,而不需要重新编译。

  然后我们在UserProfile类型定义中添加一个Address类型的属性。

 1     [Table("UserProfile")]
 2     public class UserProfile
 3     {
 4         //无关代码省略......
 5 
 6         public int? AddressId { get; set; }
 7 
 8         [ForeignKey("AddressId")]
 9         public Address Address { get; set; }
10     }
11 
12     [Table("Address")]
13     public class Address: BaseEntity<int>
14     {
15         public string City { get; set; }
16     }
View Code

  修改EditUser视图,如下:

 1 @using (Html.BeginForm("EditUser", "Account", FormMethod.Post))
 2 {
 3     @Html.ValidationSummary()
 4     <div>
 5         @Html.LabelFor(p => p.UserName)
 6         @Html.TextBoxFor(p => p.UserName)
 7         @Html.LabelFor(p => p.Address.City)
 8         @Html.TextBoxFor(p => p.Address.City)
 9     </div>
10     <input type="submit" value="提交" />
11 }
View Code

  在资源文件中添加

1 <resource key="Address.City" value="City"/>

  那么运行项目打开页面后,可以看到如下效果:

  

  导航属性的自定义资源信息也会被显示。这里就可以看出p => p.Address.City时,Provider中的ContainerType则为Address的实际类型,ModelMetadata中的Model则为实际上要显示的子类型的属性。

  问题:

  我们通过对ModelMetadata内部的属性进行修改,从而实现自定义资源的使用。这种方式属于高阶应用,有必要深入了解MVC的模型绑定相关的知识。本人在完成这篇文章的时候,耗费了相当长的时间(粗略统计大概有3天时间)。主要是想解决多个同类型属性该如何显示不同的自定义资源。例如在UserProfile类型中定义两个同为Address类型的属性,分别为UserAddress、和CompanyAddress。若同时在页面显示这两个属性的City名称,则显示的内容是相同的,都指向同一个资源:

<resource key="Address.City" value="City"/>但始终未能在第二种方案下找到适合的解决办法。希望有达人能为在下解惑,不胜感激!!!

  下期预告:第三中方案解决同类型导航属性显示不同的自定义资源。

  

 

posted @ 2014-08-30 15:01  篱笆1006  阅读(772)  评论(0编辑  收藏  举报