[译]WebAPI下的如何实现参数绑定

本文将概述在WebAPI方式下将如何将参数绑定到一个action方法,包括参数是如何被读取,一系列规则决定特定环境采用的那种绑定方式,文章最后将给出一些实际的例子。

Parameter binding说到底是接到一个Http请求,将其转换成.NET类型使得action方法的签名更易于理解。

请求消息(request message)包括了请求的所有信息,如带查询字符串的请求地址(URL),内容主体(content body)及头部信息(header)。在没有采用parameter binding

的情况下,每个action方法将需要接收request message,并手动从中提取出参数,如下所示:

public object MyAction(HttpRequestMessage request)
{
        // make explicit calls to get parameters from the request object
        int id = int.Parse(request.RequestUri.ParseQueryString().Get("id")); // need error logic!
        Customer c = request.Content.ReadAsAsync<Customer>().Result; // should be async!
        // Now use id and customer
}

很显然,这样的方式丑陋,易出错,代码重复,而且难以单元测试。我们希望action的签名类似以下的形式:

public object MyAction(int id, Customer c) { }

那么WebAPI是如何将request message转换成像id和customer这样的参数的呢?

Model Binding vs. Formatters

参数绑定有两种技术:Model Binding和Formatters。实际上,WebAPI使用model binding读取查询字符串(query string)内容进行参数绑定,使用Formatters读取主体内容

(body content)进行参数的绑定。

  • Using Model Binding:

ModelBinding和MVC中的此概念是一致的,更多内容见Here。通常有一个"ValuePeoviders"提供数据片断如查询字符串参数,model binder将这些片断组合成一个对象。

  • Using Formatters:

Formatters(如MediaTypeFormatter类所示)实际上是包含额外元数据的序列化程序。WebAPI从HttpConfiguration中获取一个formatters的列表,然后通过request信息

中的content-type来判断采用具体合适的formatter。WebAPI有不少默认的formatters。默认的JSON formatter是JSON.NET。还有Xml formatter和采用JQuery语法的

FormUrl formatter。

其中Formatters的核心方法是MediaTypeFormatter.ReadFromStreamAsync,如下所示:

public virtual Task<object> ReadFromStreamAsync(
    Type type, 
    Stream stream, 
    HttpContentHeaders contentHeaders, 
    IFormatterLogger formatterLogger)

其中,Type指代参数的类型,将传递给序列化器serializer。Stream是请求信息的content stream。Read方法将读取stream,将其实例化为一个对象,然后返回它。

HttpContentType来自请求信息。IFormatterLogger是一个回调接口,fomatter正是通过此接口来记录读取中的错误。

model binding和formatter都支持验证和错误信息记录。然后,相比formatter而言model binding更灵活。

何时采用特定的参数绑定方式?(when do we use which?)

以下这些基本原则决定了parameter是通过modelbinding还是formatter来读取的:

如果参数未添加任何特性字段[attribute]标明,那么这将由参数的.NET类型来决定具体采用何种方式。简单类型"Simple types"采用model binding。复杂类型"Complex types"

  1. 采用formatters。简单类型包括:primitives,Timespan,DateTime,Guid,Decimal,String,或者通过类型转换器(TypeConverter)从字符串转换过来的类型。
  2. 通过使用[FormBody]特性标示特定参数应该从body中取值。
  3. 通过使用[ModelBinder]特性来标示参数或参数类型应该使用model bound方式。这个特性也可以用来配置model binder。[FromUri]是一个从[ModelBinder]继承而来的 实体,用于配置model binder只应用到URL中的数据。
  4. body只能被读取一次。如果签名中有两个负责类型,至少其中一个必须添加[ModelBinder]特性标注。

以下是使得这些原则得以稳定并可预测的关键设计。

(body 只能被读取一次)Only one thing can read the body

MVC和WebAPI之间的一个关键不同点在于MVC缓存请求主体(request body)。这意味着MVC的参数绑定可以反复从body中查找参数片断。然而,在WebAPI中,请求主体(HttpContent)

只能被读取一次,不被缓存,只能向前读取的流。这意味着parameter binding需要谨慎对待stream,除非在需要绑定参数的情况下,否则stream不能被读取。

以下的action方法想直接读取stream,因而导致WebAPI不能保证其拥有用于参数绑定的stream。考虑如下的action方法:

        // Action saves the request’s content into an Azure blob 
        public Task PostUploadfile(string destinationBlobName)
        {
            // string should come from URL, we’ll read content body ourselves.
            Stream azureStream = OpenAzureStorage(destinationBlobName); // stream to write to azure
            return this.Request.Content.CopyToStream(azureStream); // upload body contents to azure. 
        }

参数是一个简单类型,因而这将从查询字符串中读取。由于action签名中并不包含任何 负责类型,WebAPI将永远不会读取request content stream,因而这里的action方法可以读取它。

示例

以下给出一些不同请求的示例说明它们将如何映射到特定action签名:

/?id=123&name=bob  void Action(int id, string name) // 所有参数都是简单类型,因而都将来自url

/?id=123&name=bob  void Action([FromUri] int id, [FromUri] string name) // 同上

void Action([FromBody] string name); //[FormBody]特性显示标明读取整个body为一个字符串作为参数

public class Customer {   // 定义的一个复杂对象类型    public string Name { get; set; }    public int Age { get; set; }  }

/?id=123  void Action(int id, Customer c) // 参数id从query string中读取,参数c是一个复杂Customer对象类戏,通过formatter从body中读取

void Action(Customer c1, Customer c2) // 出错!多个参数都是复杂类型,都试图从body中读取,而body只能被读取一次

void Action([FromUri] Customer c1, Customer c2) // 可以!不同于上面的action,复杂类型c1将从url中读取,c2将从body中读取

void Action([ModelBinder(MyCustomBinder)] SomeType c) // 标示使用特定的model binder来解析参数

[ModelBinder(MyCustomBinder)] public class SomeType { } // 通过给特定类型SomeType声明标注[ModelBidner(MyCustomBinder)]特性使得所有SomeType类型参数应用此规则 

void Action(SomeType c) // 由于c的类型为SomeType,因而应用SomeType上的特性决定其采用model binding

与MVC的区别

以下是MVC和WebAPI在参数绑定上的一些不同点:

  1. MVC只具有model binding,而没有formatters。这是由于MVC将对request body也应用model bind 进行解析,而WebAPI对request body将使用serializer来解析。
  2. MVC将缓存request body,因而能够很容易的将其传递给model binding。WebAPI不缓存request body,因而默认将不对request body应用model binding解析。
  3. WebAPI的绑定完全可以通过action签名的类型来决定。比如:在WebAPI中,我们知道一个参数最终将从body还是query string中读取绑定。然而,在MVC中,model binding 系统将同时查找body和query string数据进行解析绑定。

原文:How WebAPI does Parameter Binding

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

8338
来自专栏一个爱瞎折腾的程序猿

sqlserver使用存储过程跟踪SQL

USE [master] GO /****** Object: StoredProcedure [dbo].[sp_perfworkload_trace_s...

2810
来自专栏Golang语言社区

【Golang语言社区】GO1.9 map并发安全测试

var m sync.Map //全局 func maintest() { // 第一个 YongHuomap := make(map[st...

5378
来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

5798
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2955
来自专栏大内老A

The .NET of Tomorrow

Ed Charbeneau(http://developer.telerik.com/featured/the-net-of-tomorrow/) Exciti...

37910
来自专栏我和未来有约会

Kit 3D 更新

Kit3D is a 3D graphics engine written for Microsoft Silverlight. Kit3D was inita...

2886
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2997
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4355
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

3248

扫码关注云+社区