.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"/>但始终未能在第二种方案下找到适合的解决办法。希望有达人能为在下解惑,不胜感激!!!

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

  

 

你可能感兴趣的:(Model)