.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; }

  在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 }

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

  这说明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     }

  我们仅重写了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         }

  在视图文件中,我们修改成利用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 }

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

  然后我们在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     }

  修改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 }

  在资源文件中添加

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

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

  

时间: 2024-10-06 17:13:06

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

.NET MVC4 实训记录之四(Unit of work + Repository)

今日后开启进阶模式! 谈到MVC与EntityFramework,则不得不说一说事务与仓储(Unit of work + Repository). 仓储(Repository):领域对象集合.用于操作领域对象与数据库上下文(DbContext)的交互(在此不得不说一声,领域对象和数据库表对象还是有区别的.领域对象实际上是一组有业务关系的数据库对象的抽象.最简单的形式就是主表.关系表在同一个领域对象中进行定义.例如我们前几章看到的UserProfile,它即定义了用户信息,又定义了用户角色关系信息

.NET MVC4 实训记录之五(访问自定义资源文件)

.Net平台下工作好几年了,资源文件么,大多数使用的是.resx文件.它是个好东西,很容易上手,工作效率高,性能稳定.使用.resx文件,会在编译期动态生成已文件名命名的静态类,因此它的访问速度当然是最快的.但是它也有个最大的缺点,就是修改资源文件后,项目必须重新编译,否则修改的资源不能被识别.这对于维护期的工作来讲,非常麻烦.尤其是已经上线的项目,即使是修改一个title的显示,也需要停掉项目.由于本人做了好几年的维护,应该是从工作到现在,一直没有间断过的做维护项目,因此深受其害!必须找到一个

.NET MVC4 实训记录之一(引入Unity3.0 Ioc框架)

一直在做维护项目,没有机会接触完整的架构,于是自学.NET MVC.自今日起,将自学的过程.遇到的问题以及解决方案记录下来. 在WebApp项目中右键,使用NuGet引入Unity3.0. 引入后项目目录下会自动生成一个Bootstrapper.cs文件. 手动在WebApp根目录下创建IOC.config文件,用于配置IOC规则.(注意:之前看网上教程中的配置文 件,<typeAliases></typeAliases>节点和<unity></unity>

.NET MVC4 实训记录之二(扩展WebSecurity模型下的UserProfile表)

使用VS2013创建MVC4项目后,自动生成的代码中默认使用WebSecurity模型创建用户管理,生成以下数据库: 用户信息只有ID和UserName,角色信息也只有两个基础字段.通常情况下这样的数据表不能满足我们的需求,因此对其进行扩展. 首先定义自己的用户信息.角色信息结构. 1 [Table("UserProfile")] 2 public class UserProfile 3 { 4 [Key] 5 [DatabaseGenerated(DatabaseGeneratedO

.NET MVC4 实训记录之三(EntityFramework 与枚举)

EntityFramework对枚举的引入是从版本5开始的(如果没有记错的话).枚举可以很大程度上提高对程序的可读性.那么在EntityFramework的CodeFirst模式下,如何使用枚举呢?答案很简单:还是那么用! 看似废话,其实不然,看下面(修改上一篇中用户信息定义): /// <summary> /// 性别枚举 /// </summary> public enum Gender { Male, Female } public class UserProfile { [

.NET MVC4 实训记录之七(实现资源的自主访问后续)

我们在上一篇中讨论了如何利用ModelMetadata实现国际化资源文件访问,但也留下了一些问题,即:如何利用ModelMetadata实现相同类型的属性信息的个性化资源显示.本人没有找到合适的方案,期待着高人的指点. 本章,介绍第三种资源访问方案,用于解决上述问题(该方案并非从设计角度解决问题). 首先,描述下我们的问题. 第一步,在UserProfile类型中添加两个Address类型的属性: 1 #region 用户住址信息 2 3 public int? UserAddressId {

安卓实训第十四天---使用ContentProvider共享数据,并且利用ContentResolver监听共享数据

ContentProvider: 一.当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据:采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据.而使用ContentProvider共享数据的好处是统一了数据访问方式. 第二步需要在AndroidManif

安卓实训第十天:利用SharedPreferences来实现数据的保存和读取,以及实现手机电话备份,XMLserializer

一.利用SharedPreferences来实现数据的保存和读取: 1.Mainactivity: package com.example.sharedpreferencesdemo; import com.example.sharedpreferencesdemo.util.SharedPreferencesUtil; import android.app.Activity; import android.app.AlertDialog; import android.content.Cont

实训任务05 MapReduce获取成绩表的最高分记录

实训任务05  MapReduce获取成绩表的最高分记录 实训1:统计用户纺问次数 任务描述: 统计用户在2016年度每个自然日的总访问次数.原始数据文件中提供了用户名称与访问日期.这个任务就是要获取以每个自然日为单位的所有用户访问次数的累加值.如果通过MapReduce编程实现这个任务,首先要考虑的是,Mapper与Reducer各自的处理逻辑是怎样的:然后根据处理逻辑编写出核心代码:最后在Eclipse中编写完整代码,编译打包后提交给集群运行. 分析思路和逻辑 (1)       输入/输出