Asp.Net Web API 2第十六課——Parameter Binding in ASP.NET W

来源:http://www.cnblogs.com/wangxiaobingya/archive/2017/02/17/6410075.html
-Advertisement-
Play Games

導航 閱讀本文之前,您也可以到Asp.Net Web API 2 系列導航進行查看 http://www.cnblogs.com/aehyok/p/3446289.html。 本文主要來講解以下內容: 〇、前言 Ⅰ、Using[FromUri] Ⅱ、Using[FromBody] Ⅲ、Type Co ...


導航

閱讀本文之前,您也可以到Asp.Net Web API 2 系列導航進行查看 http://www.cnblogs.com/aehyok/p/3446289.html

本文主要來講解以下內容:

  〇、前言

  Ⅰ、Using[FromUri]

  Ⅱ、Using[FromBody]

  Ⅲ、Type Converters

  Ⅳ、Model Binders

  Ⅴ、Value Providers

  Ⅵ、HttpParameterBinding

  Ⅶ、IActionValueBinder

前言

閱讀本文之前,您也可以到Asp.Net Web API 2 系列導航進行查看 http://www.cnblogs.com/aehyok/p/3446289.html

當Web API在一個控制器中調用一個方法的時候,它必須為參數設定值,這個過程就叫做綁定。這篇文章描述Web API如何綁定參數,以及如何自定義綁定過程。

  預設情況,Web API使用如下規則來綁定參數:

  1、如果參數一個"簡單"類型,那麼Web API試圖從URI中獲取值。簡單的類型包含.NET的基元類型(int,bool,double等等)加上TimeSpanDateTimeGuiddecimal, and string,再加上任何的能從字元串進行轉換的類型。

  2、對於複雜類型,Web API試圖用媒體格式化器http://www.cnblogs.com/aehyok/p/3460164.html從消息體中來讀取值。

例如,這是一個典型的Web API控制器方法:

HttpResponseMessage Put(int id, Product item) { ... }

這個“id”參數是一個“簡單”類型,因此Web API試圖從請求的URI中獲取參數值,這個“item”參數是一個複雜類型,因此Web API試圖使用一個媒體格式化器從請求消息體中來讀取參數值。

為了從URI中獲取值,Web API會查看路由數據和URI查詢字元串。這個路由數據被填充是在路由系統解析URI並匹配它到路由的時候。對於路由的更多信息: http://www.cnblogs.com/aehyok/p/3444710.html

在這篇文章剩餘的部分我將來展示如何自定義模型綁定的過程。對於複雜類型,要儘可能的使用媒體格式化器來處理。HTTP的一個主要原則就是資源被髮送在消息體中,使用內容協商http://www.cnblogs.com/aehyok/p/3481265.html來指定資源的展現。媒體格式化器被設計就是為了這個目的。

Using [FromUri]

 為了更好的讓Web API從URI中讀取複雜類型,添加【FormUri】屬性到參數上。下麵的例子定義了一個GeoPoint 的類型,緊接著一個控制器方法從URI中獲得這個GetPoint參數。

public class GeoPoint
{
    public double Latitude { get; set; } 
    public double Longitude { get; set; }
}

public ValuesController : ApiController
{
    public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}

這個客戶端可以把Latitude和Longitude的值放進查詢字元串中。Web API將用這兩個參數來構造一個GeoPoint參數。例如:

http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989

Using [FromBody]

 為了更好的讓Web API 從消息體中讀取一個簡單類型。添加【FromBody】屬性到參數上:

public HttpResponseMessage Post([FromBody] string name) { ... }

在這個例子中,Web API將使用媒體格式化器來讀取消息體中的name值。這是一個客戶端請求的例子:

POST http://localhost:5076/api/values HTTP/1.1
User-Agent: Fiddler
Host: localhost:5076
Content-Type: application/json
Content-Length: 7

"Alice"

當一個參數擁有【FromBody】屬性的時候,Web API使用Content-Type header去選擇一個格式化器。在這個例子中Content-Type是“application/json”,這個請求體是一個原始的Json字元串(而不是Json對象)。

至多一個參數被允許從消息體中讀取值。因此如下這段將不會起作用:

public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

對於這個規則的原因就是這個請求體被存儲在只能被讀取一次的非緩衝流中。

Type Converters

 你也可以讓Web API對待一個class像一個簡單的類型,通過創建一個TypeConverter 並提供一個字元串的轉換。

接下來的代碼展示了用一個GeoPoint類來表示一個地理位置。添加一個 TypeConverter來把字元串轉換為GeoPoint實例。這個GeoPoint類用了一個TypeConverter屬性來修飾,並且指定了這個TypeConverter的類型。

    [TypeConverter(typeof(GeoPointConverter))]
    public class GeoPoint
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
        public static bool TryParse(string s, out GeoPoint result)
        {
            result = null;

            var parts = s.Split(',');
            if (parts.Length != 2)
            {
                return false;
            }

            double latitude, longitude;
            if (double.TryParse(parts[0], out latitude) &&
                double.TryParse(parts[1], out longitude))
            {
                result = new GeoPoint() { Longitude = longitude, Latitude = latitude };
                return true;
            }
            return false;
        }
    }
    public class GeoPointConverter:TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context,
            CultureInfo culture, object value)
        {
            if (value is string)
            {
                GeoPoint point;
                if (GeoPoint.TryParse((string)value, out point))
                {
                    return point;
                }
            }
            return base.ConvertFrom(context, culture, value);
        }
    }

現在Web API可以把GeoPoint看做是一個簡單類型。意味著它將可以從URI中綁定GeoPoint參數。在參數上你不需要添加【FromUri】屬性。

客戶端可以調用這個方法,例如如下的URI:

http://localhost/api/values/?location=47.678558,-122.130989

Model Binders

 比一個type converter更靈活的選項是創建一個自定義的模型綁定。有了模型綁定,你可以使用像HTTP請求,Action描述,以及路由數據中的原始值。

為了創建一個Model Binder,你需要實現IModelBinder 介面,這個介面中定義了一個方法,BindModel:

bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext);

接下來為GeoPoint對象來創建一個Model Binder。

    public class GeoPointModelBinder:IModelBinder
    {
        private static ConcurrentDictionary<string, GeoPoint> _locations
    = new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);

        static GeoPointModelBinder()
        {
            _locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
            _locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
            _locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
        }
        public bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(GeoPoint))
            {
                return false;
            }

            ValueProviderResult val = bindingContext.ValueProvider.GetValue(
                bindingContext.ModelName);
            if (val == null)
            {
                return false;
            }

            string key = val.RawValue as string;
            if (key == null)
            {
                bindingContext.ModelState.AddModelError(
                    bindingContext.ModelName, "Wrong value type");
                return false;
            }

            GeoPoint result;
            if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
            {
                bindingContext.Model = result;
                return true;
            }

            bindingContext.ModelState.AddModelError(
                bindingContext.ModelName, "Cannot convert value to Location");
            return false;
        }
    }

一個model binder從一個value provider中獲得原始的錄入值。這個設計分為兩個獨立的方法:

1、這個value provider接收到一個HTTP請求,並且填充一個鍵值對的字典。

2、然後model binder使用鍵值對的字典來填充model。

Web API中預設的value provider從路由數據和查詢字元串中獲取值。例如,這樣一個URI:

http://localhost/api/values/1?location=48,-122

value provider將會創建如下的鍵值對:

id = "1"
location = "48,122"

我們假設使用的是預設的路由模版。

被綁定的參數的名稱被存儲在ModelBindingContext.ModelName這個屬性上。model binder在字典中尋找一個鍵的值。如果這個值存在,並且也能被轉換成GeoPoint,這個model binder將分配這個值到ModelBindingContext.Model屬性。

註意:Model Binder不會限制一個簡單類型的轉換,這個model binder首先會在已知位置的列表中尋找,如果查找失敗,將會使用 type converter。

Setting the Model Binder

這裡有幾種方法去設置Model Binder.首先你可以添加一個【Model Binder】屬性到參數上。

public HttpResponseMessage Get([ModelBinder(typeof(GeoPointModelBinder))] GeoPoint location)

你也能添加【Model Binder】屬性到這個參數類型上。Web API將指定這個model binder到這個類型的所有參數上。

[ModelBinder(typeof(GeoPointModelBinder))]
public class GeoPoint
{
    // ....
}

最後,你能添加一個model-binder的提供者到HttpConfiguration。一個model-binder的提供者就是一個簡單的工廠類,它可以創建一個model binder。你能創建一個provider通過派生自 ModelBinderProvider類。無論怎樣,如果你的model binder處理單個類型,它是比較容易的通過使用已經創建的SimpleModelBinderProvider

 

接下來的代碼展示如何啟用他們:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var provider = new SimpleModelBinderProvider(
            typeof(GeoPoint), new GeoPointModelBinder());
        config.Services.Insert(typeof(ModelBinderProvider), 0, provider);

        // ...
    }
}

有了一個model-binding provider,你仍然需要添加一個[ModelBinder] 屬性到參數上,它的目的就是告知Web API應該是用model binder,而不是使用媒體格式化器。但是現在你不需要在屬性上指定這個model binder的類型。

public HttpResponseMessage Get([ModelBinder] GeoPoint location) { ... }

Value Providers

 我前面提到過一個model binder是從value provider中獲取值。寫一個自定義的value provider,實現這個IValueProvider 介面。這個例子是從請求的cookie中獲取值。

    public class CookieValueProvider:IValueProvider
    {
        private Dictionary<string, string> _values;

        public CookieValueProvider(HttpActionContext actionContext)
        {
            if (actionContext == null)
            {
                throw new ArgumentNullException("actionContext");
            }

            _values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (var cookie in actionContext.Request.Headers.GetCookies())
            {
                foreach (CookieState state in cookie.Cookies)
                {
                    _values[state.Name] = state.Value;
                }
            }
        }

        public bool ContainsPrefix(string prefix)
        {
            return _values.Keys.Contains(prefix);
        }

        public ValueProviderResult GetValue(string key)
        {
            string value;
            if (_values.TryGetValue(key, out value))
            {
                return new ValueProviderResult(value, value, CultureInfo.InvariantCulture);
            }
            return null;
        }
    }

你也需要創建一個value provider 工廠通過繼承自ValueProviderFactory 。

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

添加value provider 工廠到HttpConfiguration ,代碼如下:

public static void Register(HttpConfiguration config)
{
    config.Services.Add(typeof(ValueProviderFactory), new CookieValueProviderFactory());

    // ...
}

 Web API組合了所有的value provider,因此當一個model binder調用ValueProvider.GetValue,這個model binder接收從第一個value provider能提供它的值。

或者,通過使用ValueProvider屬性你也能在參數級別上設置value provider 工廠,代碼如下:

public HttpResponseMessage Get(
    [ValueProvider(typeof(CookieValueProviderFactory))] GeoPoint location)

這將告訴Web API模型綁定使用指定的value  provider 工廠,不要用任何另外的被註冊的value  provider。

HttpParameterBinding

 模型綁定是一個更加普遍機制的特性實例。如果你看到這個 [ModelBinder] 屬性,你將明白它是派生自ParameterBindingAttribute 抽象類。這個類定義了一個單獨的方法,並返回一個HttpParameterBinding 對象:

public abstract class ParameterBindingAttribute : Attribute
{
    public abstract HttpParameterBinding GetBinding(HttpParameterDescriptor parameter);
}

HttpParameterBinding 對象負責綁定一個值到一個參數。在[ModelBinder]修飾的情況下,這個屬性返回一個HttpParameterBinding 的實現,它使用了一個IModelBinder 去展現真實的binding。你也可以實現自己的HttpParameterBinding

 例如,假定你想從請求的if-match 和 if-none-match 的header中獲取ETags。開始我們將定義一個class來代替ETags 。

public class ETag
{
    public string Tag { get; set; }
}

我們也來定義一個枚舉指明是否從if-match 和 if-none-match 的header中獲得了ETag 。

public enum ETagMatch
{
    IfMatch,
    IfNoneMatch
}

這裡是HttpParameterBinding ,獲取該 ETag 從所需的標頭並將其綁定到類型的參數的 ETag:

public class ETagParameterBinding : HttpParameterBinding
    {
        ETagMatch _match;

        public ETagParameterBinding(HttpParameterDescriptor parameter, ETagMatch match)
            : base(parameter)
        {
            _match = match;
        }

        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
            HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            EntityTagHeaderValue etagHeader = null;
            switch (_match)
            {
                case ETagMatch.IfNoneMatch:
                    etagHeader = actionContext.Request.Headers.IfNoneMatch.FirstOrDefault();
                    break;

                case ETagMatch.IfMatch:
                    etagHeader = actionContext.Request.Headers.IfMatch.FirstOrDefault();
                    break;
            }

            ETag etag = null;
            if (etagHeader != null)
            {
                etag = new ETag { Tag = etagHeader.Tag };
            }
            actionContext.ActionArguments[Descriptor.ParameterName] = etag;

            var tsc = new TaskCompletionSource<object>();
            tsc.SetResult(null);
            return tsc.Task;
        }
    }

ExecuteBindingAsync 方法來處理綁定。在此方法中,添加參數值到ActionArgument 字典中併在HttpActionContext中。

如果你的ExecuteBindingAsync 方法讀取請求消息體。重寫這個WillReadBody 屬性去返回true。這個消息體可能是只能讀一次的未緩衝的流。因此Web API施行了一個規則至多有一個綁定可以讀取消息體。

應用一個自定義的HttpParameterBinding,你能定義一個派生自ParameterBindingAttribute 的屬性,對於ETagParameterBinding,我們將定義兩個屬性:一個是對於if-match  Header的,一個是對於if-none-match Header。都派生自一個抽象的基類。

public abstract class ETagMatchAttribute : ParameterBindingAttribute
{
    private ETagMatch _match;

    public ETagMatchAttribute(ETagMatch match)
    {
        _match = match;
    }

    public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
    {
        if (parameter.ParameterType == typeof(ETag))
        {
            return new ETagParameterBinding(parameter, _match);
        }
        return parameter.BindAsError("Wrong parameter type");
    }
}

public class IfMatchAttribute : ETagMatchAttribute
{
    public IfMatchAttribute()
        : base(ETagMatch.IfMatch)
    {
    }
}

public class IfNoneMatchAttribute : ETagMatchAttribute
{
    public IfNoneMatchAttribute()
        : base(ETagMatch.IfNoneMatch)
    {
    }
}

這是一個控制器方法 使用了[IfNoneMatch] 屬性。

public HttpResponseMessage Get([IfNoneMatch] ETag etag) { ... }

除了ParameterBindingAttribute 之外,對於添加一個自定義的HttpParameterBinding 有另外一個掛鉤。在HttpConfiguration 對象上,ParameterBindingRules 是一個匿名方法類型(HttpParameterDescriptor -> HttpParameterBinding)的集合。例如,你可以添加一個規則:在Get請求方法中任何ETag 參數使用ETagParameterBinding with if-none-match。

config.ParameterBindingRules.Add(p =>
{
    if (p.ParameterType == typeof(ETag) && 
        p.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get))
    {
        return new ETagParameterBinding(p, ETagMatch.IfNoneMatch);
    }
    else
    {
        return null;
    }
});

這個方法對於綁定不適用的參數應該返回null。

IActionValueBinder

 整個參數綁定的過程被一個可插拔的服務控制,IActionValueBinderIActionValueBinder 的預設實現將執行以下操作:

  1、在參數上查看ParameterBindingAttribute ,這包括 [FromBody][FromUri], 和[ModelBinder], 或者是自定義的屬性。

  2、否則,查看一個函數的HttpConfiguration.ParameterBindingRules ,它返回一個非null的HttpParameterBinding

  3、否則,使用我之前描述的預設規則。

    ①、如果參數類型是一個“簡單”的,或者擁有一個type converter,將會從URI進行綁定。它等價於在參數上添加[FromUri]屬性。

    ②、否則,試圖從消息體中讀取參數,這等價於在參數上添加[FromBody]屬性。

 如果你需要,你可以用一個自定義的實現來替代整個IActionValueBinder 。

總結

本文主要來講解參數綁定,但是通過上面也可以看出涉及到的知識點還是蠻多的,但是都是很實用的,例子也比較清晰。但是還是需要在項目中進行應用,才能更好的學習和掌握參數綁定的環節。

本文的參考鏈接為http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api 

參考頁面:

http://www.yuanjiaocheng.net/webapi/create-crud-api-1-delete.html

http://www.yuanjiaocheng.net/webapi/Consume-web-api.html

http://www.yuanjiaocheng.net/webapi/mvc-consume-webapi-get.html

http://www.yuanjiaocheng.net/webapi/mvc-consume-webapi-post.html

http://www.yuanjiaocheng.net/webapi/mvc-consume-webapi-put.html

it學習資料


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

-Advertisement-
Play Games
更多相關文章
  • 電子面單開發流程 服務程式 生成單號,改變三個表: 抓類型表,抓取未處理的充值記錄->根據類型取面單類型表的最大單號(判斷是否在起始和結束之間,並設置郵件或者簡訊預警 )->根據單號規則和充值的數量生成單號明細入單號明細表,改變充值記錄的處理狀態 改變面單類型最大單號(事務提交)。 ...
  • 經過本周的努力,昨晚終於完成OSS.Social微信項目的標準庫支持,當前項目你已經可以同時在.net framework和.net core 中進行調用,調用方法也發生了部分變化,這裡我簡單分享下,主要包含下邊幾個部分: · 移植後的變化 · 和OSS.Common,OSS.Http關係 · 非同步 ...
  • #define aaa //放在代碼最前面 int a = 1; a = a + 1; #if !aaa {a = a + 1;}#elif !aaaaa {a=a+11;}#endif Console.WriteLine(a); Console.ReadKey(); 據說與版本有關 ,#undef ...
  • 為什麼叫T4?因為簡寫為4個T。 T4(Text Template Transformation Toolkit)是微軟官方在VisualStudio 2008中開始使用的代碼生成引擎。在 Visual Studio 中,“T4 文本模板”是由一些文本塊和控制邏輯組成的混合模板,它可以生成文本文件。 ...
  • 在大型網站系統中,為了提高系統訪問性能,往往會把一些不經常變得內容發佈成靜態頁,比如商城的產品詳情頁,新聞詳情頁,這些信息一旦發佈後,變化的頻率不會很高,如果還採用動態輸出的方式進行處理的話,肯定會給伺服器造成很大的資源浪費。但是我們又不能針對這些內容都獨立製作靜態頁,所以我們可以在系統中利用偽靜態 ...
  • Static Using static using聲明允許直接調用靜態方法而不需要指定類名: C# 5 C# 6 Expression-Bodied Methods 使用expression-bodied methods,一個只有一句statement的函數可以使用lambda寫法。 C# 5 C# ...
  • 5.5 HTTP Cookies in ASP.NET Web API 5.5 ASP.NET Web API中的HTTP Cookie 本文引自:http://www.asp.net/web-api/overview/working-with-http/http-cookies By Mike W ...
  • EF雖然是一個晚生畸形的ORM框架,但功能強大又具有靈活性的,給了開發人員一定的發揮空間。因為微軟出發點總是好的,讓開發變得簡單,但實際上不是所有的事情都這麼理想。這裡順便推薦馬丁大叔的書《企業應架構模式》。 本節主要深入分析EF的分層問題,下麵是本節的已列出的要探討內容。 領域模型的概念 DbCo ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...