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

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

      

  • 相关阅读:
    flex 圣杯布局
    .net mvc里AutoMapper更为便捷的使用方法
    使用ClaimsIdentity来实现登录授权
    判断js中数据类型 的最短代码
    输入分钟、秒倒计时
    .net 导出Excel插件Npoi的使用
    seajs简单使用
    图片懒加载
    dropload上拉加载更多
    基于移动端的左滑删除
  • 原文地址:https://www.cnblogs.com/libra1006/p/3932056.html
Copyright © 2020-2023  润新知