大部分人不能将核心运行时(System.Web 中的类)和 ASP.NET Web Forms 应用程序平台(System.Web.UI 中的类)区分开来。

       ASP.NET 开发团队在简单的核心运行时抽象之上创建了复杂的 Web Form 抽象和 ASP.NET MVC。正因为 ASP.NET MVC 框架建立在公共抽象之上,所以 ASP.NET MVC 框架能实现的任何功能,任何人也都可以实现。ASP.NET MVC 框架本身也由若干层抽象组成,从而使得开发人员能够选择他们需要的 MVC 片段,替换或修改他们不需要的片段。对于后续的每一个版本,ASP.NET MVC 团队都开放了更多的框架内部定制点。

       ASP.NET MVC 4 中的模型系统包括几个可扩展部分,其中包括使用元数据描述模型、验证模型以及影响从请求中构造模型的能力。

 

模型扩展 - 把请求数据转化为模型

       将请求数据(表单数据、查询字符串数据、路由信息)转换为模型的过程称为模型绑定。模型绑定分为两个阶段:

  • 使用值提供器理解数据的来源
  • 使用这些值 创建/更新 模型对象(通过使用 模型绑定器)

       真实模型绑定过程中使用的值都来自值提供器。值提供器的作用仅仅是访问能够在模型绑定过程中正确使用的信息。ASP.NET MVC 框架自带的若干值提供器可以提供以下数据源中的数据:

  1. 子操作(RenderAction)的显式值
  2. 表单值
  3. 来自 XMLHttpRequest 的 JSON 数据
  4. 路由值
  5. 查询字符串值
  6. 上传的文件

       值提供器来自值提供器工厂,并且系统按照值提供器的注册顺序来从中搜寻数据(上面是默认顺序)。开发人员可以编写自己的值提供器工厂和值提供器,并且还可以把它们插入到包含在 ValueProviderFactories.Factories 中的工厂列表中。当模型绑定期间需要使用额外的数据源时,开发人员通常会选择编写自己的值提供器工厂和值提供器。

       除了 ASP.NET MVC 本身包含的值提供器工厂以外,开发团队也在 ASP.NET MVC Futures 中包含了另一些值提供器工厂和值提供器:

  1. Cookie 值提供器
  2. 服务器变量值提供器
  3. Session 值提供器
  4. TempData 值提供器

 

       模型扩展的另一部分是模型绑定器。它们从值提供器系统中获取值,并利用获取的值创建新模型或者填充已有模型。ASP.NET MVC 中的默认模型绑定器(DefaultModelBinder)是一段非常强大的代码,它可以对传统类、集合类、列表、数组、字典进行模型绑定。但默认模型绑定器不支持不可变对象(对象初始值通过构造函数设置,之后不能改变)。

       例如,由于 CLR 中 Point 类是不可变的,因此我们必须使用它的值构造一个新实例:

public class PointModelBinder : IModelBinder

{

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

    {

        var valueProvider = bindingContext.ValueProvider;

        int x = (int)valueProvider.GetValue("X").ConvertTo(typeof(int));

        int y = (int)valueProvider.GetValue("Y").ConvertTo(typeof(int));

        return new Point(x, y);

    }

}

       当创建一个新的模型绑定器时,需要告知 MVC 框架存在一个新的模型绑定器(可以在 ModelBinders.Binders 的全局列表中注册新的模型绑定器)以及何时使用它(可用 [ModelBinder] 特性来装饰绑定类)。

       模型绑定器往往容易被忽略的一个责任是:验证它们要绑定的值。上面的示例代码未包含任何验证逻辑,因此看上去非常简单。下面是一个更完整的模型绑定器版本:

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

{

    // We first attempt to find values based on the model name, and if we can't find

    // anything for the model name, we'll fall back to the empty prefix as appropriate.

 

    if (!String.IsNullOrEmpty(bindingContext.ModelName) &&

        !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))

    {

        if (!bindingContext.FallbackToEmptyPrefix)

            return null;

 

        bindingContext = new ModelBindingContext

        {

            ModelMetadata = bindingContext.ModelMetadata,

            ModelState = bindingContext.ModelState,

            PropertyFilter = bindingContext.PropertyFilter,

            ValueProvider = bindingContext.ValueProvider

        };

    }

 

    // We have to create a new model, because Point is immutable. When the type

    // isn't immutable, we can always take in the existing object, when one exists,

    // and update it instead. The existing object, if one exists, is available

    // via bindingContext.Model. Instead, we'll put a temporary (empty) object into

    // the binding context so that validation can succeed while we validate all

    // the parameter values.

 

    bindingContext.ModelMetadata.Model = new Point();

 

    return new Point(

        Get<int>(controllerContext, bindingContext, "X"),

        Get<int>(controllerContext, bindingContext, "Y")

    );

}

       上述代码新增了 2 项内容:

  1. if 代码块试图在回落到空前缀之前找到带有名称前缀的值。当系统开始模型绑定时,模型参数名称(本例中是 pt,public ActionResult Index(Point pt))被设置为 bindingContext.ModelName 中的值,然后再查看值提供器,以确定是否包含 pt 为前缀的子值,例如 pt.X,pt.Y。假如拥有一个名为 pt 的参数,那么使用的值的名称应该是 pt.X 和 pt.Y 而不是只有 X,或只有 Y。然而,如果找不到以 pt 开头的值,就需要使用名称中只有 X 和 Y 的值。
  2. 在 ModelMetadata.Model 中设置了一个 Point 对象的空实例。之所以这样做,是因为大部分验证系统包括 DataAnnotations,都期望看到一个容器对象的实例,即便里面不存在任何实际的值。由于 Get 方法调用验证,因此我们需要提供给验证系统一个某种类型的容器对象,即便知道它不是最终容器。

 

       下面是 Get 方法的完整函数:

private TModel Get<TModel>(ControllerContext controllerContext,

                            ModelBindingContext bindingContext,

                            string name)

{

    // Get the fully qualified name, because model state needs to use that, and not just

    // the simple property name.

    string fullName = name;

    if (!String.IsNullOrWhiteSpace(bindingContext.ModelName))

        fullName = bindingContext.ModelName + "." + name;

 

    // Get the value from the value provider

    ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(fullName);

 

    // Add the attempted value to model state, so that we can round-trip their

    // value even when it's incorrect and incapable of being held inside the

    // model itself (i.e., the user types "abc" for an int).

    ModelState modelState = new ModelState { Value = valueProviderResult };

    bindingContext.ModelState.Add(fullName, modelState);

 

    // Get the ModelMetadata that represents this property, as we use several of its

    // values, and it's necessary for validation

    ModelMetadata metadata = bindingContext.PropertyMetadata[name];

 

    // Convert the attempted value to null automatically

    string attemptedValue = valueProviderResult.AttemptedValue;

    if (metadata.ConvertEmptyStringToNull && String.IsNullOrWhiteSpace(attemptedValue))

        attemptedValue = null;

 

    TModel model;

    bool invalidValue = false;

 

    try

    {

        // Attempt to convert the value to the correct type

        model = (TModel)valueProviderResult.ConvertTo(typeof(TModel));

        metadata.Model = model;

    }

    catch (Exception)

    {

        // Conversion failed, so give back the default value for the type

        // and set the attempted value into model metadata

        model = default(TModel);

        metadata.Model = attemptedValue;

        invalidValue = true;

    }

 

    // Run the validators for the given property

    IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(metadata, controllerContext);

    foreach (var validator in validators)

        foreach (var validatorResult in validator.Validate(bindingContext.Model))

            modelState.Errors.Add(validatorResult.Message);

 

    // Only add the "invalid value" message if there were no other errors, because things like

    // required validation should trump conversion failures, and null/empty values will often

    // fail both required validation and type-conversion validation

    if (invalidValue && modelState.Errors.Count == 0)

        modelState.Errors.Add(

            String.Format(

                "The value '{0}' is not a valid value for {1}.",

                attemptedValue,

                metadata.GetDisplayName()

            )

        );

 

    return model;

}

 

扩展 ASP.NET MVC 模型扩展 – ASP.NET MVC 4 系列的更多相关文章

  1. ASP.NET编程模型之ASP.NET页面生命周期图解

    ASP.NET编程模型中ASP.NET页面生命周期是指什么呢?它包括什么呢?ASP.NET编程模型之ASP.NET页面生命周期具体的过程有哪些呢?下面就开始我们的讲解吧: ASP.NET 页运行时,此 ...

  2. ASP.Net 管道模型 VS Asp.Net Core 管道 总结

    1 管道模型  1 Asp.Net Web Form管道 请求进入Asp.Net工作进程后,由进程创建HttpWorkRequest对象,封装此次请求有关的所有信息,然后进入HttpRuntime类进 ...

  3. MVC模型与FishiGUI应用层MVC型号

    MVC概要: MVC (Modal View Controler)M是指数据模型,V是指用户界面,C则是控制器. 使用MVC的目的是将M和V的实现代码分离,从而使同一个程序能够使用不同的表现形式.比方 ...

  4. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(16)-类库架构扩展以及DLL文件生成修改和用户的简单添加

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(16)-类库架构扩展以及DLL文件生成修改和用户的简单添加 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) ...

  5. 在ASP.NET MVC下扩展一个带验证的RadioButtonList

    在ASP.NET MVC4中,HtmlHelper为我们提供了Html.RadioButton()方法用来显示Radio Button单选按钮.如果想显示一组单选按钮,通常的做法是遍历一个集合把每个单 ...

  6. 13个 ASP.NET MVC 的扩展

    ASP.NET MVC设计的主要原则之一是可扩展性.处理管线(processing pipeline)上的所有(或大多数)东西都是可替换的.因此,如果您不喜欢ASP.NET MVC所使用的约定(或缺乏 ...

  7. 【asp.net mvc】 扩展 htmlhelper 实现分页

    参考文档:http://www.cnblogs.com/caofangsheng/p/5670071.html                  http://www.cnblogs.com/arte ...

  8. Asp.Net Mvc 自定义扩展

    目录: 自定义模型IModelBinder 自定义模型验证 自定义视图引擎 自定义Html辅助方法 自定义Razor辅助方法 自定义Ajax辅助方法 自定义控制器扩展 自定义过滤器 自定义Action ...

  9. Asp.Net MVC 模型(使用Entity Framework创建模型类) - Part.1

    这篇教程的目的是解释在创建ASP.NET MVC应用程序时,如何使用Microsoft Entity Framework来创建数据访问类.这篇教程假设你事先对Microsoft Entity Fram ...

随机推荐

  1. UIImage学习

    UIImage A UIImage object is a high-level way to display image data. You can create images from files ...

  2. GCHandler的使用

    众所周知,我们在使用c#托管代码时,内存地址和GC回收那不是我们关心的,CLR已经给我们暗箱操作. 但是如果我们在c#中调用了一个非托管代码,比如vc的DLL,而且他有个回调函数,需要引用c#中的某个 ...

  3. query语句的拼接.

    在外网服务器上的网页,不能排序了,原因是query语句拼接时,sort没有能拼上,. 小小的思路: 1.是不是网页请求出了问题: 和我本机上的的请求一一对比,并没有发现什么问题. 2.代码除了问题吗: ...

  4. 图片压缩工具optipng/jpegoptim安装

    [1]还未实践 #yum install optipng -y [2]已成功 #yum install -y libjpeg libjpeg-devel #wget http://freecode.c ...

  5. Unable to load native-hadoop library for your platform

    #HADOOP VARIABLES START export JAVA_HOME=/home/yang/jdk1.7.0_80export HADOOP_HOME=/home/hadoop/hadoo ...

  6. The constructor User.Student(String, String, String) is not visible

    项目:蒙文词语检索 日期:2016-05-01 提示:The constructor User.Student(String, String, String) is not visible 出处:Db ...

  7. IBM Domino 9 出现 Domino Designer 您正在试图升级多用户安装。请获取正确的安装包以完成升级。 解决方案

    如果网上搜索的其他方法解决不了,那么我的这个方法可以试一下. 出现的场景: 先装好了Notes,后准备装Designer. 在装Designer解压包之后,出现下面的错误,不能安装: 您正在试图升级多 ...

  8. c++ 顺序容器学习

    所谓容器,就是一个装东西的盒子,在c++中,我们把装的东西叫做“元素” 而顺序容器,就是说这些东西是有顺序的,你装进去是什么顺序,它们在里面就是什么顺序. c++中的顺序容器一共有这么几种: vect ...

  9. [转]IoC模式

    IoC模式 1.依赖 依赖就是有联系,有地方使用到它就是有依赖它,一个系统不可能完全避免依赖.如果你的一个类或者模块在项目中没有用到它,恭喜你,可以从项目中剔除它或者排除它了,因为没有一个地方会依赖它 ...

  10. [转]const 与 readonly知多少

    引自:http://www.cnblogs.com/royenhome/archive/2010/05/22/1741592.html 尽管你写了很多年的C#的代码,但是可能当别人问到你const与r ...