Chapter 13. Formatters and Model Binding

Anyone can write code that a computer can understand. Only good programmers write code that humans can understand.

We previously discussed media types and their semantics as a way to represent concepts in the domain space of a system. Once we move to the implementation side, those abstractions must be translated somehow to a language that programmers speak. In the case of ASP.NET Web API, that final representation would be objects—or models, to give them a more precise name. Having said that, models represent a level of abstraction that developers use to map objects into media type representations or other different parts in an HTTP message.

The model binding infrastructure in ASP.NET Web API provides the necessary runtime services to perform many of these mappings for us. In that way, a developer can focus on the implementation details of Web API and leave all serialization concerns to the framework. There is an evident advantage in using this kind of architecture. The developer can work with a single level of abstraction, which is the model, and support a variety of media types according to the requirements of the different Web API consumers. For example, in our Issue Tracker application, we have a single model class representing an issue, which can optionally be converted to different media types like JSON or XML by the framework.

As part of this chapter, we will explore model binding in detail by looking at the different runtime components and extensibility hooks provided by the framework to customize or add new model binding functionality.

The Importance of Models in ASP.NET Web API

As a rule of thumb, controller actions that focus on single concerns are easier to test, extend, and maintain in the long run. Converting message representations into model objects is one of those concerns that you should try to move away from your action implementations in the first place. Consider Example 13-1, in which serialization concerns are mixed with the implementation of a Web API action.

Example 13-1. Action with serialization concerns
public  HttpResponseMessage Post(HttpRequestMessage request) // <1>
{
  int id = int.Parse(request.RequestUri.ParseQueryString().Get("id")); // <2>

  var values = request.Content.ReadAsFormDataAsync().Result // <3>

  var issue = new Issue
  {
     Id = id,
     Name = values["name"],
     Description = values["description"]
  };

  // do something with the constructed issue
}

There are a few evident problems with this code:

  • The generic signature in the controller method makes it really hard to infer its purpose without looking at the implementation details. It also limits our ability to overload the Post method with different arguments for supporting multiple scenarios.
  • It is not checking whether the parameter in the query string really exists or can be converted to an integer.
  • It is tying the implementation to a single media type (application/form-url-encoded) and also blocking the execution thread for reading the body content synchronously. To this last point, invoking the Result property directly on asynchronous tasks without checking if they are completed is not considered good practice and might prevent the execution thread from returning to the thread pool to attend to new requests.

We can easily rewrite this action to use a model class only and avoid all these issues, as illustrated in Example 13-2.

Example 13-2. Action with model binding
public void Post(Issue issue) // <1>
{
  // do something with the constructed issue
}

As you can see, all the serialization concerns literally disappeared from the implementation, leaving only what it matters most. The model binding infrastructure in the framework will take care of the rest at the moment of executing the action.

How Model Binding Works

At a very core level in the model binding infrastructure is a component called HttpParameterBinding, which knows how to infer the value of a parameter from a request message and is demonstrated in Example 13-3. Every HttpParameterBinding instance is tied to a single parameter, which is defined at the moment of the Web API with the HttpConfiguration object. How that instance is tied to a parameter is determined by another configuration class called HttpParameterDescriptor, which contains metadata for describing a parameter in terms of name, type, or any other attribute that could be used by the model binding infrastructure to select an HttpParameterBinding.

Example 13-3. Action with model binding
public abstract class HttpParameterBinding
{
  protected HttpParameterBinding(HttpParameterDescriptor descriptor);

  public abstract Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
  HttpActionContext actionContext, CancellationToken cancellationToken); // <1>
}

Example 13-3 shows the basic structure of an HttpParameterBinding with the key method ExecuteBindingAsync, which every implementation must provide to perform the binding for a parameter.

As happens with many of the runtime components in ASP.NET Web API, an HttpParameterBinding also offers an asynchronous signature for its core method, ExecuteBindingAsync. This would be useful if you had, for example, an implementation that does not necessarily rely on values obtained from the current request message and performs some I/O operations such as querying a database or reading a file. Example 13-4 shows a basic implementation of an HttpParameterBinding for binding action parameters of the type CultureInfo from the culture set in the executing thread.

Example 13-4. HttpParameterBinding implementation
public class CultureParameterBinding : HttpParameterBinding
{
  public CultureParameterBinding(HttpParameterDescriptor descriptor) // <1>
    : base(descriptor)
  {
  }

  public override System.Threading.Tasks.Task
  ExecuteBindingAsync(System.Web.Http.Metadata.ModelMetadataProvider
  metadataProvider, HttpActionContext   actionContext,
  System.Threading.CancellationToken cancellationToken)
  {
    CultureInfo culture = Thread.CurrentThread.CurrentCulture; // <2>
    SetValue(actionContext, culture); // <3>

    var tsc = new TaskCompletionSource<object>(); // <4>
    tsc.SetResult(null);
    return tsc.Task;
  }
}

An instance of our HttpParameterBinding is created with a descriptor. Our implementation just ignores that parameter, but other implementations might use some of its information <1>. The ExecuteBindingAsync method gets the CultureInfo instance from the current thread <2> and uses it to set the binding with the help of the SetValue method in the base class <3>. As the last step in this method, a TaskCompletionSource is created for returning a new task, already completed synchronously <4>. In an asynchronous version of this method, SetValue would probably be called as part of the returned task.

This CultureParameterBinding can now be used to inject a CultureInfo instance directly as a parameter of an action method, as shown in Example 13-5.

Example 13-5. A Web API action that receives a CultureInfo instance as a parameter
public class HomeController : ApiController
{
  [HttpGet]
  public HttpResponseMessage BindCulture(CultureInfo culture)
  {
    return Request.CreateResponse(System.Net.HttpStatusCode.Accepted,
        String.Format("BindCulture with name {0}.", culture.Name));
  }

Now you know what an HttpParameterBinding is, but we haven’t discussed yet how it is configured and selected by the framework when an action is executed. This selection is made in one of the many pluggable services available in the System.Web.Http.ModelBinding.IActionValueBinder framework, whose default implementation is System.Web.Http.ModelBinding.DefaultActionValueBinder. An IActionValueBinder is reponsible for returning an HttpActionBinding instance, which mainly contains a collection of HttpParameterBinding instances associated with a given controller action that can be cached across requests:

public interface IActionValueBinder
{
  HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor);
}

The built-in implementation in DefaultActionValueBinder uses reflection to build a list of HttpParameterDescriptors, which is later used for querying the configuration and selecting the appropriate HttpParameterBinding instances (see Figure 13-1).

The HttpParameterBinding selection
Figure 13-1. The HttpParameterBinding selection

This class currently supports two different ways to determine which HttpParameterBinding instances are associated with an action. In the first one, the association is done through configuration with the property ParameterBindingRules in the HttpConfiguration object, which exposes a set of rules for choosing an binding instance for a given HttpParameterDescriptor. Those rules take the shape of a delegate, Func<HttpParameterDescriptor, HttpParameterBinding>, that receives a descriptor as a parameter and returns a binding instance. That means you can either provide a method callback or a lambda expression to resolve the bindings. For our scenario with the CultureParameterBinding, we need a rule that returns our binding for an HttpParameterDescriptor associated with the type System.Globalization.CultureInfo, as shown in Example 13-6.

Example 13-6. HttpParameterBinding configuration with a rule
config.ParameterBindingRules.Insert(0, (descriptor) => // <1>
{
    if (descriptor.ParameterType == typeof(System.Globalization.CultureInfo)) // <2>
      return new CultureParameterBinding(descriptor);

    return null;
});

The new rule is inserted with a lambda expression <1> that checks for the ParameterType property in the descriptor and returns the binding only when the type is equal to System.Globalization.CultureInfo <2>.

A second mechanism, which is more declarative, involves the use of an attribute, ParameterBindingAttribute, that we need to derive (as we will explore in the next section).

If no mapping rule or ParameterBindingAttribute is found, this binder uses a default policy, which binds simple types to URI segments or query string variables and complex types to the request body.

Built-In Model Binders

The framework ships with several built-in implementations, but only three of them deserve special attention from a developer: ModelBindingParameterBinder, FormatterParameterBinder, and HttpRequestParameterBinding, which implement completely different ways of binding a message part to a model. The first one, ModelBindingParameterBinder, uses an approach borrowed from ASP.NET MVC in which the model is composed of different parts in the message, as if they are Lego building blocks. The second one, FormatterParameterBinder, relies on formatters that understand all the semantics and formatting of a given media type and know how to serialize or deserialize a model applying those semantics. Formatters represent a key part of content negotiation and are the preferred method for binding a message body to a model. Finally, the third one, HttpRequestParameterBinding, is used for supporting scenarios with generic actions that use HttpRequestMessage or HttpResponseMessage instances directly as part of the method signature.

The ModelBindingParameterBinder Implementation

The ModelBindingParameterBinder implementation reuses the same idea applied in ASP.NET MVC for doing model binding. It relies on value providers, which know how to obtain data from different parts of an HTTP message, and model binders for assembling those parts into a model.

This implementation is mainly focused on binding simple key/value pairs such as those found in HTTP headers, URL segments, query strings, or a body encoded with application/form-url-encoded (the media type used for encoding an HTTP form). All these values are usually strings that can be found in the message and converted to primitive types. Modeling binders do not know anything specific about media types or how they can be interpreted; that’s the job of the formatters, which we will discuss in detail in the next section.

The framework ships with several built-in model binder implementations that assemble different small pieces found in an HTTP message into fairly complex models.To be more precise, those implementations also take care of converting strings into simple data types such as Timespan, Int, Guid, Decimal, or other types decorated with a type converter before hydrating the model. Two examples of these built-in implementations are ArrayModelBinder and TypeConverterModelBinder, both in the System.Web.Http.ModelBinding.Binders namespace. It’s worth mentioning that model binders are mostly used for rehydrating simple types, or as building blocks for composing more complex types. These built-in implementations typically cover the most common scenarios, so you will have to think twice before you start writing a new model binder from scratch.

Model binding in action
Figure 13-2. Model binding in action

In Figure 13-2, the configured value providers first take care of decomposing the message into pieces for getting different values such as the issue ID from the query string and the rest of the fields from the message body, which were submitted as a HTTP PUT with the URL form encoding media type. The selected model binder works closely with the value providers to request the data needed for initializing a new Issue class instance.

Value Providers

Value providers provide a thin abstraction layer for decoupling model binders from any messaging details. They do this by aggregating values from different parts of an HTTP message, and providing an uniform interface to consume them.

At a very core level, every value provider implements the System.Web.Http.ValueProviders.IValueProvider interface, as shown in Example 13-7.

Example 13-7. IValueProvider interface definition
public interface IValueProvider
{
  bool ContainsPrefix(string prefix); // <1>
  ValueProviderResult GetValue(string key); // <2>
}

The first method, ContainsPrefix <1>, returns a Boolean value indicating whether the value provider implementation can provide a value for a key with the prefix passed as an argument, which typically represents a property name in the model being deserialized.

The second method, and probably the most important, GetValue <2>, searches the key passed as an argument in the HTTP message and returns the associated value. The value is not returned as a raw string directly but as a ValueProviderResult instance, which contains methods for getting the raw value or a value cast to an specific type.

You might want to create a new value provider or derive from an existing one for addressing new use cases such as searching values in the request message under specific name conventions or in other places such as custom cookies.

Example 13-8 shows a basic implementation of a value provider for searching headers with a vendor prefix X-.

Example 13-8. IValueProvider implementation
public class HeaderValueProvider : IValueProvider
{
  public const string HeaderPrefix = "X-";

  private HttpControllerContext context;

  public HeaderValueProvider(HttpControllerContext context) // <1>
  {
    this.context = context;
  }

  public bool ContainsPrefix(string prefix)
  {
    var contains = context.Request
      .Headers
      .Any(h => h.Key.Contains(HeaderPrefix + prefix)); // <2>

    return contains;
  }

  public ValueProviderResult GetValue(string key)
  {
    if (!context.Request.Headers.Any(h => h.Key == HeaderPrefix + key))
      return null;

    var value = context.Request
      .Headers
      .GetValues(HeaderPrefix + key).First(); // <3>

    var stringValue = (value is string) ? (string)value : value.ToString(); // <4>

    return new ValueProviderResult(value, stringValue,
       CultureInfo.CurrentCulture); // <5>
  }
}

The HeaderValueProvider implementation is constructed with an HttpControllerContext instance that provides access to the execution context and also the request message <1>. The ContainsPrefix method returns true for any key in the HTTP request headers starting with an X- prefix <2>, and the GetValue method gets its value <3>. That value is returned in a new ValueProviderResult instance <5> and also as a raw string <4>.

A IValueProvider implementation can be injected at runtime through a ValueProviderFactory, which is a class that derives from the abstract class System.Web.Http.ValueProviders.ValueProviderFactory and overrides the method GetValueProvider for returning instances of the IValueProvider implementation. You can find the corresponding value provider factory implementation for HeaderValueProvider in Example 13-9.

Example 13-9. ValueProviderFactory implementation
public class HeaderValueProviderFactory : ValueProviderFactory
{
  public override IValueProvider GetValueProvider(HttpActionContext actionContext)
  {
    return new HeaderValueProvider(actionContext.ControllerContext); // <1>
  }
}

The HeaderValueProviderFactory implementation instantiates a new HeaderValueProvider using the current HttpActionContext as an argument in the constructor <1>. We can register this factory in the HttpConfiguration object using the global dependency resolver, as shown in Example 13-10.

Example 13-10. The HeaderValueProviderFactory injected in the configuration for a web host
public static void RegisterValueProvider(HttpConfiguration config)
{
  var valueProviderFactories = config.ServiceResolver
     .GetValueProviderFactories().ToList();

  valueProviderFactories.Insert(0, new HeaderValueProviderFactory()); // <1>

  config.ServiceResolver.SetServices(typeof(System.Web.Http.ValueProviders
     .ValueProviderFactory),
     valueProviderFactories.ToArray()); // <2>
}

The factory is added to the existing list of factories in the first position <1>, so it takes precedence when a value needs to be provided, and is injected afterward as a service with the dependency resolver <2>.

The most important value providers shipped with the framework are System.Web.Http.ValueProviders.Providers.QueryStringValueProvider and System.Web.Http.ValueProviders.Providers.RouteDataValueProvider, and their correspoding factories, System.Web.Http.ValueProviders.Providers.QueryStringValueProviderFactory and System.Web.Http.ValueProviders.Providers.RouteDataValueProvider. While the first provider parses and provides values found in the query string, the second is responsible for obtaining values from the route parameters (i.e., the parameters that you define at the route level in the route configuration).

Model Binders

Model binders orchestrate all the actions for assembling a new model instance from the different data pieces requested to the configured value providers. A model binder implements the interface System.Web.Http.ModelBinding.IModelBinder, which contains only one method, BindModel, where all the magic happens (see Example 13-11).

Example 13-11. IModelBinder interface
public interface IModelBinder
{
  bool BindModel(HttpActionContext actionContext, ModelBindingContext
  bindingContext); // <1>
}

The BindModel method receives two objects <1>, an HttpActionContext instance with specific information about the current execution, and an instance of ModelBindingContent representing the context of the model binding process. This method also returns a Boolean value indicating whether the implementation could successfully assemble a new model instance. There are two important properties available as part of the binding context, ModelState and ModelMetadata. The former is a property bag class used by the model binder for storing the results of the binding model process or any error that might happen in that process. The latter provides access to the discovered metadata associated to the model, such as available properties or any component model attribute for performing data validations. Although this interface looks very simple at first glance, it hides a good deal of the complexity required for implementing a model binder and providing the right behavior at runtime. For that reason, the following sequence describes in detail all the steps performed by an IModelBinder implementation.

  1. The implementation tries to get all the values it needs to assemble a new model from the value provider passed as part of the binding context. Although the binding context provides access to a single value provider, that instance usually represents a built-in value provider, CompositeValueProvider, which implements the IValueProvider interface but internally delegates the method calls to all the configured value providers.
  2. A model is created and initialized with all the values obtained from the value provider. If some error happens during the model initialization, the exceptions are set on the binding context through the ModelState property.
  3. The model is set on the binding context.

Example 13-12 shows a model binder implementation to create instances of the Issue model class previously discussed in this chapter.

Example 13-12. IssueModelBinder implementation
public class IssueModelBinder : IModelBinder
{
  public bool BindModel(HttpActionContext actionContext, ModelBindingContext
  bindingContext)
  {
    var model = (Issue)bindingContext.Model ?? new Issue();

    var hasPrefix = bindingContext.ValueProvider
      .ContainsPrefix(bindingContext.ModelName);

    var searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";

    int id = 0;
    if(int.TryParse(GetValue(bindingContext, searchPrefix, "Id"), out id)
    {
      model.Id = id; // <1>
    }

    model.Name = GetValue(bindingContext, searchPrefix, "Name"); // <2>
    model.Description = GetValue(bindingContext, searchPrefix, "Description"); // <3>

    bindingContext.Model = model;

    return true;
  }

  private string GetValue(ModelBindingContext context, string prefix, string key)
  {
    var result = context.ValueProvider.GetValue(prefix + key); // <4>
    return result == null ? null : result.AttemptedValue;
  }
}

This implementation uses the value provider available as part of the binding context <1> for requesting data, and binds those values to the model’s properties afterward in <2>, <3>, and <4>. This is not something you would likely do in a real application, but it provides a simple demonstration of how an IModelBinder implementation might look.

A model binder implementation is finally configured and injected at runtime through a model binder provider, which works as a factory. A model binder provider derives from the base class System.Web.Http.ModelBinding.ModelBinderProvider and implements the method GetBinder for returning a new model binder instance, as shown in Example 13-13.

Example 13-13. A ModelBinderImplementation for returning IssueModelBinder instances
public class IssueModelBinderProvider : ModelBinderProvider
{
  public override IModelBinder GetBinder(HttpActionContext actionContext,
  ModelBindingContext bindingContext)
  {
    return new IssueModelBinder();
  }
}

You can register this provider by using the dependency resolver available as part of the HttpConfiguration object, or by decorating the model class with a System.Web.Http.ModelBinding.ModelBinderAttribute, as shown in Examples 13-14 and 13-15.

Example 13-14. A model class decorated with ModelBinderAttribute
[ModelBinder(typeof(IssueModelBinderProvider))]
public class Issue
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Description { get; set; }
}
Example 13-15. A parameter decorated with ModelBinderAttribute
public void Post([ModelBinder(typeof(IssueModelBinderProvider))]Issue issue)
{
}

An interesting fact about the ModelBinderAttribute is that it derives from the previously discussed attribute ParameterBindingAttribute. This attribute was used to declaratively attach an HttpParameterBinding instance to a parameter. In this case, the ModelBinderAttribute initializes a new instance of ModelBindingParameterBinder that internally uses the ModelBinderProvider passed as an argument (IssueModelBinderProvider, in our examples).

Model Binding Against URIs Only

The framework ships with another attribute, FromUriAttribute, that derives from the ModelBinderAttribute to force the runtime to perform the binding only against data available in the URL. This is useful for binding values found in the URL to properties in a model class, as the framework will bind values in the URL only against simple types by default.

Example 13-16 illustrates how the query string variables Lang and Filter are automatically mapped to the properties with the same name on the IssueFilters model.

Example 13-16. Model binding with query string variables
// Sample url: http://../Issues?Lang=en&Filter=2345

public class IssueFilters
{
  public string Lang { get; set; }
  public string Filter { get; set; }
}

public IEnumerable<Issue> Get([FromUri]IssueFilters)
{
  // Action implementation
}

The FormatterParameterBinder Implementation

This implementation relies on formatters, which were introduced in ASP.NET Web API for supporting better content-negotiation scenarios with the use of media types. In ASP.NET MVC, only the HTML (text/html) and JSON (application/json) media types were treated as first-class citizens and fully supported across the entire stack. Also, there was not a consistent model for supporting content negotiation. You could support different media types for the response messages by providing custom ActionResult implementations, but it was not clear how a new media type could be introduced and handled by the framework. Developers typically solved this by leveraging the model binding infrastructure with new model binders or value providers.

Fortunately, this inconsistency has been solved in ASP.NET Web API with the introduction of formatters. A formatter now unifies all the serialization concerns by providing a single entry point for serializing or deserializing a model using the format expressed by a media type. The formatters to use for a given message will be determined by the content negotiation algorithm.

Every formatter derives from the base class MediaTypeFormatter (see Example 13-17) and overrides the methods CanReadType and ReadFromStreamAsync for supporting deserialization, and CanWriteType and WriteToStreamAsync for supporting serialization of models following the semantics and format of a media type.

Example 13-17. MediaTypeFormatter class definition
public abstract class MediaTypeFormatter
{
  public Collection<Encoding> SupportedEncodings { get; }

  public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; }

  public Collection<MediaTypeMapping> MediaTypeMappings { get; }

  public abstract bool CanReadType(Type type);

  public abstract bool CanWriteType(Type type);

  public virtual Task<object> ReadFromStreamAsync(Type type, Stream readStream,
    HttpContent content, IFormatterLogger formatterLogger);

  public virtual Task WriteToStreamAsync(Type type, object value,
    Stream writeStream, HttpContent content, TransportContext transportContext);
}

The following list summarizes the principal characteristics of the MediaTypeFormatter class:

  • The CanReadType and CanWriteType methods receive a type as an argument, and must return a value indicating whether they can read or write an object of that type into an stream representing the message body. This means a formatter might know how to write a type but not how to read it from an stream, for example.
  • The SupportedMediaTypes collection specifies the list of supported media types (e.g., text/html). This list is typically initialized in the formatter constructor method. The runtime will determine which formatter to use during the content negotiation handshake based on the value returned by the CanReadType or CanWriteType methods and the supported media types. It’s worth mentioning that a request message can mix different media types sometimes when the Content-Type header is set to multipart, so every part defines its media type. The runtime can handle this scenario as well by selecting one or more formatters for all the present media types.
  • A MediaTypeFormatter adheres to the Task Parallel Library (TPL) programming model for the read and write operations. Most implementations will still run synchronously, as they involve only serialization.
  • The MediaTypeMappings collection allows a formatter to define how to look for the media type associated with a request message. (e.g., query string, HTTP header). For example, a client application might send the expected media type format for the response as part of the query string.

The framework includes a set of formatters out of the box for handling the most common media types such as form-encoded data (FormUrlEncodedMediaTypeFormatter), JSON (JsonMediaTypeFormatter), or XML (XmlMediaTypeFormatter). For other media types, you will have to write your own implementation, or use one of the many implementations provided by the open source community.

Now we’ll discuss the implementation of a MediaTypeFormatter for serializing a model as part of a RSS or ATOM feed (see Example 13-18).

Example 13-18. MediaTypeFormatter implementation
public class SyndicationMediaTypeFormatter : MediaTypeFormatter
{
  public const string Atom = "application/atom+xml";
  public const string Rss = "application/rss+xml";

  public SyndicationMediaTypeFormatter()
    : base()
  {
    this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(Atom)); // <1>
    this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(Rss));
  }

  public override bool CanReadType(Type type)
  {
    return false;
  }

  public override bool CanWriteType(Type type)
  {
    return true; // <2>
  }

  public override Task WriteToStreamAsync(Type type, object value, Stream
  writeStream, HttpContent content, TransportContext transportContext) // <3>
  {
    var tsc = new TaskCompletionSource<AsyncVoid>(); // <4>
    tsc.SetResult(default(AsyncVoid));

    var items = new List<SyndicationItem>();

    if (value is IEnumerable)
    {
      foreach (var model in (IEnumerable)value)
      {
        var item = MapToItem(model);
        items.Add(item);
      }
    }
    else
    {
      var item = MapToItem(value);
      items.Add(item);
    }

    var feed = new SyndicationFeed(items);

    SyndicationFeedFormatter formatter = null;
    if (content.Headers.ContentType.MediaType == Atom)
    {
      formatter = new Atom10FeedFormatter(feed);
    }
    else if (content.Headers.ContentType.MediaType == Rss)
    {
      formatter = new Rss20FeedFormatter(feed);
    }
    else
    {
      throw new Exception("Not supported media type");
    }

    using (var writer = XmlWriter.Create(writeStream))
    {
      formatter.WriteTo(writer);

      writer.Flush();
      writer.Close();
    }

    return tsc.Task; // <5>
  }

  protected SyndicationItem MapToItem(object model) // <6>
  {
    var item = new SyndicationItem();

    item.ElementExtensions.Add(model);

    return item;
  }

  private struct AsyncVoid
  {
  }
}

This implementation knows only how to serialize models according to the Atom and RSS media type definitions, so that is explicitly specified as part of the constructor <1>. It also returns true in the CanWrite method to specify that the implementation is write only <2>.

The WriteToStreamAsync method implementation <3> mainly relies on the syndication classes included with the WCF Web Programming model for serializing the models into Atom or RSS feeds. This programming model provides classes for constructing a syndication feed and all the associated entries, as well as the formatter classes for transforming those into a well-known syndication format such as Atom or RSS.

As we stated, the WriteToStreamAsync and ReadFromStreamAsync methods leverage the new Task Parallel Library for doing the asynchronous work. They both return a Task instance that internally wraps the asynchronous work. However, most of the time, serialization is a safe operation that can be done synchronously. In fact, many of the serializer classes you will find in the .NET Framework do their job synchronously. Creating a new task using the method Task.Factory.StartNew for all the serialization work would be the easiest thing to do, but there are some collateral effects associated with that action. After we invoke the StartNew method, a new job is scheduled, which might generate a thread context switch that hurts performance. The trick in this scenario is to use a TaskCompletionSource <4>. The TaskCompletionSource is marked as complete so all the work is done synchronously afterward, and the resulting task associated with the TaskCompletionSource is returned <5> The method MapToItem <6> simply uses the model instance as the content for a syndication item.

One more thing we might want to support in a formatter is the ability to negotiate the media type from additional sources rather than the Accept header. This is a very common requirement these days for clients where the HTTP client stack is not correctly implemented (as happens with browsers in some older mobile devices). In those cases, a client might want to provide the accepted media type in the query string, such as http://…/Issues?format=atom. The MediaTypeFormatter supports this scenario through the MediaMappings property, which represents a collection of MediaMapping instances indicating the locations where the media type can be found, such as query strings, headers, or URI segments. The framework provides several concrete implementations of the MediaMapping abstract class for addressing the most common scenarios. The following list provides a brief description of these mappings:

QueryStringMapping
This can be used to map the requested media type to a query string variable. For example, the format variable in the URL http://localhost/issues?format=atom would map to the atom media type.
UriPathExtensionMapping
This can be used to map a path in a URI to a media type. For example, http://localhost/issues.atom would map the path .atom to the atom media type.
RequestHeaderMapping
This maps a request header to a media type. This would be useful in case you do not want to use any of the standard HTTP request headers.

The media type mappings are injected in a formatter through the constructor. Example 13-19 shows how the constructor was modified to use a QueryStringMapping instance for searching the media type as part of a query string.

Example 13-19. Media type mapping from query string
public const string Atom = "application/atom+xml";
public const string Rss = "application/rss+xml";

public SyndicationMediaTypeFormatter()
  : base()
{
  this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(Atom));
  this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(Rss));

  this.MediaTypeMappings
        .Add(new QueryStringMapping("format", "atom",
                new MediaTypeHeaderValue(Atom))); // <1>
}

If the formatter found a query string variable format with a value atom, that would be mapped to the Atom media type (application/atom+xml) <1>.

Default HttpParameterBinding Selection

By default, the model binding infrastructure will try to use FormatterParameterBinder for complex type parameters, and ModelBindingParameterBinder for simple .NET types. If you have multiple complex type arguments, the binding will fail unless you explicitly specify that any of these arguments must be bound from the URL or the body with the FromUriAttribute or FromBodyAttribute attributes, respectively. The FromBodyAttribute is another mechanism you can use to force the use of a FormatterParameterBinder for a given parameter. If an action contains multiple complex parameters, only one of them can be read from the request body via the FromBodyAttribute. Otherwise, the runtime will throw an exception.

Model Validation

Model validation is another feature that you get in Web API with the model binding infrastructure. You can use this feature to either enforce business rules or to make sure the data sent by a client is correct. As the model validation is performed in a single place while the model is bound, this centralization results in code that’s easier to maintain and test.

Another important aspect of model validation is to inform clients about any possible errors in the data they sent with a chance to correct those errors. In practice, when this aspect is not enforced, developers will simply stop adopting the API as part of their applications.

As with all of the model binding infrastructure, model validation in ASP.NET Web API is also completely extensible. The framework ships with a general-purpose validator that uses attributes for validating the models. This validator works for most scenarios, and reuses the data annotation attributes included in the System.ComponentModel.DataAnnotations namespace. Several validation attributes are provided out of the box in that namespace—such as Required to mark a property as required or RegularExpression to validate a property value with a regular expression. You are also free to create your own custom data annotation attributes for use cases initially not covered by the built-in ones.

Applying Data Annotation Attributes to a Model

Suppose we want have some validations applied to our issue model. We can start using the data annotation attributes to decorate the model and enforce common validation scenarios without having to write much code, and more importantly, without requiring repetitive code. Example 13-20 shows how the issue model looks after we’ve applied some data annotation attributes.

Example 13-20. An issue model with data annotation attributes
public class Issue
{
  [DisplayName("Issue Id")]
  [Required(ErrorMessage = "The issue id is required")]
  [Range(1, 1000, ErrorMessage = "The unit price must be between {1} and {2}")]
  public int Id { get; set; }

  [DisplayName("Issue Name")]
  [Required(ErrorMessage = "The issue name is required")]
  public string Name { get; set; }

  [DisplayName("Issue Description")]
  [Required(ErrorMessage = "The issue description is required")]
  public string Description { get; set; }
}

All the properties in the model have been labeled with attributes that clearly state their intention. Some properties, such as Name and Description, have been marked as required, and the Id property has been marked to require a value within a given range. The DisplayName attribute is not used for validation but affects how the output messages are rendered.

Querying the Validation Results

Once the model binding infrastructure validates a model based on the attributes that were defined on it, the results will become available to be used by a controller implementation or in a more centralized manner with a filter.

Adding a few lines in an action implementation is by far the simplest way to check whether the model has been correctly bound and all the validation results were successful (see Example 13-21).

Example 13-21. Checking the validation results in an action implementation
public class ValidationError
{
  public string Name { get; set; }
  public string Message { get; set; }
}

public class IssueController : ApiController
{
  public HttpResponseMessage Post(Issue product)
  {
    if(!this.ModelState.IsValid)
    {
       var errors = this.ModelState // <1>
                .Where(e => e.Value.Errors.Count > 0)
                .Select(e => new ValidationError // <2>
                {
                    Name = e.Key,
                    Message = e.Value.Errors.First().ErrorMessage
                }).ToArray();

        var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
        response.Content = new ObjectContent<ValidationError[]>(errors,
                new JsonMediaTypeFormatter());

        return response;
    }

    // Do something
  }
}

As illustrated in Example 13-21, all the validation results become available in a controller action through the ModelState property <1>. In that example, the action simply converts all the validation errors into a model class, ValidationError <2>, that we can serialize into the response body as JSON, and returns that with a Bad Request status code.

This represents some generic code that you might want to reuse in multiple actions, so probably the best way to do that is to move it to a custom filter. Example 13-22 shows the same code in a filter implementation.

Example 13-22. ActionFilterAttribute implementation for doing validations
public class ValidationActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
          var errors = this.ModelState
                .Where(e => e.Value.Errors.Count > 0)
                .Select(e => new ValidationError
                {
                    Name = e.Key,
                    Message = e.Value.Errors.First().ErrorMessage
                }).ToArray();

          var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
          response.Content = new ObjectContent<ValidationError[]>(errors,
                  new JsonMediaTypeFormatter());

          actionContext.Response = response;
        }
    }
}

As you can see, the filter implementation is quite simple as well. When the filter detects that the model is not valid, the execution pipeline is automatically interrupted and a new response is sent to the consumer with the validation errors. If we send, for example, a message with an empty product name and invalid unit price, we will get the response shown in Example 13-23.

Example 13-23. Invalid request message and corresponding response message
Request Message in JSON

POST http://../Isssues HTTP/1.1
Content-Type: application/json

{
  Id: 1,
  "Name":"",
  "Description": "My issue"
}

Response Message

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8

[{
  "Message": "The Issue Name is required.",
  "Name": "Name"
}]

Conclusion

The model binding infrastructure acts as a mapping layer between HTTP messages and object instances known as models. It mainly relies on HttpParameterBinding components for doing the parameter binding to different HTTP message parts like headers, query strings, or the body text. Two main implementations of HttpParameterBinding are shipped out of the box in the framework: a ModelBindingParameterBinder implementation that uses the traditional binding mechanism brought from ASP.NET MVC (in which the models are composed from small pieces found in the HTTP messages), and a FormatterParameterBinder that uses formatters to convert a media type format to a model.

The ModelBindingParameterBinder implementation uses IValueProvider instances for collecting values from different parts in a HTTP message, and IModelBinder instances to compose all those values into a single model.

The FormatterParameterBinder implementation is a fundamental piece of content negotiation, as it understands how to transform the body of an HTTP message expressed with the semantics rules and format of a given media type into a model using formatters. A formatter derives from the base class MediaTypeFormatter and typically knows how to manage a single media type. In addition to parameter binding, the model binding infrastructure also offers an extensibility point for validating the models once they are deserialized. The models are validated out of the box through rules defined as data annotation attributes.

Get Designing Evolvable Web APIs with ASP.NET now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.