Sunday, August 18, 2019

Parameter Binding In ASP.NET Web API

Binding is a process to set values for the parameters when Web API calls a controller action method. In this article, we learn how to map Web API methods with the different types of the parameters and how to customize the binding process. 

Web API tries to get the value from the URI. If a parameter type is a primitive type (Bool, int, Double, log, Timespan, DateTime, Guid, String) Web API tries to read the value of the parameter from the body for complex type, using media-type formatter.

Example
  1. Public HttpResponseMessage Put(int id, Employee employee)  
  2. {  
  3. …  
  4. …  
  5. }  
In this example, ID parameter is a primitive type so Web API tried to get this parameter value from the URL and employee parameter is a complex type, so Web API tried to get the value of this parameter from request body, using media-type formatter.

We can also force Web API to get the parameter value from the request body or request URL by using FromUri and FromBody attribute.

FromUri Attribute
It forces the Web API to read the parameter value from the requested URL. In the following example, I have defined the complex type and forced the Web API to read the value for complex type from the requested parameter.
  1. public classTestData  
  2. {  
  3.     public string Name   
  4.     {  
  5.         get;  
  6.         set;  
  7.     }  
  8.     public int Id   
  9.     {  
  10.         get;  
  11.         set;  
  12.     }  
  13. }  
Get method of Employee controller class shown below:
  1. public HttpResponseMessage Get([FromUri] TestData data)   
  2. {……  
  3.     return Request.CreateResponse(HttpStatusCode.OK, true);  
  4. }  
Client can put the value of ID and Name property of TestData class in the query string. Web API uses them to construct TestData class.

Example

URI:
 http://localhost:24367/api/Employee?Name=Jignesh&Id=10

Output
Output

FromBody Attribute
It forces the Web API to read the parameter value from the requested body. In the following example, I forced the Web API to read the value for simple type from the requested body by using FromBody attribute.

Example
  1. [HttpPost]  
  2. public HttpResponseMessage Post([FromBody] string name)  
  3. {  
  4.     ……  
  5.     return Request.CreateResponse(HttpStatusCode.OK, true);  
  6. }  
In the above example, I posted the one parameter to the requested body, and Web API used media-type formatter to read the value of  the name from the requested body.

To select the media formatter, Web API uses the content type header. In the above example, content type is set to "application/json" and requested body contains a string value, so it binds to the parameter at Web API. It supports the JSON string, not the JSON object, so only one parameter is allowed to read from the message body.

Output

Type converters
We can force Web API to treat complex types as a simple type (i.e. web API bind the value of parameter from URI) using type converter.

In the following example, I created TestData class, which has ID and Name properties. We also created a type converter that converts the string data to equivalent TestData instance. The class TestData is decorated with a TypeConverter attribute to specify the type of a converter.

Type converter definition
  1. namespace WebAPITest  
  2. {  
  3.     using System;  
  4.     using System.ComponentModel;  
  5.     using System.Globalization;  
  6.     public class TestTypeConverter: TypeConverter  
  7.     {  
  8.         public override bool CanConvertFrom(ITypeDescriptorContext context, TypesourceType)  
  9.         {  
  10.             if (sourceType == typeof(string))  
  11.             {  
  12.                 returntrue;  
  13.             }  
  14.             returnbase.CanConvertFrom(context, sourceType);  
  15.         }  
  16.   
  17.         public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)  
  18.         {  
  19.             if (value is string)  
  20.             {  
  21.                 TestData data;  
  22.                 if (TestData.TryParse((string) value, out data))  
  23.                 {  
  24.                     return data;  
  25.                 }  
  26.             }  
  27.             return base.ConvertFrom(context, culture, value);  
  28.         }  
  29.     }  
  30. }  
TestData class definition
  1. namespaceWebAPITest   
  2. {  
  3.     using System.ComponentModel;  
  4.     [TypeConverter(typeof(TestTypeConverter))]  
  5.     public class TestData  
  6.     {  
  7.         public string Name  
  8.         {  
  9.             get;  
  10.             set;  
  11.         }  
  12.         public int Id  
  13.         {  
  14.             get;  
  15.             set;  
  16.         }  
  17.   
  18.         public static bool TryParse(string s, outTestData result)  
  19.         {  
  20.             result = null;  
  21.   
  22.             var parts = s.Split(',');  
  23.             if (parts.Length != 2)  
  24.             {  
  25.                 return false;  
  26.             }  
  27.   
  28.             int id;  
  29.             string name = parts[1];  
  30.             if (int.TryParse(parts[0], out id))  
  31.             {  
  32.                 result = newTestData()  
  33.                 {  
  34.                     Id = id, Name = name  
  35.                 };  
  36.                 return true;  
  37.             }  
  38.             return false;  
  39.         }  
  40.     }  
  41. }  
After applying type converter, Web API is able to treat complex type as a simple type, so we don't have to include [FromUri] on the parameter.
  1. public HttpResponseMessage Get(TestData data)  
  2. {  
  3.     ……  
  4.     return Request.CreateResponse(HttpStatusCode.OK, true);  
  5. }  
URI: http://localhost:24367/api/Employee?data=10,jignesh%20trivedi

Output
Output

Model Binder
Create custom model binder, which is more flexible compared to using the type converter. We can use HTTP request, action description and raw values from URL.

IModelBinder interface is used to create custom model binder. This interface has only one method called "BindModel". 

In the following example, I have created custom model binder for the TestData class. In this model binder, I have read the raw value from route data, split this data and set the data to the class properties. Here, I have fetched the raw data value, using a value provider. Here, I have used a simple conversion for Model Binder, but it is not limited to a simple type.

Model Binder code
  1. namespace WebAPITest  
  2. {  
  3.     using System;  
  4.     using System.Web.Http.Controllers;  
  5.     using System.Web.Http.ModelBinding;  
  6.     using System.Web.Http.ValueProviders;  
  7.     public class CustomModelBinder: IModelBinder  
  8.     {  
  9.         static CustomModelBinder()  
  10.         {  
  11.   
  12.         }  
  13.   
  14.         public bool BindModel(HttpActionContextactionContext, ModelBindingContextbindingContext)  
  15.         {  
  16.             if (bindingContext.ModelType != typeof(TestData))  
  17.             {  
  18.                 return false;  
  19.             }  
  20.   
  21.             ValueProviderResult val = bindingContext.ValueProvider.GetValue(  
  22.                 bindingContext.ModelName);  
  23.             if (val == null)  
  24.             {  
  25.                 return false;  
  26.             }  
  27.   
  28.             string key = val.RawValue as string;  
  29.             if (key == null)  
  30.             {  
  31.                 bindingContext.ModelState.AddModelError(  
  32.                     bindingContext.ModelName, "Wrong value type");  
  33.                 returnfalse;  
  34.             }  
  35.             TestData result = newTestData();  
  36.   
  37.             var data = key.Split(newchar[]  
  38.             {  
  39.                 ','  
  40.             });  
  41.             if (data.Length > 1)  
  42.             {  
  43.                 result.Id = Convert.ToInt32(data[0]);  
  44.                 result.Name = data[1];  
  45.                 bindingContext.Model = result;  
  46.                 return true;  
  47.             }  
  48.   
  49.             bindingContext.ModelState.AddModelError(  
  50.                 bindingContext.ModelName, "Cannot convert value to TestData");  
  51.             return false;  
  52.         }  
  53.     }  
  54. }  
To register the custom model binder, we have to create a model-binder provider to the configuration file. In this example, I am using built-in model binder provider - "SimpleModelBinderProvider".
  1. namespace WebAPITest  
  2. {  
  3.     using System.Web.Http;  
  4.     using System.Web.Http.ModelBinding;  
  5.     using System.Web.Http.ModelBinding.Binders;  
  6.     public static class WebApiConfig  
  7.     {  
  8.         public static void Register(HttpConfigurationconfig)  
  9.         {  
  10.             var provider = newSimpleModelBinderProvider(  
  11.                 typeof(TestData), newCustomModelBinder());  
  12.             config.Services.Insert(typeof(ModelBinderProvider), 0, provider);  
  13.   
  14.             config.MapHttpAttributeRoutes();  
  15.   
  16.             config.Routes.MapHttpRoute(  
  17.                 name: "DefaultApi",  
  18.                 routeTemplate: "api/{controller}/{id}",  
  19.                 defaults: new  
  20.                 {  
  21.                     id = RouteParameter.Optional  
  22.                 });  
  23.         }  
  24.     }  
  25. }  
There are many ways to set the model binder.

Easiest way is to add ModelBinder attribute to the parameter. In following example, I have added ModelBinder attribute to the "data" parameter, so web API can understand how to use custom model binder, while binding the data.
  1. public HttpResponseMessage Get([ModelBinder(typeof(CustomModelBinder))] TestData data)  
  2.   
  3. {  
  4.     ……  
  5.     return Request.CreateResponse(HttpStatusCode.OK, true);  
  6. }  
URI: http://localhost:24367/api/Employee?data=10,jignesh%20trivedi

Output 
Output

Another way is to add ModelBinder attribute to the type. When we define ModelBinder attribute to the type, Web API uses this model binder for all the parameters of this type.
  1. [ModelBinder(typeof(CustomModelBinder))]  
  2. public class TestData   
  3. {  
  4.     //......  
  5. }  

No comments:

Post a Comment

How to register multiple implementations of the same interface in Asp.Net Core?

 Problem: I have services that are derived from the same interface. public interface IService { } public class ServiceA : IService { ...