ASP.NET MVC Model元数据及其定制:一个重要的接口IMetadataAware

在介绍用于自定义Model元数据属性的AdditionalMetadataAttribute特性时我们提到了它实现的接口IMedataAware,我们说这是一个非常重要并且有用的接口,通过自定义实现该接口的特性我们可以对最终生成的Model元数据进行自由地定制。如下面的代码片断所示,IMedataAware接口具有唯一的方法成员OnMetadataCreated。当Model元数据被创建出来后,会先获取上述的这一系列标注特性对其进行初始化,然后获取应用在目标元素上所有实现了IMedataAware接口的特性,并将初始化的ModelMetadata对象作为参数调用OnMetadataCreated方法。所以我们通过创建实现该接口的特性不仅仅可以添加一些额外的元数据属性,也可以修改已经通过相应的标注特性初始化的相关属性。[本文已经同步到《How ASP.NET MVC Works?》中]

   1: public interface IMetadataAware
   2: {    
   3:     void OnMetadataCreated(ModelMetadata metadata);
   4: }

ASP.NET MVC定义了两个实现了IMedataAware接口的特性,一个就是我们已经介绍过的AdditionalMetadataAttribute,另一个则是AllowHtmlAttribute

一、AllowHtmlAttribute

为了防止最终用于通过在针对某个数据的输入中注入一些HTML来攻击我们的Web应用,ASP.NET MVC在进行Model绑定之前会对对应的请求数据进行验证,确保没有任何HTML标记包含其中。这个针对HTML标记的验证通过ModelMetadata的RequestValidationEnabled来控制,如下面的代码片断所示,这是一个布尔类型的可读写属性。该属性在默认情况下为True,意味着默认开启针对HTML标记的请求验证。

   1: public class ModelMetadata
   2: {
   3:     //其他成员
   4:     public virtual bool RequestValidationEnabled { get; set; } 
   5: }

AllowHtmlAttribute特性,顾名思义,就是运行作为目标元素的内容包含HTML标记。如下面的代码片断所示,AllowHtmlAttribute是实现了IMetadataAware 接口,在OnMetadataCreated方法中它直接将作为参数的ModelMetadata对象的RequestValidationEnabled属性设置为False,从而使针对目标对象的请求验证被忽略掉。

   1: [AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
   2: public sealed class AllowHtmlAttribute : Attribute, IMetadataAware
   3: {
   4:     public void OnMetadataCreated(ModelMetadata metadata)
   5:     {
   6:         //其他操作
   7:         metadata.RequestValidationEnabled = false;
   8:     }
   9: }

为了验证ASP.NET MVC针对HTML标记的请求验证和AllowHtmlAttribute的作用,我们来做一个简单的实例演示。在通过Visual Studio提供的ASP.NET MVC项目模板创建的空Web应用中,我们定义了如下一个数据类型Foo,其中属性Baz上应用了AllowHtmlAttribute特性。

   1: public class Foo
   2: {
   3:     public string Bar { get; set; }
   4:  
   5:     [AllowHtml]
   6:     public string Baz { get; set; }
   7: }

然后我们创建如下一个默认的HomeController,默认的Index操作方法中具有一个类型为Foo的参数,该参数直接作为Model呈现在默认的View中。

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult Index(Foo foo)
   4:     {
   5:         return View(foo);
   6:     }
   7: }

如下所示的Index操作对应的View定义,这是一个以Foo为Model的强类型View。在该View中,我们直接调用HtmlHelper<Model>的EditorForModel方法将Foo对象以编辑模式呈现出来。

   1: @model Foo
   2: @{
   3:     ViewBag.Title = "Index";
   4: }
   5: @Html.EditorForModel()

现在我们直接运行该Web应用。根据Model绑定的规则我们知道,如果我们通过浏览器访问HomeController的Index操作,可以通过查询字符串的方式对该操作方法的参数进行初始化。具体来说,我们可以分别指定名称为Bar和Baz的查询字符串对作为参数的Foo对象的两个属性进行初始化。为了验证对包含HTML标记的输入的验证,我们将最终绑定到Model上的查询字符串设置为<script/>。

如下图所示,由于Foo的属性Baz上应用了AllowHtmlAttribute特性是之支持包含HTML标记的数据,所以我们以查询字符串方式指定的包含HTML标记的内容(<script/>)直接显示在相应的文本框中。但是Bar属性在默认情况下是不运行绑定的数据具有任何HTML标记的,所以会将输入的数据视为恶意注入的HTML,直接抛出异常。

二、实例演示:创建实现IMetadataAware接口的特性定制Model元数据

通过上面对Model元数据定义的介绍我们知道显示名称可以通过在数据类型或者属性成员上应用DisplayAttribute特性来定义。在使用该特性的时候,我们需要显式制定表示显示名称的Name属性,如果需要进行本地化处理,需要将显示内容定义在某个资源文件中,并通过ResourceType属性指定该资源文件生成的类型。[源代码从这里下载]

为了简化,我们通过实现IMetadataAware接口的方式定义了如下一个DisplayTextAttribute特性。该特性的属性DisplayName/ResourceType与DisplayAttribute的Name/ResourceType具有相同的作用,唯一不同的是DisplayTextAttribute的这两个属性均是可以缺省的。如果DisplayName没有显式指定,则默认使用属性名称或者类型名称;如果ResourceType没有显式指定,则采用通过静态字段staticResourceType表示的默认资源类型,该类型通过静态方法SetResourceType进行注册。

   1: [AttributeUsage(AttributeTargets.Class| AttributeTargets.Property)]
   2: public class DisplayTextAttribute: Attribute, IMetadataAware
   3: {
   4:     private static Type staticResourceType;
   5:     public string DisplayName { get; set; }
   6:     public Type ResourceType { get; set; }
   7:  
   8:     public DisplayTextAttribute()
   9:     {
  10:         this.ResourceType = staticResourceType;
  11:     }
  12:  
  13:     public void OnMetadataCreated(ModelMetadata metadata)
  14:     {
  15:         this.DisplayName = this.DisplayName ?? (metadata.PropertyName ?? metadata.ModelType.Name);
  16:         if (null == this.ResourceType)
  17:         {
  18:             metadata.DisplayName = this.DisplayName;
  19:             return;
  20:         }
  21:         PropertyInfo property = this.ResourceType.GetProperty(this.DisplayName, BindingFlags.NonPublic|BindingFlags.Public| BindingFlags.Static);
  22:         metadata.DisplayName = property.GetValue(null, null).ToString();
  23:     }
  24:  
  25:     public static void SetResourceType(Type resourceType)
  26:     {
  27:         staticResourceType = resourceType;
  28:     }
  29: }

DisplayTextAttribute对Model元数据的定制实现在OnMetadataCreated方法中。具体来说,我们根据设置的DisplayName和ResourceType属性解析出最终作为目标元素显示名称的文本作为ModelMetadata的DisplayName属性值。

接下来我们来演示如何使用这个DisplayTextAttribute特性来替换DisplayAttribute特性进行显示名称的设置,为此我们在通过Visual Studio的ASP.NET MVC 项目模板创建的空Web应用中创建如下一个表示员工的Employee类型。Employee所有的属性上均应用了DisplayTextAttribute特性,而DisplayName和ReourceType属性没有显式指定。

   1: public class Employee
   2: {
   3:     [DisplayText]
   4:     public string Name { get; set; }
   5:  
   6:     [DisplayText]
   7:     public string Gender { get; set; }
   8:  
   9:     [DisplayText]
  10:     [DataType(DataType.Date)]
  11:     public DateTime BirthDate { get; set; }
  12:  
  13:     [DisplayText]
  14:     public string Department { get; set; }
  15: }

接下来我们打开项目的属性对话框并选择“资源(Rources)”Tab页,按照如下图所示为Employee中的四个属性定义相应的资源字符串作为显示的名称,资源字符串条目的名称为属性名。

该资源文件会自动生成一个类型为Resources的内部类型。由于应用在Employee属性上的DisplayTextAttribute特性并没有显式指定资源类型,所以我们需要在Global.asax文件中通过如下的方式将Resources类型注册为默认的资源类型。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {    
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         DisplayTextAttribute.SetResourceType(typeof(Resources));
   8:     }
   9: }

现在我们通过调用HtmlHelper<TModel>的EditorForModel方法将一个具体的Employee对象以编辑模式显示在某个Model类型为Employee的强类型View上,会呈现出如下图所示的效果,我们可以看到作为标签显示的文字正式我们定义在资源文件中的内容。

ASP.NET MVC Model元数据及其定制: 初识Model元数据 ASP.NET MVC Model元数据及其定制: Model元数据的定制 ASP.NET MVC Model元数据及其定制:一个重要的接口IMetadataAware

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java3y

【不用框架】文件上传和下载

什么是文件上传? 文件上传就是把用户的信息保存起来。 为什么需要文件上传? 在用户注册的时候,可能需要用户提交照片。那么这张照片就应该要进行保存。 上传组件(工...

5904
来自专栏向前进

【笔记】HybridApp中使用Promise化的JS-Bridge

背景: HybridApp,前端采用JS-bridge的方式调用Native的接口,如获取设备信息、拍照、人脸识别等 前端封装了调用库,每次调用Native接口...

3454
来自专栏腾讯数据库技术

Online DDL过程介绍

3673
来自专栏Java架构沉思录

分布式ID常见解决方案

在分布式系统中,往往需要对大量的数据如订单、账户进行标识,以一个有意义的有序的序列号来作为全局唯一的ID。

5522
来自专栏逆向技术

常见注入手法第一讲EIP寄存器注入

             常见注入手法第一讲EIP寄存器注入 鉴于注入手法太多,所以这里自己整理一下,每个注入单独一片博客。方便大家简单理解。 但是有的注入可...

4306
来自专栏cnblogs

knockoutjs 上自己实现的flux

在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题。 一、其实简单 flux的设计理念和实现方案,很大程度上人借...

2188
来自专栏xingoo, 一个梦想做发明家的程序员

【Spring实战】—— 3 使用facotry-method创建单例Bean总结

如果有这样的需求:   1 不想再bean.xml加载的时候实例化bean,而是想把加载bean.xml与实例化对象分离。   2 实现单例的bean ...

1985
来自专栏逆向技术

常见注入手法第四讲,SetWindowsHookEx全局钩子注入.以及注入QQ32位实战.

钩子回调根据SetWindowsHookEx参数1来设定的.比如如果我们设置WH_CBT 那么我们设置的回调函数就是CBT回调. 具体查询MSDN

1.5K2
来自专栏技术博文

mail邮件类

<?php class MailSvc { //-设置全局变量 var $mailTo = ""; // 收件人 var $mailCC = ""; // 抄送...

3659
来自专栏开发技术

cassandra高级操作之分页的java实现(有项目具体需求)

  接着上篇博客,我们来谈谈java操作cassandra分页,需要注意的是这个分页与我们平时所做的页面分页是不同的,具体有啥不同,大家耐着性子往下看。

1251

扫码关注云+社区

领取腾讯云代金券