扩展 ASP.NET MVC 模型扩展 – ASP.NET MVC 4 系列

       大部分人不能将核心运行时(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;
}

 

时间: 2024-10-10 08:47:13

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

MVC模型概述(1)

摘自( 私塾在线,跟开涛学SpringMVC) 1.标准MVC模型概述       MVC模型(Model-View-Controller)是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的结构组织的更加合理,使展示与模型分离.流程控制逻辑.业务逻辑调用与展示逻辑分离. Model提供要展示的数据.View负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西.Controller接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示

[转]ASP.NET MVC HtmlHelper扩展之Calendar日期时间选择

本文转自:http://blog.bossma.cn/asp_net_mvc/asp-net-mvc-htmlhelper-calendar-datetime-select/ 这里我们扩展HtmlHelper,就像它包含在ASP.NET MVC中一样,扩展方法使我们能为已有的类添加方法.这里使用了一个日期时间选择控件:My97DatePicker,需要添加到网站中,并在页面中引用. 先看看是怎么扩展的: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

Asp.net模块化开发之Mvc分区扩展框架

对于一个企业级项目开发,模块化是非常重要的. 默认Mvc框架的AreaRegistration对模块化开发已经支持比较好了,还有必要扩展吗?还能扩展吗? 本文中的栗子是使用.net4.0.Mvc4.0及Unity2.0(企业库4.0)的,提供完整源码. 本分区扩展集成了IoC和分区DI(依赖注入)及分区过滤器的支持. 本分区扩展框架(Fang.Mvc)在演示栗子源码中包含完整源码,拿到自己的项目直接引用即可使用了. 感兴趣的同学请继续,用AreaRegistration有不爽的看官请拭目以待..

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

在ASP.NET MVC4中,HtmlHelper为我们提供了Html.RadioButton()方法用来显示Radio Button单选按钮.如果想显示一组单选按钮,通常的做法是遍历一个集合把每个单选按钮显示出来.本篇尝试写一个扩展方法用来展示一组带验证的单选按钮. 首先来扩展HtmlHelper,扩展方法中接收一个SelectListItem的集合,遍历这个集合把每个单选按钮显示出来,并且让这些单选按钮具有不同的id属性值. using System.Collections.Generic;

[转] ASP.NET MVC 模型绑定的功能和问题

摘要:本文将与你深入探究 ASP.NET MVC 模型绑定子系统的核心部分,展示模型绑定框架的每一层并提供扩展模型绑定逻辑以满足应用程序需求的各种方法. 同时,你还会看到一些经常被忽视的模型绑定技术,并了解如何避免一些最常见的模型绑定错误. ASP.NET MVC 模型绑定通过引入自动填充控制器操作参数的抽象层.处理通常与使用 ASP.NET 请求数据有关的普通属性映射和类型转换代码来简化控制器操作. 虽然模型绑定看起来很简单,但实际上是一个相对较复杂的框架,由许多共同创建和填充控制器操作所需对

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

这篇教程的目的是解释在创建ASP.NET MVC应用程序时,如何使用Microsoft Entity Framework来创建数据访问类.这篇教程假设你事先对Microsoft Entity Framework没有任何的了解.读完本篇教程,你将会理解如何使用Entity Framework来选择.插入.更新和删除数据库记录. Microsoft Entity Framework是一个对象关系映射(O/RM)工具,它能你让自动从数据库生成数据访问层.Entity Framework能够使你免于手工

Asp.Net MVC 模型(使用Entity Framework创建模型类)

这篇教程的目的是解释在创建ASP.NET MVC应用程序时,如何使用Microsoft Entity Framework来创建数据访问类.这篇教程假设你事先对Microsoft Entity Framework没有任何的了解.读完本篇教程,你将会理解如何使用Entity Framework来选择.插入.更新和删除数据库记录. Microsoft Entity Framework是一个对象关系映射(O/RM)工具,它能你让自动从数据库生成数据访问层.Entity Framework能够使你免于手工

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序使用高级功能

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第十二篇:为ASP.NET MVC应用程序使用高级功能 原文:Advanced Entity Framework 6 Scenarios for an MVC 5 Web Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中,您已经实现了继承.本教程引入了当你在使用实体框架Code

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(1)-框架搭建

原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(1)-框架搭建 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) 前言:这篇博客开始我们便一步一步的来实现这个权限系统的初步设计-框架搭建,首先我要说的是我们需要开发工具Visual Studio 2012或者10也行,其次是我们要有SQL Server数据库,如果是Visual Studio 2010的话,你还要安装MVC4的开发文件,这个是吗?我不记得了,谁可以回答我一下的,我一直用2012,都是集成