MVC源码分析 - ModelBinder绑定 / 自定义数据绑定

这几天老感觉不对, 总觉得少点什么, 今天才发现, 前面 3 里面, 在获取Action参数信息的时候,  少解析了. 里面还有一个比较重要的东西. 今天看也是一样的.

在 InvokeAction() 方法里面, 有一句代码:

IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor);

这个是用来获取参数的. 那么参数是不是随便获取呢? 在Mvc 里面, 页面向Action 传参的时候, 有没有尝试过传一个数组, 然后接收的时候, 也直接解析成数组呢? 或者接收更复杂的类型呢?

答案都在这一篇里面了. 先来看源码.

protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext,   ActionDescriptor actionDescriptor)
{
    Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
    foreach (ParameterDescriptor descriptor in actionDescriptor.GetParameters())
    {
        dictionary[descriptor.ParameterName] = this.GetParameterValue(controllerContext, descriptor);
    }
    return dictionary;
}

一、源码解析

1. actionDescriptor.GetParameters()

//System.Web.Mvc.ReflectedActionDescriptor
public override ParameterDescriptor[] GetParameters()
{
    return ActionDescriptorHelper.GetParameters(this, this.MethodInfo, ref this._parametersCache);
}

这里应该是获取所有的参数和其描述信息.

2. this.GetParameterValue() -- 主要方法

protected virtual object GetParameterValue(ControllerContext controllerContext,   ParameterDescriptor parameterDescriptor)
{
    Type parameterType = parameterDescriptor.ParameterType;   //根据参数描述来获取参数的处理接口
    IModelBinder modelBinder = this.GetModelBinder(parameterDescriptor);
    IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
    string str = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;   //获取参数上面的过滤器, 并在下面放入到参数解析上下文中(ModelBindingContext)
    Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
    ModelBindingContext bindingContext = new ModelBindingContext {
        FallbackToEmptyPrefix = parameterDescriptor.BindingInfo.Prefix == null,
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
        ModelName = str,
        ModelState = controllerContext.Controller.ViewData.ModelState,
        PropertyFilter = propertyFilter,
        ValueProvider = valueProvider
    };   //执行参数的处理程序
    return (modelBinder.BindModel(controllerContext, bindingContext) ?? parameterDescriptor.DefaultValue);
}

2.1 GetModelBinder()方法

private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor)
{
    return (parameterDescriptor.BindingInfo.Binder ??       this.Binders.GetBinder(parameterDescriptor.ParameterType));
}

这里是根据参数描述来获取参数的处理接口

public interface IModelBinder
{
    // Methods
    object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
}

2.2 BindModel()方法 - 这里看的是 DefaultModelBinder, 后面会自定义一个

public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    EnsureStackHelper.EnsureStack();
    if (bindingContext == null)
    {
        throw new ArgumentNullException("bindingContext");
    }
    bool flag = false;
    if (!string.IsNullOrEmpty(bindingContext.ModelName)     && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
    {
        if (!bindingContext.FallbackToEmptyPrefix)
        {
            return null;
        }
        ModelBindingContext context = new ModelBindingContext {
            ModelMetadata = bindingContext.ModelMetadata,
            ModelState = bindingContext.ModelState,
            PropertyFilter = bindingContext.PropertyFilter,
            ValueProvider = bindingContext.ValueProvider
        };
        bindingContext = context;
        flag = true;
    }
    if (!flag)
    {
        bool flag2 = ShouldPerformRequestValidation(controllerContext, bindingContext);
        bool skipValidation = !flag2;
        ValueProviderResult valueProviderResult = bindingContext.UnvalidatedValueProvider        .GetValue(bindingContext.ModelName, skipValidation);
        if (valueProviderResult != null)
        {         //为简单对象返回参数值
            return this.BindSimpleModel(controllerContext, bindingContext, valueProviderResult);
        }
    }
    if (!bindingContext.ModelMetadata.IsComplexType)
    {
        return null;
    }
    return this.BindComplexModel(controllerContext, bindingContext);
}

这里面的内容有点多, 也有点复杂, 其实解析到这里, 差不多已经得到我想要的东西了.

二、自定义ModelBinder

1. IModelBinder -- 对于相对比较简单的类型, 可以使用这种方式

首先新建一个稍微复杂一点的类.

public enum GenderEnum
{
    Female,
    Male,
    Unknow
}

public class ExtInfo
{
    public string QQ { get; set; }
    public string Phone { get; set; }
}

public class User
{   //这里的构造函数, 我创建了一个ExtInfo实例, 否则, 一会这个ExtInfo对象就是空的, 影响我的演示
    public User()
    {
        Extension = new ExtInfo();
    }
    public int Id { get; set; }
    public string Name { get; set; }
    public GenderEnum Gender { get; set; }
    public ExtInfo Extension { get; set; }
}

接下来, 看一下控制器代码

public class ModelController : Controller
{
    public ActionResult Get(User user)
    {
        return View(user);
    }

    public ActionResult Index([ModelBinder(typeof(ModelIndexBinder))]User user)
    {
        return View(user);
    }
}

控制器中需要注意的部分, 我已经标红了. 接下来看我自定义的ModelBinder

public class ModelIndexBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var request = controllerContext.HttpContext.Request;
        User user = new User
        {
            Id = Convert.ToInt32(request.Params["Id"]),
            Name = request.Params["Name"].ToString(),
            Gender = (GenderEnum)Convert.ToInt32(request.Params["Gender"]),
            Extension = new ExtInfo
            {
                QQ = request.Params["QQ"].ToString(),
                Phone = request.Params["Phone"].ToString()
            }
        };
        return user;
    }
}

先看两个方法执行的效果吧.

Index Get

注 : 这里是可以有别的方式来达到目的的, 只需要修改一下url即可:

http://localhost:11620/model/Get?id=1&name=haha&gender=0&Extension.qq=123123123&Extension.phone=12312341234

此处是为了举一个例子, 才这么弄的.

对于比较简单的, 用IModelBinder挺方便的, 但是对于稍微复杂一点的, 可以实现 DefaultModelBinder 来实现.

不过这部分实现就不解析了, 我暂时没用过.

一般对于Url Get请求, 不会很复杂, 只有在使用Post请求的时候, 会遇到比较复杂的处理方式. 但是对于这种复杂的处理, 还有一种比较常用的方式, 就是采用反序列化的方式, 我常用的是 Newtonsoft.

参考:

  MVC扩展

  深入Asp.net Mvc

目录已同步

时间: 2024-10-31 12:39:15

MVC源码分析 - ModelBinder绑定 / 自定义数据绑定的相关文章

asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 在前面的文章中我们曾经涉及到ControllerActionInvoker类GetParameterValue方法中有这么一句代码: ModelBindingContext bindingContext = new ModelBindingContext() { FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix

asp.net mvc源码分析-ModelValidatorProviders 客户端的验证

几年写过asp.net mvc源码分析-ModelValidatorProviders 当时主要是考虑mvc的流程对,客户端的验证也只是简单的提及了一下,现在我们来仔细看一下客户端的验证. 如图所示, 首先我们要知道这里的data-val这些属性是在哪里生成的?可以肯定是在mvc后台生成的, @Html.PasswordFor(m => m.Password) 生成input @Html.ValidationMessageFor(m => m.Password) 生成span 调用层级关系:

ASP.NET MVC 源码分析(一)

ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 AntiForgery:防伪相关 ActionResults:action返回对象相关 ApiExplorer:API描述和元数据相关接口 ApplicationModels:应用程序模型相关,应该是全局的model Areas:地区标签 Filters:大名鼎鼎的过滤器组件 Formatters:

WebForm / MVC 源码分析

ASP.NET WebForm / MVC 源码分析 浏览器 Url:https//localhost:6565/Home/Index ,https//localhost:6565/WebForm1.aspx,请求服务器(构建请求报文,并且将请求报文发送给服务器) 1:服务器(内核模式 Http.sys)对请求报文做基本的处理 2:请求服务器(用户模式,IIS服务器) 2.1:对发送过来的数据做一个检查,如果请求的是静态资源的(.html,jpg,js),那么IIS直接将这些资源返回浏览器 2.

jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on

转:http://www.cnblogs.com/aaronjs/p/3440647.html?winzoom=1 事件(Event)是JavaScript应用跳动的心脏,通过使用JavaScript ,你可以监听特定事件的发生,并规定让某些事件发生以对这些事件做出响应 事件的基础就不重复讲解了,本来是定位源码分析实现的, 所以需要有一定的基础才行 为了下一步更好的理解内部的实现,所以首先得清楚的认识到事件接口的划分 网上资料遍地都是,但是作为一个jQuery系列的源码分析,我还是很有必要在重新

Spring MVC源码分析--视图解析过程

写在最前,本文中的源码是4.2.3版本的源码,针对的是JstlView. 视图解析的过程即DispatcherServlet的doDispatch()方法的调用的processDispatchResult(): 1,processDispatchResult()里,调用DispatchServlet的render()方法: 2,render()方法里,调用DispatchServlet的resolveViewName()方法,把配置文件里注册的全部ViewResolver对象添加进来,寻找合适的

MVC源码分析 - Error过滤器

接 上一篇 内容, 这里先看一下错误处理过滤器. 在看此部分之前, 先看看MVC已经提供的功能吧. 一. MVC 自带功能 1. 配置方法 <system.web> <!--mode : Off / On / RemoteOnly--> <customErrors mode="RemoteOnly"> <error statusCode="404" redirect="~/NotFound/Index"/

asp.net mvc源码分析-Action篇 IModelBinder

我们首先还是看看ReflectedParameterBindingInfo的Binder属性吧: public override IModelBinder Binder {            get {                IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,                    () => String.Format(CultureInfo.Curre

asp.net MVC 源码分析

先上一张图吧 asp.net请求机制的图  by传智播客邹华栋老师 然后是 邹老师添加MVC请求过程的图 其实MVC 实在.netframework上加了一个过滤器  HttpModule 在C:\Windows\Microsoft.NET\Framework\v4.0.30319 下的配置文件里加入的  <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /&