.NET Core開發日誌——Model Binding

来源:https://www.cnblogs.com/kenwoo/archive/2018/08/22/9514817.html
-Advertisement-
Play Games

ASP.NET Core MVC中所提供的Model Binding功能簡單但實用,其主要目的是將請求中包含的數據映射到action的方法參數中。這樣就避免了開發者像在Web Forms時代那樣需要從Request類中手動獲取數據的繁鎖操作,直接提高了開發效率。此功能繼承自ASP.NET MVC,所 ...


ASP.NET Core MVC中所提供的Model Binding功能簡單但實用,其主要目的是將請求中包含的數據映射到action的方法參數中。這樣就避免了開發者像在Web Forms時代那樣需要從Request類中手動獲取數據的繁鎖操作,直接提高了開發效率。此功能繼承自ASP.NET MVC,所以熟悉上一代框架開發的工程師,可以毫無障礙地繼續享有它的便利。

本文想要探索下Model Binding相關的內容,這裡先從源碼中找到其發生的時機與場合。

在ControllerActionInvoker類的Next方法內部,可以看到對BindArgumentsAsync方法的調用,這裡即會對方法的參數進行綁定數據的處理。

private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
{
    switch (next)
    {
        case State.ActionBegin:
            {
                var controllerContext = _controllerContext;

                _cursor.Reset();

                _instance = _cacheEntry.ControllerFactory(controllerContext);

                _arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

                var task = BindArgumentsAsync();
                if (task.Status != TaskStatus.RanToCompletion)
                {
                    next = State.ActionNext;
                    return task;
                }

                goto case State.ActionNext;
            }
        ...
    }
}

此方法又調用了ControllerActionInvokerCacheEntry類中ControllerBinderDelegate屬性,該屬性是一個delegate方法,所以傳入參數後可直接執行處理。

private Task BindArgumentsAsync()
{
    ...
    
    return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments);
}

創建ControllerActionInvokerCacheEntry的地方是前兩篇文章(ControllerAction)中已經提到過的ControllerActionInvokerCache類。

public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext)
{
    ...
    if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
    {
        ...
        var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate(
            _parameterBinder,
            _modelBinderFactory,
            _modelMetadataProvider,
            actionDescriptor);

        var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);

        cacheEntry = new ControllerActionInvokerCacheEntry(
            filterFactoryResult.CacheableFilters, 
            controllerFactory, 
            controllerReleaser,
            propertyBinderFactory,
            objectMethodExecutor,
            actionMethodExecutor);
        cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
    }
    ...

    return (cacheEntry, filters);
}

於是跟蹤至ControllerBinderDelegateProvider類,找到CreateBinderDelegate方法。

public static ControllerBinderDelegate CreateBinderDelegate(
    ParameterBinder parameterBinder,
    IModelBinderFactory modelBinderFactory,
    IModelMetadataProvider modelMetadataProvider,
    ControllerActionDescriptor actionDescriptor)
{
    ...

    var parameterBindingInfo = GetParameterBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor);
    ...

    return Bind;

    async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments)
    {
        var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
        var parameters = actionDescriptor.Parameters;

        for (var i = 0; i < parameters.Count; i++)
        {
            var parameter = parameters[i];
            var bindingInfo = parameterBindingInfo[i];
            var modelMetadata = bindingInfo.ModelMetadata;

            if (!modelMetadata.IsBindingAllowed)
            {
                continue;
            }

            var result = await parameterBinder.BindModelAsync(
                controllerContext,
                bindingInfo.ModelBinder,
                valueProvider,
                parameter,
                modelMetadata,
                value: null);

            if (result.IsModelSet)
            {
                arguments[parameter.Name] = result.Model;
            }
        }

        ...
    }
}

這裡可以看到創建綁定的delegate方法,與之對應的是之前那句_cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments)代碼。

public virtual async Task<ModelBindingResult> BindModelAsync(
    ActionContext actionContext,
    IModelBinder modelBinder,
    IValueProvider valueProvider,
    ParameterDescriptor parameter,
    ModelMetadata metadata,
    object value)
{
    ...

    var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
        actionContext,
        valueProvider,
        metadata,
        parameter.BindingInfo,
        parameter.Name);
    modelBindingContext.Model = value;

    ...

    await modelBinder.BindModelAsync(modelBindingContext);

    ...

    var modelBindingResult = modelBindingContext.Result;

    ...

    return modelBindingResult;
}

到了此處,就是旅程的終點。ParameterBinder類的BindModelAsync中可以找到對IModelBinder類型的BindModelAsync方法的調用。Model Binding這一操作便是在此時此地實現的。

接下來的疑問有兩處,modelBinder是如何產生的,請求中的數據又是怎樣與modelBinder發生聯繫。

ModelBinder

回到ControllerBinderDelegateProvider類的CreateBinderDelegate方法,可以看到其中調用了GetParameterBindingInfo方法。

private static BinderItem[] GetParameterBindingInfo(
    IModelBinderFactory modelBinderFactory,
    IModelMetadataProvider modelMetadataProvider,
    ControllerActionDescriptor actionDescriptor)
{
    var parameters = actionDescriptor.Parameters;
    ...

    var parameterBindingInfo = new BinderItem[parameters.Count];
    for (var i = 0; i < parameters.Count; i++)
    {
        var parameter = parameters[i];

        ...

        var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
        {
            BindingInfo = parameter.BindingInfo,
            Metadata = metadata,
            CacheToken = parameter,
        });

        parameterBindingInfo[i] = new BinderItem(binder, metadata);
    }

    return parameterBindingInfo;
}

這裡的代碼很明顯地說明瞭modelBinder由ModelBinderFactory類的CreateBinder方法創建。

public IModelBinder CreateBinder(ModelBinderFactoryContext context)
{
    ...

    IModelBinder binder;
    if (TryGetCachedBinder(context.Metadata, context.CacheToken, out binder))
    {
        return binder;
    }

    var providerContext = new DefaultModelBinderProviderContext(this, context);
    binder = CreateBinderCoreUncached(providerContext, context.CacheToken);
    ...
    AddToCache(context.Metadata, context.CacheToken, binder);

    return binder;
}

CreateBinder方法內部中如果緩存可以取到值,則從緩存內取值並直接返回,否則通過CreateBinderCoreUncached方法取值。

private IModelBinder CreateBinderCoreUncached(DefaultModelBinderProviderContext providerContext, object token)
{
    ...

    IModelBinder result = null;

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

    ...

    return result;
}

這裡的providers集合又包含哪些數據呢?可以從MvcCoreMvcOptionsSetup類中找到答案。

public void Configure(MvcOptions options)
{
    // Set up ModelBinding
    options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
    options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
    options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options));
    options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
    options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider());
    options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options));
    options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
    options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
    options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());
    options.ModelBinderProviders.Add(new FormFileModelBinderProvider());
    options.ModelBinderProviders.Add(new FormCollectionModelBinderProvider());
    options.ModelBinderProviders.Add(new KeyValuePairModelBinderProvider());
    options.ModelBinderProviders.Add(new DictionaryModelBinderProvider());
    options.ModelBinderProviders.Add(new ArrayModelBinderProvider());
    options.ModelBinderProviders.Add(new CollectionModelBinderProvider());
    options.ModelBinderProviders.Add(new ComplexTypeModelBinderProvider());

    ...
}

以上便是.NET Core MVC中所有被框架支持的ModelBinderProvider。

以一個最典型的FormCollectionModelBinderProvider為例。它以Metadata.ModelType的類型作為判斷依據,如果是IFormCollection類型的話,則返回一個FormCollectionModelBinder對象。

public IModelBinder GetBinder(ModelBinderProviderContext context)
{
    ...

    var modelType = context.Metadata.ModelType;

    ...

    if (modelType == typeof(IFormCollection))
    {
        var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
        return new FormCollectionModelBinder(loggerFactory);
    }

    return null;
}

在CreateBinderCoreUncached方法的迴圈體內部會依次嘗試ModelBinderProvider們是否能創建合適的ModelBinder,一旦能夠生成ModelBinder,則跳出當前迴圈,以這個對象作為返回值。

ValueProvider

有了ModelBinder,還需要有數據才能進行綁定。而為ModelBinder提供數據的是一些ValueProvider。

MvcCoreMvcOptionsSetup類的Configure方法里,再往下找,可以看到ValueProvider們的蹤影。更確切地是與之對應的工廠類們。

public void Configure(MvcOptions options)
{
    ...

    // Set up ValueProviders
    options.ValueProviderFactories.Add(new FormValueProviderFactory());
    options.ValueProviderFactories.Add(new RouteValueProviderFactory());
    options.ValueProviderFactories.Add(new QueryStringValueProviderFactory());
    options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory());

    ...
}

以FormValueProviderFactory為例,看一下其內部:

public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
    ...

    var request = context.ActionContext.HttpContext.Request;
    if (request.HasFormContentType)
    {
        // Allocating a Task only when the body is form data.
        return AddValueProviderAsync(context);
    }

    return Task.CompletedTask;
}

private static async Task AddValueProviderAsync(ValueProviderFactoryContext context)
{
    var request = context.ActionContext.HttpContext.Request;
    var valueProvider = new FormValueProvider(
        BindingSource.Form,
        await request.ReadFormAsync(),
        CultureInfo.CurrentCulture);

    context.ValueProviders.Add(valueProvider);
}

通過CreateValueProviderAsync方法可以得到一個FormValueProvider對象。

而這些ValueProviderFactory所創建的ValueProvider又統一被CompositeValueProvider類的CreateAsync方法聚合成CompositeValueProvider這個集合對象的內部元素。

public static async Task<CompositeValueProvider> CreateAsync(
    ActionContext actionContext,
    IList<IValueProviderFactory> factories)
{
    var valueProviderFactoryContext = new ValueProviderFactoryContext(actionContext);

    for (var i = 0; i < factories.Count; i++)
    {
        var factory = factories[i];
        await factory.CreateValueProviderAsync(valueProviderFactoryContext);
    }

    return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders);
}

再到ControllerBinderDelegateProvider類的CreateBinderDelegate方法中,找到valueProvider創建的起始點。

async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments)
{
    var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
    var parameters = actionDescriptor.Parameters;

    for (var i = 0; i < parameters.Count; i++)
    {
        var parameter = parameters[i];
        var bindingInfo = parameterBindingInfo[i];
        var modelMetadata = bindingInfo.ModelMetadata;

        if (!modelMetadata.IsBindingAllowed)
        {
            continue;
        }

        var result = await parameterBinder.BindModelAsync(
            controllerContext,
            bindingInfo.ModelBinder,
            valueProvider,
            parameter,
            modelMetadata,
            value: null);

        if (result.IsModelSet)
        {
            arguments[parameter.Name] = result.Model;
        }
    }

    ...
}

所得到的valueProvider在ParameterBinder類的BindModelAsync方法里還要再作進一步的處理。先作為參數傳入創建DefaultModelBindingContext的方法:

var modelBindingContext = DefaultModelBindingContext.CreateBindingContext(
    actionContext,
    valueProvider,
    metadata,
    parameter.BindingInfo,
    parameter.Name);

再對ValueProvider作過濾處理:

return new DefaultModelBindingContext()
{
    ActionContext = actionContext,
    BinderModelName = binderModelName,
    BindingSource = bindingSource,
    PropertyFilter = propertyFilterProvider?.PropertyFilter,

    // Because this is the top-level context, FieldName and ModelName should be the same.
    FieldName = binderModelName ?? modelName,
    ModelName = binderModelName ?? modelName,

    IsTopLevelObject = true,
    ModelMetadata = metadata,
    ModelState = actionContext.ModelState,

    OriginalValueProvider = valueProvider,
    ValueProvider = FilterValueProvider(valueProvider, bindingSource),

    ValidationState = new ValidationStateDictionary(),
};

FilterValueProvider方法最終會調用CompositeValueProvider類的Filter方法,以得到所有合適的valueProvider。

public IValueProvider Filter(BindingSource bindingSource)
{
    ...

    var filteredValueProviders = new List<IValueProvider>();
    foreach (var valueProvider in this.OfType<IBindingSourceValueProvider>())
    {
        var result = valueProvider.Filter(bindingSource);
        if (result != null)
        {
            filteredValueProviders.Add(result);
        }
    }

    ...

    return new CompositeValueProvider(filteredValueProviders);
}

那麼當在ModelBinder的BindModelAsync方法里需要獲取數據時,以FloatModelBinder為例:

public Task BindModelAsync(ModelBindingContext bindingContext)
{
    ...

    var modelName = bindingContext.ModelName;
    var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
    
    ...
}

會試圖從已過濾的ValueProvider中獲取值。這時還是利用了CompositeValueProvider類中的方法。

public virtual ValueProviderResult GetValue(string key)
{
    // Performance-sensitive
    // Caching the count is faster for IList<T>
    var itemCount = Items.Count;
    for (var i = 0; i < itemCount; i++)
    {
        var valueProvider = Items[i];
        var result = valueProvider.GetValue(key);
        if (result != ValueProviderResult.None)
        {
            return result;
        }
    }

    return ValueProviderResult.None;
}

這裡的邏輯是從valueProvider集合中逐一嘗試取值,有數據的則直接返回。

這也意味著數據綁定會以FormValueProvider到RouteValueProvider,再到QueryStringValueProvider,最後向JQueryFormValueProvider取值,這一流程執行,中間如果有任何一個能得到數據的話,則不再繼續訪問後面的ValueProvider。當然,前提是這些ValueProvider要不被先前的過濾處理排除在外。

若是還不明白這一順序關係的話,可以回想下從ValueProviderFactories的添加順序,再至ValueProvider集合生成時各個ValueProvider的順序,就比較容易瞭解其中道理了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在 "上一篇" 中我們學習了創建型模式的建造者模式和原型模式。本篇則來學習下結構型模式的適配器模式和橋接模式。 適配器模式 簡介 適配器模式是作為兩個不相容的介面之間的橋梁。這種類型的設計模式屬於結構型模式,它結合了兩個獨立介面的功能。 簡單的來說就是通過某個介面將不相容的兩個類進行相容,俗稱 ...
  • jsp 內置對象 轉發與重定向的比較 重定向和轉發有一個重要的不同:當使用轉發時,JSP容器將使用一個內部的方法來調用目標頁面,新的頁面繼續處理同一個請求,而瀏覽器將不會知道這個過程。 與之相反,重定向方式的含義是第一個頁面通知瀏覽器發送一個新的頁面請求。因為,當你使用重定向時,瀏覽器中所顯示的UR ...
  • 所有文章僅是筆記!都是看網上大神或者視頻學習來的。 一、軟體層面機器碼翻譯(為了吹的牛B:write once run everywhere) 二、記憶體管理:java經久不衰的原因之一 ...
  • Python的錯誤異常在大部分IDE編輯器中則可以直接顯示出來,便於開發人員的調試及修改工作,對初學者也比較友好。 Python中包含錯誤和異常兩種情況,錯誤主要是常見的語法錯誤SyntaxError,並且在錯誤提示中會有倒三角箭頭的修改指示位置;python中的另外一種錯誤提醒叫做異常,指的是在語 ...
  • 好,大家好,我是Simon。接下來的時間由我和大家一起學習VC編程。 那麼我們現在的話就是開始去進入實際的一個程式設計階段。 那麼這個的話是說編寫一個全屏截圖工具。那麼這個工具的話就是我們採用一個cimage的一個類。 然後對它進行一個截圖操作。那麼採用這個類的話,主要是方便,然後再說快捷。在這... ...
  • 值類型、引用類型和泛型 多語言 咱們先不說主題,先說說CLR支持多語言。 .net有個非常強大的特點,那就是跨語言,支持很多語言,比如C#、J#等。先來個圖看一看 看到這個圖,每個語言都有自己的編譯器,通過第一次編譯,編譯成中間文件(dll或是exe文件)。在程式運行的時候,再次編譯把中間文件編譯成 ...
  • asp.net core 2.1 部署IIS(win10) 概述 與ASP.NET時代不同,ASP.NET Core不再是由IIS工作進程(w3wp.exe)托管,而是使用自托管Web伺服器(Kestrel)運行,IIS則是作為反向代理的角色轉發請求到Kestrel不同埠的ASP.NET Core ...
  • 1、Parallel.Invoke 主要用於任務的並行 這個函數的功能和Task有些相似,就是併發執行一系列任務,然後等待所有完成。和Task比起來,省略了Task.WaitAll這一步,自然也缺少了Task的相關管理功能。它有兩種形式: Parallel.Invoke( params Action ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...