Detailed descriptions of Web API parameter binding and model binding, detailed descriptions of api
Today, I will share with you how the Controller parses the data transmitted from the client in Asp. NET Web API and then assigns the parameter to the Controller, that is, parameter binding and model binding.
Web API parameter binding is simple type binding, such as string, char, bool, int, uint, byte, sbyte, short, ushort, long, float primitive types. Model binding is a binding of other complex types. We all know that in MVC, model binding is based on the default defamodelmodelbinder. There are no Get requests or POST requests. However, in Web APIs, the parameters and model binding mechanisms are different in Get requests and POST requests.
1. Parameter binding (simple type binding)
When binding Web API parameters, Action obtains data from the route data (url segment) and querystring by default. We all know that when a Get Request is sent to a service, the client places the data in the URL and sends the data to the server, while the POST Request places the data in the Request Body and sends it to the server. Therefore, by default, we can only use GET requests to send simple types of data to the server, and then use Action to obtain the data. For example:
Prepare the model:
Public class Number {public int A {get; set;} public int B {get; set;} public Operation {get; set ;}} public class Operation {public string Add {get; set;} public string Sub {get; set ;}}View Code
Configure the route:
Config. routes. mapHttpRoute (name: "ActionApi", routeTemplate: "api/norestful/{controller}/{action}/{id}", defaults: new {id = RouteParameter. optional });View Code
The WebAPI Controller is as follows:
Public class ValuesController: ApiController {[HttpGet, HttpPost] public int SubNumber (int a, int B) {return a-B ;}}View Code
The client ajax call is as follows:
Function ajaxOp (url, type, data, contentType) {$. ajax ({url: url, type: type, data: data, contentType: contentType success: function (result) {alert (result) ;}});} function () {var data = {a: 1, B: 2}; ajaxOp ('/api/norestful/Values/subnumber', 'post', data );}View Code
The output result is as follows:
If it is changed to a POST request, no matching action is found.
This is mainly because the simple type of binding uses the [FromUri] feature to parse data by default. The name indicates that it is only responsible for reading data from the URL, the usage of FromUri will be discussed later in complex type binding.
Of course, you can bind simple types in POST requests. There are three solutions.
Method 1: the requested data is put in the URL as querystring, And the POST data is empty.
function a() { var data = { a: 1, b: 2 }; ajaxOp('/api/norestful/Values/SubNumber?'+$.param(data), 'POST');}
Method 2: manually read data from the request:
POSTRequest:
[HttpPost] public async Task <IHttpActionResult> AddNumbers () {if (Request. content. isFormData () {NameValueCollection values = await Request. content. readAsFormDataAsync (); int a, B; int. tryParse (values ["a"], out a); int. tryParse (values ["B"], out B); return OK (a-B);} return StatusCode (HttpStatusCode. badRequest );}View Code
This method can solve the problem, but it has nothing to do with model binding. ReadAsFormDataAsync is an extension method of the HTTP content attribute of the HttpRequestMessage class, this method is used to parse data in the Request Header whose content-type is application/x-www-form-urlencoded.
GetRequest:
[HttpGet] public IHttpActionResult SubNumberByGet () {Dictionary <string, string> dic = Request. getQueryNameValuePairs (). toDictionary (x => x. key, x => x. value); int a, B; int. tryParse (dic ["a"], out a); int. tryParse (dic ["B"], out B); return OK (a-B );}View Code
Method 3: Use the [FromBody] feature to obtain data from the Request body.However, the [FromBody] feature can only be used for one parameter in the Action. If it is written as follows:
[HttpGet, HttpPost] public int SubNumber([FromBody]int a,[FromBody]int b) { return a - b; }
Will throw an exception that cannot bind multiple parameters to the request:
Therefore, binding a simple type in the POST request can still use the [FromBody] Feature and is also the most common solution.
The reason why the FromBody feature is used to bind multiple simple types to throw an exception is that in the Web API framework, data in the request body is sent to the server as a stream, however, we cannot modify the data in the stream after the model binding system reads the data in the stream. Unlike the Request body (RequestBody) in the MVC framework before the request starts) after the data in is processed, it is stored in the memory as a key-value pair. Therefore, it is no problem to bind multiple complex types to the MVC Controller. Similarly, in the Web API framework, one Action cannot be bound to multiple complex types at the same time by default. This will be discussed later. It will also provide related solutions for binding multiple complex types at the same time.
In the Web API framework, parameter binding (Simple Type Binding) can read data in three different ways:
1: The Web API first checks whether the [FromBody] feature is applied to the parameter. If yes, it reads data directly from the request body.
2: without the [FromBody] feature, the Web API reads data from the binding rule. The binding rule is implemented by setting the ParameterBindingRules attribute of HttpConfiguration in the WebApiConfig file, for example, config. parameterBindingRules. insert (0, typeof (Number), o => o. bindwithattriattribute (new FromUriAttribute () means that in the project, the Number type is obtained by default through the [FromUri] feature, so that you do not need to display the [FromUri] feature.
3: If the [FromBody] feature is not available and there are no binding rules, data is read through the [FroUri] feature, which is also the default behavior of parameter binding.
Ii. Model binding (complex type binding)
When binding complex types of Web APIs, Action obtains data from the request body by default. By default, Action can only send complex types to the server using POST requests. For example:
The Action is as follows:
[HttpGet,HttpPost] public int AddNumber(Number number) { return number.A + number.B; }
The client ajax is as follows:
function b() { var data = { a: 1, b: 2}; ajaxOp('/api/norestful/Values/AddNumber', 'POST', data);}
Action can be bound to only one complex type in the default POST request. If you bind multiple complex types, an exception is thrown because it has been mentioned earlier. There are at least four solutions to bind multiple complex types.
Method 1: Apply the FromUri feature to all types of GET requests.
The Action is as follows:
[HttpGet, HttpPost] public int OpNumbers([FromUri]Number number,[FromUri] Operation op) { return op.Add ? number.A + number.B : number.A - number.B; }
The client ajax is as follows:
function d() { var data = { a: 1, b: 2, add: true, sub: false } ajaxOp('/api/norestful/Values/OpNumbers', 'GET', data);}
Method 2: read data from the request manually. The specific implementation method is the same as the method for manually reading data from the request of the simple type above.
Method 3: bind data with nested complex types in GET requests and apply the [FromUri] Feature
The Action is as follows:
[HttpGet] public int OpNumbersByNestedClass([FromUri]Number number) { return number.Operation.Add ? number.A + number.B : number.A - number.B; }
The client ajax is as follows:
function b2() { var data = { a: 1, b: 2, add: true, sub: false }; ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'GET', { 'number.a': data.a, 'number.b': data.b, 'number.operation.add': data.add, 'number.operation.sub': data.sub });}
Method 4: POST request:
The Action is as follows:
[HttpGet,HttpPost] public int OpNumbersByNestedClass(Number number) { return number.Operation.Add ? number.A + number.B : number.A - number.B; }
The client ajax is as follows:
function b4() { var data = { a: 1, b: 2, 'operation.add': true, 'operation.sub': false }; ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'POST', data); }
In a POST request, a complex type attribute must be prefixed with a type name, or var data = {a: 1, B: 2, operation: {add: true, sub: false.
In fact, there is no big difference between simple type binding and complex type binding. The real difference is that data binding uses GET requests (the default method of simple type binding, complex types are implemented by setting FromUri or POST requests (the default method of complex types is implemented by setting Frombody for simple types, to put it bluntly, it is the difference between the FromUri and FromBody features. Now let's talk about how these two features find data internally.
FromUri features
In the application of the FromUri feature, parameters in Web API Action will parse data from the URL, while data parsing retrieves data by creating value providers in the value provider factory. For simple types, the value provider obtains the Action parameter name and parameter value. For complex types, the value provider obtains the type attribute name and attribute value.
The following is the code for the factory class of the Web API median provider:
namespace System.Web.Http.ValueProviders { public abstract class ValueProviderFactory { public abstract IValueProvider GetValueProvider(HttpActionContext context); }}
ValueProviderFactory uses the HttpActionContext parameter to select the provider to be created to parse the data.
FromBody features
With the Frombody feature, parameters in Web API Action will be retrieved and bound from the Request Body through the media formatter, in the Web API framework, there are 4 built-in media formatters:
1: JsonMediaTypeFormatter. The corresponding content-type is: application/json, text/json
2: XmlMediaTypeFormatter. The corresponding content-type is XmlMediaTypeFormatter.
3: FormUrlEncodedMediaTypeFormatter. The corresponding content-type is: application/x-www-form-urlencoded.
4: JQueryMvcFormUrlEncodedFormatter. The corresponding content-type is: application/x-www-form-urlencoded.
By default, the POST request uses JQueryMvcFormUrlEncodedFormatter to parse data. The JQueryMvcFormUrlEncodedFormatter class reads data from the URL using the value provider through model binding. The value provider here is the NameValuePairsValueProvider class, this class implements the IValueProvider interface to obtain data in key-value pairs.
Of course, you can also specify the content-type of the request when the client requests. In this way, the Web API selects different media types based on the content-type of the client. If the client uses the application/json format to transmit data, the Web API uses JsonMediaTypeFormatter in the background to parse the data. For example, the last example above changes the client ajax request, and the Controller remains unchanged:
function b4() { var data = { a: 1, b: 2, operation: { add: true, sub: false } }; ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'POST', JSON.stringify(data), 'application/json');}
We can see the data request format. This type of data is passed to Action in Json format to process model binding. I believe it is everywhere in MVC.
By default, JQueryMvcFormUrlEncodedFormatter is used, as shown in content-type:
Now, let's get here today.