本文将概述在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)进行参数的绑定。
ModelBinding和MVC中的此概念是一致的,更多内容见Here。通常有一个"ValuePeoviders"提供数据片断如查询字符串参数,model binder将这些片断组合成一个对象。
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指代参数的类型,将传递给序列化器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"
以下是使得这些原则得以稳定并可预测的关键设计。 (body 只能被读取一次)Only one thing can read the bodyMVC和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
/?id=123&name=bob void Action([FromBody] string name); //[FormBody]特性显示标明读取整个body为一个字符串作为参数 public class Customer { // 定义的一个复杂对象类型 /?id=123 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在参数绑定上的一些不同点:
|
|