Introduction
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
- Public HttpResponseMessage Put(int id, Employee employee)
- {
- …
- …
- }
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.
- public classTestData
- {
- public string Name
- {
- get;
- set;
- }
- public int Id
- {
- get;
- set;
- }
- }
Get method of Employee controller class shown below:
- public HttpResponseMessage Get([FromUri] TestData data)
- {……
- return Request.CreateResponse(HttpStatusCode.OK, true);
- }
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
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
- [HttpPost]
- public HttpResponseMessage Post([FromBody] string name)
- {
- ……
- return Request.CreateResponse(HttpStatusCode.OK, true);
- }
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.
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
- namespace WebAPITest
- {
- using System;
- using System.ComponentModel;
- using System.Globalization;
- public class TestTypeConverter: TypeConverter
- {
- public override bool CanConvertFrom(ITypeDescriptorContext context, TypesourceType)
- {
- if (sourceType == typeof(string))
- {
- returntrue;
- }
- returnbase.CanConvertFrom(context, sourceType);
- }
-
- public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
- {
- if (value is string)
- {
- TestData data;
- if (TestData.TryParse((string) value, out data))
- {
- return data;
- }
- }
- return base.ConvertFrom(context, culture, value);
- }
- }
- }
TestData class definition
- namespaceWebAPITest
- {
- using System.ComponentModel;
- [TypeConverter(typeof(TestTypeConverter))]
- public class TestData
- {
- public string Name
- {
- get;
- set;
- }
- public int Id
- {
- get;
- set;
- }
-
- public static bool TryParse(string s, outTestData result)
- {
- result = null;
-
- var parts = s.Split(',');
- if (parts.Length != 2)
- {
- return false;
- }
-
- int id;
- string name = parts[1];
- if (int.TryParse(parts[0], out id))
- {
- result = newTestData()
- {
- Id = id, Name = name
- };
- return true;
- }
- return false;
- }
- }
- }
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.
- public HttpResponseMessage Get(TestData data)
- {
- ……
- return Request.CreateResponse(HttpStatusCode.OK, true);
- }
URI: http://localhost:24367/api/Employee?data=10,jignesh%20trivedi
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
- namespace WebAPITest
- {
- using System;
- using System.Web.Http.Controllers;
- using System.Web.Http.ModelBinding;
- using System.Web.Http.ValueProviders;
- public class CustomModelBinder: IModelBinder
- {
- static CustomModelBinder()
- {
-
- }
-
- public bool BindModel(HttpActionContextactionContext, ModelBindingContextbindingContext)
- {
- if (bindingContext.ModelType != typeof(TestData))
- {
- 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");
- returnfalse;
- }
- TestData result = newTestData();
-
- var data = key.Split(newchar[]
- {
- ','
- });
- if (data.Length > 1)
- {
- result.Id = Convert.ToInt32(data[0]);
- result.Name = data[1];
- bindingContext.Model = result;
- return true;
- }
-
- bindingContext.ModelState.AddModelError(
- bindingContext.ModelName, "Cannot convert value to TestData");
- return false;
- }
- }
- }
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".
- namespace WebAPITest
- {
- using System.Web.Http;
- using System.Web.Http.ModelBinding;
- using System.Web.Http.ModelBinding.Binders;
- public static class WebApiConfig
- {
- public static void Register(HttpConfigurationconfig)
- {
- var provider = newSimpleModelBinderProvider(
- typeof(TestData), newCustomModelBinder());
- config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
-
- config.MapHttpAttributeRoutes();
-
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{id}",
- defaults: new
- {
- id = RouteParameter.Optional
- });
- }
- }
- }
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.
- public HttpResponseMessage Get([ModelBinder(typeof(CustomModelBinder))] TestData data)
-
- {
- ……
- return Request.CreateResponse(HttpStatusCode.OK, true);
- }
URI: http://localhost:24367/api/Employee?data=10,jignesh%20trivedi
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.
- [ModelBinder(typeof(CustomModelBinder))]
- public class TestData
- {
-
- }
Summary
Model binding is a very important process to bind parameter with the value. In this article, I described various ways of model binding in the Web API.