码迷,mamicode.com
首页 > Web开发 > 详细

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

时间:2016-11-10 11:45:38      阅读:203      评论:0      收藏:0      [点我收藏+]

标签:完整   count   new   取值   字符   sys   ann   context   sid   

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

标签:完整   count   new   取值   字符   sys   ann   context   sid   

原文地址:http://www.cnblogs.com/SkySoot/p/6050251.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!