asp.net core mvc action参数绑定 从使用到原理 原理篇

源码太多,涉及太广,作为使用者其实不用了解那么多,带着问题去看看源码吧。

Q1: 数据是哪来的?

Q1.1 总的来讲,有四种来源的数据

    1. 来自值提供器的数据,值提供器就是继承了接口IValueProvider
    /// <summary>
    /// Defines the methods that are required for a value provider.
    /// </summary>
    public interface IValueProvider
    {
        /// <summary>
        /// Determines whether the collection contains the specified prefix.
        /// </summary>
        /// <param name="prefix">The prefix to search for.</param>
        /// <returns>true if the collection contains the specified prefix; otherwise, false.</returns>
        bool ContainsPrefix(string prefix);

        /// <summary>
        /// Retrieves a value object using the specified key.
        /// </summary>
        /// <param name="key">The key of the value object to retrieve.</param>
        /// <returns>The value object for the specified key. If the exact key is not found, null.</returns>
        ValueProviderResult GetValue(string key);
    }
    1. 解析器 有些数据不是以键值对表示的数据结构,比如文件,json,xml,类型复杂,必须要解析器才能解析出请求的数据。
      解析器就是实现了IInputFormatter
 /// <summary>
    /// Reads an object from the request body.
    /// </summary>
    public interface IInputFormatter
    {
        /// <summary>
        /// Determines whether this <see cref="IInputFormatter"/> can deserialize an object of the
        /// <paramref name="context"/>'s <see cref="InputFormatterContext.ModelType"/>.
        /// </summary>
        /// <param name="context">The <see cref="InputFormatterContext"/>.</param>
        /// <returns>
        /// <c>true</c> if this <see cref="IInputFormatter"/> can deserialize an object of the
        /// <paramref name="context"/>'s <see cref="InputFormatterContext.ModelType"/>. <c>false</c> otherwise.
        /// </returns>
        bool CanRead(InputFormatterContext context);

        /// <summary>
        /// Reads an object from the request body.
        /// </summary>
        /// <param name="context">The <see cref="InputFormatterContext"/>.</param>
        /// <returns>A <see cref="Task"/> that on completion deserializes the request body.</returns>
        Task<InputFormatterResult> ReadAsync(InputFormatterContext context);
    }
    1. request.Headers 数据源。
      FromHeader,就是从request.Headers中取数据。
    1. 来自DI的数据
      DI容器中的对象可以直接通过[FromService]特性加载到Action中。

Q1.2 四种数据源的被应用位置

    1. 值提供器
  class DecimalModelBinder

  public Task BindModelAsync(ModelBindingContext bindingContext)
  {
        var modelName = bindingContext.ModelName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
  }
    1. 解析器
 class BodyModelBinder

 public async Task BindModelAsync(ModelBindingContext bindingContext)
        {

                var result = await formatter.ReadAsync(formatterContext);

                if (result.HasError)
                {
                }

                if (result.IsModelSet)
                {
                }
                else
                {
                }
        }

Q1.3 什么时候用值提供器,什么时候用解析器

  • 值提供器和解析器都是绑定器的数据源
  • 值提供器和解析器 与 绑定器的关系是 一对多,也就是一个值提供器和解析器可以为多种绑定器提供数据。

值提供器


  • CompositeValueProvider
  • FormValueProvider
  • JQueryFormValueProvider
  • JQueryQueryStringValueProvider
  • QueryStringValueProvider
  • RouteValueProvider

解析器

  • JsonInputFormatter
  • JsonPatchInputFormatter
  • XmlDataContractSerializerInputFormatter
  • XmlSerializerInputFormatter

Q1.4 哪些绑定器的数据源是值提供器

  • ByteArrayModelBinder
  • DecimalModelBinder
  • CollectionModelBinder<TElement>
  • FloatModelBinder
  • DoubleModelBinder
  • SimpleTypeModelBinder

Q1.5 哪些绑定器的数据源是解析器

  • BodyModelBinder

Q1.6 怎么确定该用哪个绑定器

关键代码

             //这里会获得一个绑定器
            IModelBinder result = null;

            for (var i = 0; i < _providers.Length; i++)
            {
                var provider = _providers[i];
                result = provider.GetBinder(providerContext);
                if (result != null)
                {
                    break;
                }
            }


//这里是BodyModelBinder GetBinder的逻辑
public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            //是否BindingSource 是BindingSource.Body
            if (context.BindingInfo.BindingSource != null &&
                context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
            {
                if (_formatters.Count == 0)
                {
                    throw new InvalidOperationException(Resources.FormatInputFormattersAreRequired(
                        typeof(MvcOptions).FullName,
                        nameof(MvcOptions.InputFormatters),
                        typeof(IInputFormatter).FullName));
                }

                return new BodyModelBinder(_formatters, _readerFactory, _loggerFactory, _options);
            }

            return null;
        }

//这里是SimpleTypeModelBinderProvider GetBinder的逻辑
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (!context.Metadata.IsComplexType)//是否是复杂类型
            {
                var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
                return new SimpleTypeModelBinder(context.Metadata.ModelType, loggerFactory);
            }

            return null;
        }

不同的绑定器有不同的判断条件,总的讲逻辑就是,根据ModelBinderProviderContext 判断我能不能行。


  • FromBodyAttribute
  • FromFormAttribute
  • FromHeaderAttribute
  • FromQueryAttribute
  • FromRouteAttribute
  • FromServicesAttribute

Q1.7 6大From特性的作用就是影响绑定器的 “判断条件”

每个From特性有不同的BindingSource,绑定器提供器构建绑定器的时候,会判断BindingSource。

eg:

    public class FromHeaderAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
    {
        /// <inheritdoc />
        public BindingSource BindingSource => BindingSource.Header;

        /// <inheritdoc />
        public string Name { get; set; }
    }
       public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            var bindingInfo = context.BindingInfo;
            if (bindingInfo.BindingSource == null ||
                !bindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Header))
            {
                return null;
            }

            return new HeaderModelBinder(loggerFactory);
            }
}

Q1.8 为啥application/json提交的时候,不加FromBody绑定不了Model

1. 简单类型,默认的绑定器是什么?

action

        public IActionResult Index(string a)
        {
            return View();
        }

获取绑定器

QQ截图20190228132304.png

2. 绑定Model的时候,默认的绑定器是什么?

action 定义

    public class HomeController : Controller
    {
        public IActionResult Index(Test a)
        {
            return View();
        }
    }
    public class Test
    {
        public string a { get; set; }
    }

顶层复杂类型绑定器,绑定器是一个树形结构

QQ截图20190228135851.png

复杂类型绑定器中根据属性描述,生成属性对应的绑定器。绑定器是一个树形结构。

public class ComplexTypeModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
            {
                var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
                for (var i = 0; i < context.Metadata.Properties.Count; i++)
                {
                    //根据属性描述,生成属性对应的绑定器。绑定器是一个树形结构。
                    var property = context.Metadata.Properties[i];
                    propertyBinders.Add(property, context.CreateBinder(property));
                }

                var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
                return new ComplexTypeModelBinder(propertyBinders, loggerFactory);
            }

            return null;
        }
    }

绑定属性的依然是简单绑定器

QQ截图20190228141052.png

复杂类型绑定器中的属性绑定器有哪几种

  • ByteArrayModelBinder
  • DecimalModelBinder
  • CollectionModelBinder<TElement>
  • FloatModelBinder
  • DoubleModelBinder
  • SimpleTypeModelBinder
  • ComplexTypeModelBinder

就是上面提到的“绑定器的数据源是值提供器”


也就是说,默认情况下,绑定的数据来源都是值提供器。而 application/json是需要解析器解析数据,数据源是解析器,所以,默认情况下,application/json的json数据是无法绑定到model上的。

3. FromModel 特性设置后,为啥可以绑定application/json的数据了

    public class HomeController : Controller
    {
        public IActionResult Index([FromBody]Test a)
        {
            return View();
        }
    }
    public class Test
    {
        public string a { get; set; }
    }

设置后,默认绑定器变成了


QQ截图20190228142655.png

为啥默认绑定器变成了BodyModelBinder

看看BodyModelBinderProvider中的GetBinder的逻辑就知道了

public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (context.BindingInfo.BindingSource != null &&
                context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
            {
                if (_formatters.Count == 0)
                {
                    throw new InvalidOperationException(Resources.FormatInputFormattersAreRequired(
                        typeof(MvcOptions).FullName,
                        nameof(MvcOptions.InputFormatters),
                        typeof(IInputFormatter).FullName));
                }

                return new BodyModelBinder(_formatters, _readerFactory, _loggerFactory, _options);
            }

            return null;
        }
        if (context.BindingInfo.BindingSource != null && context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body))
  • 这个判断中的context.BindingInfo.BindingSource的值是读取的FromBody中的值
  • FromBody中的BindingSource 是 BindingSource.Body

所以默认绑定器变成了BodyModelBinder


特性详解:特性配置后的影响

  • FromBodyAttribute 影响绑定器,设置了FromBody会导致默认绑定器变成了BodyModelBinder
  • FromHeaderAttribute影响绑定器,设置了FromHeader会导致默认绑定器变成了HeaderModelBinder,HeaderModelBinder有个比较特殊的地方在于他不是从解析器和值提供器中获取数据,而是从request.Headers中获取数据。
  • FromFormAttribute 影响值提供器,导致绑定器只能从FormValueProvider JQueryFormValueProvider中获取
  • FromQueryAttribute 影响值提供器,导致绑定器只能从JQueryQueryStringValueProvider QueryStringValueProvider中获取
  • FromRouteAttribute 影响值提供器,导致绑定器只能从RouteValueProvider中获取
  • FromServicesAttribute 内部的获取方式是:HttpContext.RequestServices.GetService(),手动从容器中抓取的对象。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351