C# .NET Web API 如何自訂 ModelBinder

来源:https://www.cnblogs.com/KingJaja/archive/2019/03/17/10549500.html
-Advertisement-
Play Games

各位好!這次要來替大家介紹的是如何在 .NET Web API 中自訂一個 ModelBinder 透過自定義的 ModelBinder 我們可以很簡單的將 QueryString 傳過來的參數綁定成我們設計好的 Complex Model 。 為什麼需要自行定義一個 ModelBinder 呢? ...


各位好!這次要來替大家介紹的是如何在 .NET  Web API 中自訂一個 ModelBinder

透過自定義的 ModelBinder 我們可以很簡單的將 QueryString 傳過來的參數綁定成我們設計好的 Complex Model 。

為什麼需要自行定義一個 ModelBinder 呢? 一個主要的原因是,如果我們的 HTTP GET API 需要多個參數的話,一般來說是可以直接在函數這裡定義好對應的參數。

如下圖

image

但如果參數過多的話,其實會造成閱讀不容易以及使用上十分的不方便。

比如說你可能會這樣子將收到的參數重新組合成相關方法會用到的 Model

image

但這裡還是要提一下,如果你的 Model 是 Simple Model 的話 (也就是你的 Model 並沒有再利用其它 Class 做為 Property ) ,.NET Web API 是可以很聰明的替你將參數做綁定的

比如說像這樣的 Model , .NET Web API 是可以替你將參數做綁定的

image

image

但如果是像下麵這樣的 Model ,就會需要用到我們今天提的自定義的 ModelBinder 來

處理了

image

image

那麼要如何建立我們自已的 ModelBinder 呢?

首先需要瞭解的概念是在 Web API 的 ModelBinder 裡會分為三塊,分別是Binder 、ValueProvider 、ProviderFactory 。而 Web API 在處理的流程則是會先建立好 ProviderFactory ,並在 Binder 呼叫 ValueProvider 取值時,呼叫對應的 ValueProvider 進行取值的動作。

ASP.NET Web API 中繫結的參數

詳細的觀念也可參考保哥這篇

ModelBinder 與 ValueProvider 的用途

接下來我們就一步一步將我們的 ModelBinder 建立起來吧!

第一步:建立 ProviderFactory

建立 ProviderFacotry 這一步非常的簡單,其實 PrivderFacotry 要做的事就是在裡頭去將對應的 ValueProvider 建立起來並回傳。詳細的程式碼如下

public class ComplexProviderFactory : ValueProviderFactory
   {
       public override IValueProvider GetValueProvider(HttpActionContext actionContext)
       {
           return new ComplexProvider(actionContext);
       }
   }

這裡可以看到我們直接繼承一個 ValueProviderFactory 然後在實作裡面回傳一個我們定義好的 ValueProvider

是不是很簡單呢,接下來我們就要來建立我們的 ValueProvider 了

第二步:建立 ValueProvider

在建立 ValueProvider  時,我們一樣也會繼承一個 IValueProvider 並去實作他

在這裡有幾個主要要實作的地方,一個是建構子的部分,從 ComplexProviderFactory 的程式碼我們可以看到,我們在回傳一個 ValueProvider 的時候,其實是有傳入一個 HttpActionContext 的參數。所以在我們的 ValueProvider 也要建對應的參數(黑色粗體處)

再來是 ValueProvider 要如何取值呢? 這裡就要看我們 GetValue 這個方法如何實作了,我這裡實作的方式有一部分是參考 .NET 官方提供的 Source Code

DictionaryValueProvider

首先,因為我希望這個 ModelBinder 能夠泛用,所以我在 ValueProvider  這裡將傳入的 QueryString 以 Key,Value 的形式直接存放到 Dictionary ,讓 ModelBinder 在取值時是直接取回一組 Dictionary ,並做後續的處理。在這裡 ContainsPrefix 我們並沒有用到故先略過,完整的程式碼如下

public class ComplexProvider : IValueProvider
    {
        private Dictionary<string, object> _vals;

       //取值器只做取值的動作
        public ComplexProvider(HttpActionContext actionContext)
        {
            _vals = new Dictionary<string, object>();
            foreach (var pair in actionContext.Request.GetQueryNameValuePairs())
            {
                _vals.Add(pair.Key, pair.Value);
            }
        }

       public bool ContainsPrefix(string prefix)
        {
            return true;
        }

       public ValueProviderResult GetValue(string key)
        {
            if (_vals.Count > 0)
            {
                return new ValueProviderResult(_vals, "", CultureInfo.InvariantCulture);
            }
            return null;
        }
    }

 

第三步:就是我們的 ModelBinder了

一樣 ModelBinder 我們這裡也需要繼承對應的介面 IModelBinder 並實作 BindModel 這個方法,在實作的時候我們比較常會用到的就是 bindingContext 了, bindingContext 是在 Model 做繫結的過程中,讓我們可以去得知目前繫結參數的名稱以及取用相對應的 Value ,最後還可以去設定繫結的結果。

先前我們有提到想要實作一個泛用的 ModelBinder ,在這裡我們先來看看黃底的程式碼

在黃底程式碼的第一行我們先取得這次繫結的參數類型,接下來在第二行我們就直接取回我們在 ValueProvider 建立的 Dictionary ,這裡有一個有趣的點是,依照我觀看 .NET 官方的實作,一般來說是會透過 .GetValue(bindingContext.ModelName) 搭配不同的參數名稱 (ModelName) 取得對應的值。但我這裡為了簡單化,所以在 ValueProvider 中是不管參數名稱的。

接下來在粗體字處,我們就簡單的判斷目前有沒有取到 ValueProvider 建立的 Dictionary

並透過反射來建立好我們完整的 Model ,最後將結果設定回 bindingContext.Model 即可

詳細的程式碼如下:

public class ComplexBinder : IModelBinder
     {
         public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
         {
             if (actionContext == null) { throw new ArgumentException("action context is null"); }
            
var target = bindingContext.ModelType;
             var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

             if (val == null)
             {
                 return false;
             }
             if (val.RawValue != null)
             {
                 var result = Activator.CreateInstance(target);
                 var pros = result.GetType().GetProperties();
                 var valDic = val.RawValue as Dictionary<string, object>;
                 foreach (var propertyInfo in pros)
                 {
                     object propertyVal;
                     if (valDic.TryGetValue(propertyInfo.Name, out propertyVal))
                     {
                        
                        propertyInfo.SetValue(result, Convert.ChangeType(propertyVal, propertyInfo.PropertyType));
                     }
                     else
                     {
                         if (!propertyInfo.PropertyType.IsPrimitive && propertyInfo.PropertyType != typeof(string))
                         {
                             var childInstance = Activator.CreateInstance(propertyInfo.PropertyType);
                             var childPros = childInstance.GetType().GetProperties();
                             object childPropVal;
                             foreach (var childPro in childPros)
                             {
                                 if (valDic.TryGetValue(childPro.Name, out childPropVal))
                                 {
                                     childPro.SetValue(childInstance, Convert.ChangeType(childPropVal, childPro.PropertyType));
                                 }
                             }
                             propertyInfo.SetValue(result, Convert.ChangeType(childInstance, propertyInfo.PropertyType));
                         }
                     }

                }
                 bindingContext.Model = result;
             }
             return true;
         }
     }

到此我們已經差不多都完成囉! 最後一步只需要將我們寫好的 ValueProviderFactory 做註冊就行了。

在 Web API 專案中找到我們的 WebApiConfig.cs 設定檔,將我們剛剛做好的 Factory 註冊進去即可。設定的方式如下(粗體字處)

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 設定和服務
            config.Services.Add(typeof(ValueProviderFactory), new ComplexProviderFactory());
            //var provider = new SimpleModelBinderProvider(typeof(Student), new ComplexBinder());
            //config.Services.Insert(typeof(ModelBinderProvider),0,provider);
            // Web API 路由
            config.MapHttpAttributeRoutes();

           config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
          
        }
    }

 

最後來看一下我們的結果

image

從上圖可以看到,我們利用 ModelBinder 指定了我們剛剛寫好的 Binder 後,在參數繫結上,Course 這個屬性已經正確的被初始化並且指定了我們傳入的名稱給 CourseName

以上就是如何自定義一個 ModelBinder ,是不是很簡單呢?

下次如果發現參數實在太多又不適合通通放在同一個 Model 中,可以試試看這樣的方法喔!

我們下次見

 


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

-Advertisement-
Play Games
更多相關文章
  • 基於Cmake、QT Creator、Visual Studio的C++項目構建、開發、編譯是初學者必要的工具,本文主要介紹這些工具的下載與安裝,以及C++及Qt項目構建,主要包括 1.基於VS構建Qt項目;2.基於Qt Creater構建,在VS中開發Qt Creater生成的項目;3.基於Cma... ...
  • 概述 在PHP中有一種代碼復用的技術, 因為單繼承的問題, 有些公共方法無法在父類中寫出, 而 Trait可以應對這種情況, 它可以定義一些復用的方法, 然後在你需要使用的類中將其引入即可. 剛開始的時候給我的感覺就是將trait代碼塊直接拿到類中的意思, 但後來我發現, 我太天真了. PHP中的T ...
  • / 【例11 10】建立一個學生成績信息(包括學號、姓名、成績)的單向鏈表,學生數據按學號由小到大順序排列,要求實現對成績信息的插入、修改、刪除和遍歷操作。 / / 用鏈表實現學生成績信息的管理 / include include include struct stud_node{ int num; ...
  • 6 2 遞歸求階乘和 (10 分) 本題要求實現一個計算非負整數階乘的簡單函數,並利用該函數求 1!+2!+3!+...+n! 的值。 函數介面定義: double fact( int n ); double factsum( int n ); 函數fact應返回n的階乘,建議用遞歸實現。函數fac ...
  • 目錄 1、什麼是日誌? 簡單的說,日誌就是記錄程式的運行軌跡,方便查找關鍵信息,也方便快速定位解決問題。我們 Java 程式員在開發項目時都是依賴 Eclipse/ Idea 等開發工具的 Debug 調試功能來跟蹤解決 Bug,在開發環境可以這麼做,但項目發佈到了測試、生產環境呢?你有可能會說可以 ...
  • 本人經過2周的學習,成功搭建了認證伺服器,資源伺服器和客戶端 。下麵是本人對 oauth2的理解,以及spring-security的使用,如果理解錯誤的地方,還望指正。 現在代碼有點凌亂,過段時間會放到github上面的,本人會在代碼中添加詳細註釋,供學習交流使用 理解 1.認證伺服器 如抖音可以 ...
  • 參考資料 [1] @毛星雲【《Effective C 》提煉總結】 https://zhuanlan.zhihu.com/p/24553860 [2] 《C 捷徑教程》 [3] @flashyiyi【C NoGCString】 https://zhuanlan.zhihu.com/p/3552560 ...
  • 目前.NET Core 3.0的版本為.NET Core 3.0 Preview 3,對應ASP.NET Core 3.0 Preview 3。 ASP.NET Core 3.0 之後將不再支持.NET Framework,只運行在.NET Core 上面。 ASP.NET Core 3.0 現在已 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...