• 扩展 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;
    }

     

  • 相关阅读:
    Storyboard中segue使用总结
    Present ViewController Modally
    UILabel设置富文本格式显示
    objective-c 中随机数的用法 (3种:arc4random() 、random()、CCRANDOM_0_1() )
    ios中static的作用
    NSBundle的使用,注意mainBundle和Custom Bundle的区别
    OC的基础语法OC继承和复合语言特性目标动作回调
    动态规划-被3整除的子序列
    A
    Coins POJ
  • 原文地址:https://www.cnblogs.com/SkySoot/p/6050251.html
Copyright © 2020-2023  润新知