CHAPTER 17

image

Binding Complex Data Types Part II

In this chapter, I conclude the coverage of the parameter and model binding processes by explaining how media type formatters can be used to deserialize complex types from the request body. I show you how to perform deserialization with a custom media type formatter and then detail how the built-in formatters work, including how to send data in the required format from the client. I finish this chapter by showing you how to replace the class that is responsible for integrating the behavior I have been describing since Chapter 15 with a custom implementation. Table 17-1 summarizes this chapter.

Table 17-1. Chapter Summary

Problem

Solution

Listing

Deserialize a custom data format.

Create a media type formatter and override the ReadFromStreamAsync method.

1–6

Register a custom media type formatter.

Add or insert an instance of the formatter class to the HttpConfiguration.Formatters collection.

7

Process URL-encoded data.

Target the FormUrlEncodedMediaTypeFormatter or JQueryMvcFormUrlEncodedFormatter media type formatters.

8–11

Instantiate difficult types from URL-encoded data.

Derive a custom class from the FormUrlEncodedMediaTypeFormatter class and override the ReadFromStreamAsync method to read the data and bind the object.

12–15

Process JSON-encoded data.

Target the JsonMediaTypeFormatter media type formatter.

16

Instantiate difficult types from JSON-encoded data.

Derive a custom class from the MediaTypeFormatter class and use the Json.Net library directly.

17–18

Process XML-encoded data.

Apply the DataContract and DataMember attributes to the model class and target the XmlMediaTypeFormatter media type formatter.

19–20

Instantiate difficult types from XML-encoded data.

Derive a custom class from the MediaTypeFormatter class and use LINQ to XML to process the data.

21–22

Change the entire parameter and model binding processes.

Create a custom implementation of the IActionValueBinder interface, either by implementing the interface directly or by deriving from the DefaultActionValueBinder class.

23–26

Preparing the Example Project

I am going to continue using the ExampleApp project I have been working with throughout this part of the book, but I need to make some preparatory changes for this chapter. First I need to simplify the Numbers class so that it has a parameterless constructor and settable properties—changes that are required so that I can demonstrate the default behavior. Listing 17-1 shows the changes I made, which include removing the ModelBinder and TypeConverter attributes that I applied in Chapter 16. I have left the constructor with parameters in place so that I can demonstrate dealing with objects that require special handling.

Listing 17-1. Simplifying the Class in the BindingModels.cs File

namespace ExampleApp.Models {

    public class Numbers {

        public Numbers() { /* do nothing */ }

        public Numbers(int first, int second) {
            First = first; Second = second;
        }

        public int First { get; set; }
        public int Second { get; set; }
        public Operation Op { get; set; }
        public string Accept { get; set; }
    }

    public class Operation {
        public bool Add { get; set; }
        public bool Double { get; set; }
    }
}

Image Tip  Remember that you don’t have to create the example project yourself. You can download the source code for every chapter for free from Apress.com.

I need to remove the model binding configuration from the WebApiConfig.cs file. The features that I will describe in this chapter do not use value providers or model binders. Listing 17-2 shows the revised configuration file.

Listing 17-2. Simplifying the WebApiConfig.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.ValueProviders;
using ExampleApp.Infrastructure;
using ExampleApp.Models;

namespace ExampleApp {
    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {

            config.DependencyResolver = new NinjectResolver();

            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "Binding Example Route",
                routeTemplate: "api/{controller}/{action}/{first}/{second}"
            );

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Services.Add(typeof(ValueProviderFactory),
                new HeaderValueProviderFactory());

            config.Services.Insert(typeof(ModelBinderProvider), 0,
                new SimpleModelBinderProvider(typeof(Numbers), new NumbersBinder()));
        }
    }
}

The final change is to the bindings.js file so that the jQuery Ajax request uses standard data encoding and uses the POST verb. Listing 17-3 shows the changes.

Listing 17-3. Changing the Request Verb and Data Format in the bindings.js File

var viewModel = ko.observable({
    first: 2, second: 5, "op.add": true, "op.double": true
});
var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function (requestType) {
    $.ajax("/api/bindings/sumnumbers", {
        type: "POST",
        data: viewModel(),
        success: function (data) {
            gotError(false);
            response("Total: " + data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

$(document).ready(function () {
    ko.applyBindings();
});

I also want to change the SumNumbers action method in the Bindings controller so that it returns an int result. I changed the result to a string so that I could include the value of the Accept header, but I only need to report the result of the calculation performed by the SumNumbers action method in this chapter. Listing 17-4 shows the change to the action method.

Listing 17-4. Changing the Action Method Result in the BindingsController.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using ExampleApp.Models;
using ExampleApp.Infrastructure;

namespace ExampleApp.Controllers {
    public class BindingsController : ApiController {
        private IRepository repo;

        public BindingsController(IRepository repoArg) {
            repo = repoArg;
        }

        [HttpGet]
        [HttpPost]
        public int SumNumbers(Numbers numbers) {
            var result = numbers.Op.Add ? numbers.First + numbers.Second
                : numbers.First - numbers.Second;
            return numbers.Op.Double ? result * 2 : result;
        }
    }
}

Testing the Application

To test the changes, start the application and use the browser to navigate to the /Home/Bindings URL. When you click the Send Request button, the client will display the result, which will be twice the sum of the values in the input elements, as shown in Figure 17-1.

9781484200865_Fig17-01.jpg

Figure 17-1. Testing the application preparation

Creating a Custom Media Type Formatter

By default, Web API assumes that any complex type parameter will be obtained from the request body and uses a media type formatter to try to get and bind a value. I introduced media type formatters in Chapter 12 and described the built-in JSON and XML media type formatters in Chapter 13, but my focus in both chapters was on using them to serialize data from action method results so that it could be sent to the client. Media type formatters are also able to deserialize data and use it to create instances of the classes required to invoke an action method.

Image Tip  As a reminder, the simple types are the TimeSpan, DateTime, or Guid object or any one of the .NET primitive types: string, char, bool, int, uint, byte, sbyte, short, ushort, long, ulong, float, double, and decimal. Any other type—and that includes arrays and collections of simple types—is a complex type.

In Chapter 12, I created the ProductFormatter class, which was responsible for formatting a Product object into a string like this:

1,Kayak,275.0

The three comma-separated values represented the ProductId, Name, and Price properties defined by the Product model object. In this section, I am going to return to this data format and create a media type formatter that can deserialize it to create Numbers objects. Table 17-2 puts creating a custom media type formatter to deserialize data into context.

Table 17-2. Putting Custom Media Type Formatters in Context

Question

Answer

What are they?

Custom media types can be used to deserialize data from the request body that is in a bespoke or unusual encoding format. Custom media formatters are also useful for dealing with classes that cannot be instantiated by invoking a parameterless constructor and setting properties.

When should you use them?

The built-in media type formatters, which I describe later in this chapter, support the most commonly encountered data format, but custom formatters are useful for dealing with classes that have to be instantiated in a specific way or when you need to support legacy clients that don’t use a common data format.

What do you need to know?

Media type formatters don’t have to be able to serialize data, which I described in Chapter 12. Instead, you can create formatters that just deserialize data, which is helpful when your application sends out data in a standard format but needs to work around poor encoding sent by clients.

Preparing the Client

The first change I need to make is to change the Ajax request that jQuery sends so that the data is in the expected format and that the Content-Type header is correctly set, as shown in Listing 17-5.

Listing 17-5. Changing the Ajax Request in the bindings.js File

var viewModel = ko.observable({
    first: 2, second: 5, add: true, double: true
});
var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function (requestType) {
    $.ajax("/api/bindings/sumnumbers", {
        type: "POST",
        contentType: "application/x.product",
        data: [viewModel().first, viewModel().second, viewModel().add,
               viewModel().double].join(),
        success: function (data) {
            gotError(false);
            response("Total: " + data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

$(document).ready(function () {
    ko.applyBindings();
});

I have used the contentType setting to specify that the content is in my custom application/x.product encoding (the MIME type I used in Chapter 12) and created a formatted string for the data setting by creating an array with the values from the view model and by calling the join method, which combines the values with a comma separator. If you start the application and send a request, you will see the following URL in the F12 tools:

/api/bindings/sumnumbers

It has a payload of the following:

2,5,true,true

The web service will report an error because I have not implemented the deserialization support.

Creating the Media Type Formatter

Now that I have a request I can process, it is time to create the custom media type formatter. I added a class file called XNumbersFormatter.cs to the Infrastructure folder and used it to create the media type formatter shown in Listing 17-6.

Listing 17-6. The Contents of the XNumbersFormatter.cs File

using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using ExampleApp.Models;

namespace ExampleApp.Infrastructure {
    public class XNumbersFormatter : MediaTypeFormatter {
        long bufferSize = 256;

        public XNumbersFormatter() {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x.product"));
        }

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

        public override bool CanReadType(Type type) {
            return type == typeof(Numbers);
        }

        public async override Task<object> ReadFromStreamAsync(Type type,
            Stream readStream, HttpContent content, IFormatterLogger formatterLogger) {

            byte[] buffer = new byte[Math.Min(content.Headers.ContentLength.Value,
                bufferSize)];
            string[] items = Encoding.Default.GetString(buffer, 0,
                await readStream.ReadAsync(buffer, 0, buffer.Length)).Split(',', '='),

            if (items.Length == 4) {
                return new Numbers(
                    GetValue<int>("First", items[0], formatterLogger),
                    GetValue<int>("Second", items[1], formatterLogger)) {

                    Op = new Operation {
                        Add = GetValue<bool>("Add", items[2], formatterLogger),
                        Double = GetValue<bool>("Double", items[3], formatterLogger)
                    }
                };
            } else {
                formatterLogger.LogError("", "Wrong Number of Items");
                return null;
            }
        }

        private T GetValue<T>(string name, string value, IFormatterLogger logger) {
            T result = default(T);
            try {
                result = (T)System.Convert.ChangeType(value, typeof(T));
            } catch {
                logger.LogError(name, "Cannot Parse Value");
            }
            return result;
        }
    }
}

Image Note  I am not going to build on the ProductFormatter class I created in Chapter 12 because it is specific to the Product model class and because I made all sorts of additions to demonstrate different features, ending up with an overly complicated class. I want to focus on just deserializing data in this chapter, so I have created a new class to keep the example simple.

This formatter binds instances of the Numbers class for requests whose Content-Type header is application/x.product. In the sections that follow, I’ll break down each part of the class and explain how it works.

Defining the Formatter Structure

The MediaTypeFormatter class is abstract and requires only the CanWriteType and CanReadType methods to be implemented—but there are two other steps required to create a working media type formatter. First you need to add the MIME type that you want to support to the SupportedMediaTypes collection in the class constructor, as follows:

...
public XNumbersFormatter() {
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x.product"));
}
...

For my implementations of the CanWriteType and CanReadType methods, I want to tell Web API that I am not willing to serialize any data and that I can deserialize only Numbers objects.

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

public override bool CanReadType(Type type) {
    return type == typeof(Numbers);
}
...

Getting the Request Data

Media type formatters that deserialize data override the ReadFromStreamAsync method, and that is where the bulk of the work is done in the XNumbersFormatter class. The ReadFromStreamAsync method is passed arguments of the types described in Table 17-3 and is responsible for using them to instantiate an object.

Table 17-3. The Parameter Types of the MediaTypeFormatter.ReadFromStreamAsync Method

Type

Description

Type

The type that the formatter is required to instantiate. This is useful if the implementation of the CanReadType method responds with true to multiple types.

Stream

A System.IO.Stream object from which the request body can be read. This must be used cautiously; see the warnings and examples that follow this table.

HttpContent

A System.Net.Http.HttpContent object that describes the request content and provides access to it. This object is used to gain access to an HttpContentHeaders object through its Headers property. See Table 17-4 for details of the headers that are available.

IFormatterLogger

An implementation of the System.Net.Http.Formatting.IFormatterLogger interface that can be used to report problems processing the data. See the “Creating the Model Object” section.

Table 17-4. The Request Header Convenience Properties Defined by the HttpContentHeaders Class

Name

Description

ContentEncoding

Returns the value of the Content-Encoding header, which is used to indicate when additional encodings have been applied to the content in addition to the one specified by the Content-Type header.

ContentLength

Returns the value of the Content-Length header, which reports the size of the request body in bytes. When using the value of the Content-Length header, be sure to apply an upper limit to how much data you read from the request body; see the following text for details.

ContentMD5

Returns the value of the Content-MD5 header, which contains a hash code to ensure the integrity of the data.

ContentType

Returns the value of the Content-Type header, which specifies the primary encoding of the request body. Additional encodings can be specified with the Content-Encoding header.

The HttpContent class provides information about the request and provides methods that can be used to read the request body, but the ReadFromStreamAsync method is required to read data from the Stream object it receives as an argument. This means the value of the HttpContent object is its Headers property, which returns an instance of the HttpContentHeaders class. The HttpContentHeaders class is derived from HttpHeaders and adds convenience properties for content-related headers, as described in Table 17-4. (The HttpContentHeaders class contains properties that are used in responses and requests. I have included the request headers only in the table.)

Image Tip  See www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for the detailed specification and use of HTTP headers.

For my purposes, it is the ContentLength property that is most useful because it tells me how much data I need to read from the Stream argument to the ReadFromStreamAsync method. Here is the code that reads the body of the request and converts it into an array of strings:

...
byte[] buffer = new byte[Math.Min(content.Headers.ContentLength.Value, bufferSize)];
string[] items = Encoding.Default.GetString(buffer, 0,
    await readStream.ReadAsync(buffer, 0, buffer.Length)).Split(',', '='),
...

These statements follow three important rules that you should follow when writing a custom media type formatter:

  • Don’t use the convenience methods to read basic types.
  • Limit the amount of data you read.
  • Read data asynchronously.

A simple but effective denial-of-service attack is for a client to send an HTTP server misleading information in the Content-Length header, either to cause an error or to try to get the server to exhaust its memory by reading enormous amounts of data. A custom media type formatter requires care because the content is read and processed by your code, rather than that of the ASP.NET Framework as is the case in an MVC application.

The first precaution you should take is to avoid using the convenience methods provided by the stream and reader classes in the System.IO namespace. Using a ReadLine or ReadString method would allow me to simplify my media type formatter, but these methods just keep reading data from the underlying stream until they get the data they expect. Instead, you should read the content into a byte[] buffer directly from the stream.

The second precaution you should take is to limit the amount of data you read from the stream. In Listing 17-6, I defined a maximum buffer size of 256 bytes, which is enough to represent my Numbers class. When I create the byte array, I set the size of the array using the Content-Length header only if it is smaller than 256 (and I ignore negative Content-Length headers, which are sent to generate an error, although this is largely habit.

...
byte[] buffer = new byte[Math.Min(content.Headers.ContentLength.Value, bufferSize)];
...

Image Tip  You don’t have to guard against negative Content-Length header values, which used to be a popular attack. Basic validation is performed on the headers when the request is processed and requests with illegal headers are rejected by ASP.NET.

The final rule you should follow is to read data asynchronously from the Stream to maximize the request throughput of the web service. I used the Stream.ReadAsync method to read the request body.

...
string[] items = Encoding.Default.GetString(buffer, 0,
    await readStream.ReadAsync(buffer, 0, buffer.Length)).Split(',', '='),
...

I have used the await keyword for my read operation, and that is why I have added the async keyword to the ReadFromStreamAsync method.

Creating the Model Object

Once I have the data from the request body, I can use it to create an instance of the Numbers class, which is handled by this statement:

...
return new Numbers(
    GetValue<int>("First", items[0], formatterLogger),
    GetValue<int>("Second", items[1], formatterLogger)) {

    Op = new Operation {
        Add = GetValue<bool>("Add", items[2], formatterLogger),
        Double = GetValue<bool>("Double", items[3], formatterLogger)
    }
};
...

I have used the Numbers constructor that takes parameters, just to show that you can instantiate objects in any way you need when writing a custom media type formatter. I get the values I require for the constructor parameters and the Operation properties through a method called GetValue, which I defined to let me take advantage of C# generic types so that I can easily convert string values to different types.

...
private T GetValue<T>(string name, string value, IFormatterLogger logger) {
    T result = default(T);
    try {
        result = (T)System.Convert.ChangeType(value, typeof(T));
    } catch {
        logger.LogError(name, "Cannot Parse Value");
    }
    return result;
}
...

The caller specifies the type that is required using the generic type parameter T, and I use the System.Convert.ChangeType method to perform the conversion. The important part of the GetValue method is the use of the IFormatterLogger parameter object, which is used to record any problems processing the request data to create the model object. The default implementation of the IFormatterLogger interface adds errors to the model state, which is part of the model validation process I describe in Chapter 18. The IFormatterLogger interface defines the methods I have listed in Table 17-5.

Table 17-5. The Methods Defined by the IFormatterLogger Interface

Method

Description

LogError(property, message)

Records an error for the specified property. The error is described by a string message.

LogError(property, exception)

Records an error for the specified property. The error is described by an Exception.

In the listing, I note any problems parsing values, but I still return the default value for the required type. This will make more sense once I describe the model validation process in Chapter 18.

Registering and Testing the Media Type Formatter

Having created the custom media type formatter (and explained how it works), I can tell Web API to start using it to deserialize Numbers objects for requests that use my application/x.product format. Listing 17-7 shows the change I made to the WebApiConfig.cs file to register the XNumbersFormatter class.

Listing 17-7. Registering a Media Type Formatter in the WebApiConfig.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.ValueProviders;
using ExampleApp.Infrastructure;
using ExampleApp.Models;

namespace ExampleApp {
    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {

            config.DependencyResolver = new NinjectResolver();

            // ...routing statements omitted for brevity...

            config.Services.Add(typeof(ValueProviderFactory),
                new HeaderValueProviderFactory());

            config.Services.Insert(typeof(ModelBinderProvider), 0,
                new SimpleModelBinderProvider(typeof(Numbers), new NumbersBinder()));

            config.Formatters.Add(new XNumbersFormatter());
        }
    }
}

For variety, I am going to test the custom formatter with Postman. Set the URL to http://localhost:29844/api/bindings/sumnumbers (replacing 29844 with your application’s port number), the request type to POST, and the Content-Type header to application/x.product.

Click the Raw button to specify a request body that won’t be formatted by Postman and enter the following into the text box:

100,150,true,false

Click the Send button, and Postman will send the request to the web service, which will return the following result (the previous values specified that the web service should add 100 and 150 but not double the result):

250

Using the Built-in Media Type Formatters

The default behavior for a complex type parameter is to act as though the FromBody attribute has been applied. I introduced the FromBody attribute in Chapter 14 when I showed you how to use it to force Web API to look in the request body for a value that it would otherwise try to obtain it from the URL. Behind the scenes, the FromBody attribute is used to select a media type formatter that can process the body of a request based on the MIME type. Web API comes with four built-in media type formatters enabled by default, which I have listed in Table 17-6.

Table 17-6. The Built-in Media Type Formatters

MIME Types

Media Type Formatter

application/json, text/json

JsonMediaTypeFormatter

application/xml, text/json

XmlMediaTypeFormatter

application/x-www-form-urlencoded

FormUrlEncodedMediaTypeFormatter

application/x-www-form-urlencoded

JQueryMvcFormUrlEncodedFormatter

Image Tip  There is an additional media type formatter for the BSON data format, which is disabled by default and which I am not describing in this book because BSON isn’t widely used.

When a request arrives, the MIME type in the Content-Type header selects the media type formatter that can handle that type. In the sections that follow, I explain how each formatter works and show you how to format the client request data to target them. Table 17-7 puts binding complex types with the built-in media type formatters in context.

Table 17-7. Putting Binding Complex Types with the Built-in Media Type Formatters in Context

Question

Answer

What are they?

The built-in formatters support the three most commonly used data formats for a web service. They are used by default but can be supplemented or replaced by custom media type formatters.

When should you use them?

The built-in media type formatters are used by default and are suitable for dealing with requests that lead to the instantiation of classes with parameterless constructors and settable properties.

What do you need to know?

It is usually a simple matter to create a custom media type formatter that deals with inconsistent or incorrectly formatted data sent by a client—or to deal with classes that must be instantiated carefully.

Handling URL-Encoded Data

If you use jQuery to write your application client, then you will usually end up dealing with form-encoded data because it is the default format that jQuery uses when sending an Ajax request. As Table 17-7 shows, two media type formatters can handle the application/x-www-form-urlencoded MIME type: FormUrlEncodedMediaTypeFormatter and JQueryMvcFormUrlEncodedFormatter. I explain the relationship between them and how each of them works in the sections that follow.

SELECTING OTHER DATA FORMATS

I have covered the media type formatter that deals with URL-encoded data first because it is the format that you are most likely to encounter as an MVC framework developer and you are in a position to write your own web service clients. It is the default encoding used by jQuery, and it is well-understood and supported.

The other data formats are important if you need to support clients that you have not written yourself or that are not browser-based. Just about every web-connected platform can create and process JSON or XML data, and by supporting these formats, you broaden the kinds of clients that can consume your web service.

I recommend making a deliberate decision about the data formats you support. Each additional format requires testing and maintenance and adds to the burden of building and running the web service. I recommend starting with URL-encoded and JSON data and enabling XML only if you can’t deliver your web service without it. See Chapter 13 for details of how to disable media type formatters.

Handling URL-Encoded Requests

The FormUrlEncodedMediaTypeFormatter class can bind only to an instance of the FormDataCollection class, which is defined in the System.Net.Http.Formatting namespace and which presents form-encoded data as a collection of name-value pairs.

The real value of the FormUrlEncodedMediaTypeFormatter class is that it provides a foundation for creating formatters that handle more useful types, such as the JQueryMvcFormUrlEncodedFormatter class that I describe in the next section.

I need to change the data format for the Ajax request that jQuery makes in order to target the FormUrlEncodedMediaTypeFormatter class, as shown in Listing 17-8.

Listing 17-8. Changing the Request Format in the bindings.js File

var viewModel = ko.observable({
    first: 2, second: 5, add: true, double: true
});
var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function (requestType) {
    $.ajax("/api/bindings/sumnumbers", {
        type: "POST",
        data: viewModel(),
        success: function (data) {
            gotError(false);
            response("Total: " + data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

$(document).ready(function () {
    ko.applyBindings();
});

The changes to the client-side code are minor because jQuery sends URL-encoded data by default. Listing 17-9 shows the change I made to the action method to receive that data using a FormDataCollection object.

Listing 17-9. Receiving Request Data in the BindingsController.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using ExampleApp.Models;
using ExampleApp.Infrastructure;
using System.Net.Http.Formatting;

namespace ExampleApp.Controllers {
    public class BindingsController : ApiController {
        private IRepository repo;

        public BindingsController(IRepository repoArg) {
            repo = repoArg;
        }

        [HttpGet]
        [HttpPost]
        public IHttpActionResult SumNumbers(FormDataCollection numbers) {

            int first, second;
            bool add, doubleVal;

            if (int.TryParse(numbers["first"], out first)
                && int.TryParse(numbers["second"], out second)
                && bool.TryParse(numbers["add"], out add)
                && bool.TryParse(numbers["double"], out doubleVal)) {

                int result = add ? first + second : first - second;
                return Ok(string.Format("{0}", doubleVal ? result * 2 : result));

            } else {
                return BadRequest();
            }
        }
    }
}

I have to take responsibility for converting the form data values into the types I require to do the work in the action method, which in this case means using the int.TryParse and bool.TryParse methods to convert form data values (which are expressed as string values) to int and bool types. If I receive all of the data values I need—and I can convert them to the types I need—then I perform the calculation and return the result using the Ok method, which I introduced in Chapter 11. If I don’t get the values I need, then I use the BadRequest method to send the client a 400 (Bad Request) response.

Image Note  You won’t often rely on the FormUrlEncodedMediaTypeFormatter class directly because the other built-in media type formatters can bind to .NET classes, but I have included the details for completeness and because I use this formatter when showing you how to customize the binding process.

Creating Complex Types from URL-Encoded Requests

The JQueryMvcFormUrlEncodedFormatter class is derived from FormUrlEncodedMediaTypeFormatter and adds support for binding values to complex types, which is a lot more useful than working with the FormDataCollection class.

Behind the scenes, the JQueryMvcFormUrlEncodedFormatter class uses extension methods defined in the System.Web.Http.ModelBinding.FormDataCollectionExtensions class to create objects using the model binding feature that I described in Chapter 16. Unfortunately, the FormDataCollectionExtensions extension methods are written to use only the built-in media formatters and value providers, preventing the use of custom classes and limiting the range of types that can be bound from application/x-www-form-urlencoded data to classes with parameterless constructors and settable properties, arrays, lists, and key-value pairs.

Image Tip  The name of the JQueryMvcFormUrlEncodedFormatter class reflects the fact that the property names are ­converted from the jQuery default to that used by the MVC framework.

Using model binders also means that the data sent by the client needs to be structured with prefixes in order to be properly processed. Listing 17-10 shows the changes I made to the view model in the bindings.js file.

Listing 17-10. Adding Prefixes to the View Model in the bindings.js File

...
var viewModel = ko.observable({
    first: 2, second: 5,
    "op.add": true, "op.double": true
});
...

If I had not prefixed add and double with op, then the media type formatter would not have assigned values to the Op and Add properties of the Numbers object it creates. Listing 17-11 shows the changes I made to the action method in the Bindings controller to use the JQueryMvcFormUrlEncodedFormatter formatter.

Listing 17-11. Changing the Action Method Parameter in the BindingsController.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using ExampleApp.Models;
using ExampleApp.Infrastructure;
using System.Net.Http.Formatting;

namespace ExampleApp.Controllers {
    public class BindingsController : ApiController {
        private IRepository repo;

        public BindingsController(IRepository repoArg) {
            repo = repoArg;
        }

        [HttpGet]
        [HttpPost]
        public int SumNumbers(Numbers numbers) {
            var result = numbers.Op.Add ? numbers.First + numbers.Second
                : numbers.First - numbers.Second;
            return numbers.Op.Double ? result * 2 : result;
        }
    }
}

It is this change—specifying a parameter that is a complex type but not the FormDataCollection class—that causes Web API to select the JQueryMvcFormUrlEncodedFormatter class instead of the FormUrlEncodedMediaTypeFormatter class.

Image Tip  You might be wondering how the JQueryMvcFormUrlEncodedFormatter class is able to use the model binding system when the built-in value providers read values from the URL. The answer is that the NameValuePairsValueProvider class is used behind the scenes. This class takes an arbitrary set of key-value pairs and presents them through the IValueProvider interface. The NameValuePairsValueProvider class is the superclass of the QueryStringValueProvider and RouteDataValueProvider classes, which get their key-value pairs from the query string and routing data, respectively. The JQueryMvcFormUrlEncodedFormatter works directly with the NameValuePairsValueProvider class and gets its key-value pairs from the FormUrlEncodedMediaTypeFormatter class, which decodes the URL-encoded request body.

Instantiating Difficult Types Using URL-Encoded Data

Although the JQueryMvcFormUrlEncodedFormatter doesn’t allow the use of custom model binders, I can use the FormUrlEncodedMediaTypeFormatter class to create a custom media type formatter that does—and that means I can instantiate classes that require special handling, such as those with constructor parameters. Listing 17-12 shows the contents of a class file called UrlNumbersFormatter.cs that I added to the Infrastructure folder and used to create a media type formatter that can instantiate the Numbers class using the constructor with parameters.

Listing 17-12. The Contents of the UrlNumbersFormatter.cs File

using System;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.ModelBinding;
using System.Web.Http.ValueProviders.Providers;
using ExampleApp.Models;

namespace ExampleApp.Infrastructure {
    public class UrlNumbersFormatter : FormUrlEncodedMediaTypeFormatter {

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

        public override bool CanReadType(Type type) {
            return type == typeof(Numbers);
        }

        public override async Task<object> ReadFromStreamAsync(Type type,
            Stream readStream, HttpContent content, IFormatterLogger formatterLogger) {

            FormDataCollection fd = (FormDataCollection)
                await base.ReadFromStreamAsync(typeof(FormDataCollection),
                    readStream, content, formatterLogger);

            HttpActionContext actionContext = new HttpActionContext { };

            ModelMetadata md = GlobalConfiguration.Configuration
                .Services.GetModelMetadataProvider().GetMetadataForType(null, type);

            ModelBindingContext bindingContext = new ModelBindingContext {
                ModelMetadata = md,
                ValueProvider = new NameValuePairsValueProvider(fd,
                    CultureInfo.InvariantCulture)
            };

            if (new NumbersBinder().BindModel(actionContext, bindingContext)) {
                return bindingContext.Model;
            }
            return null;
        }
    }
}

The ReadFromStreamAsync method calls the base implementation to create a FormDataCollection object, which I then pass to an instance of the NameValuePairsValueProvider class. The NameValuePairsValueProvider class implements the IValueProvider interface and allows me to take values from the body and feed them into the custom model binder I created in Chapter 16.

Many of the rest of the statements in the ReadFromStreamAsync method prepare the context objects that I need in order to use the model binder. I need HttpActionContext and ModelBindingContext objects to call the BindModel method of an IModelBinder implementation, but I need to provide only the context that the binder relies on. For my NumbersBinder class, that means I can instantiate an HttpActionContext object without needing to set any properties. For the ModelBindingContext, I assign the NameValuePairsValueProvider object to the ValueProvider property. I also have to set the ModelMetadata property because it is checked by the ModelBindingContext class when the Model property is set by the model binder class. I describe the ModelMetadata class in Chapter 18, but for the moment it is enough to know that I get the ModelMetadata instance from the services collection.

Once I have creating the context objects, I can instantiate the model binder class and call the BindModel method. The BindModel method returns true when it is able to bind an object, and when I get that result, I return the value of the BindingContext.Model property.

The reason that the built-in JQueryMvcFormUrlEncodedFormatter media type formatter doesn’t allow custom binders and value providers is because the problems that feeding data from the body to model binders can cause. For my custom model binder, there are two problems that I need to resolve, and you can see the changes that I made to the NumbersBinder class in Listing 17-13.

Listing 17-13. Adapting a Model Binder in the NumbersBinder.cs File

using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Web.Http.ValueProviders;
using ExampleApp.Models;

namespace ExampleApp.Infrastructure {

    public class NumbersBinder : IModelBinder {

        public bool BindModel(HttpActionContext actionContext,
                ModelBindingContext bindingContext) {

            string modelName = bindingContext.ModelName;

            Dictionary<string, ValueProviderResult> data
                = new Dictionary<string, ValueProviderResult>();

            data.Add("first", GetValue(bindingContext, modelName, "first"));
            data.Add("second", GetValue(bindingContext, modelName, "second"));
            data.Add("add", GetValue(bindingContext, modelName, "op", "add"));
            data.Add("double", GetValue(bindingContext, modelName, "op", "double"));
            data.Add("accept", GetValue(bindingContext, modelName, "accept"));

            if (data.All(x => x.Key == "accept" || x.Value != null)) {
                bindingContext.Model = CreateInstance(data);
                return true;
            }
            return false;
        }

        private ValueProviderResult GetValue(ModelBindingContext context,
                params string[] names) {

            for (int i = 0; i < names.Length - 1; i++) {
                string prefix = string.Join(".",
                    names.Skip(i).Take(names.Length - (i + 1)));
                if (prefix != string.Empty &&
                        context.ValueProvider.ContainsPrefix(prefix)) {
                    return context.ValueProvider.GetValue(prefix + "." + names.Last());
                }
            }
            return context.ValueProvider.GetValue(names.Last());
        }

        private Numbers CreateInstance(Dictionary<string, ValueProviderResult> data) {
            // ...statements omitted for brevity...
        }

        private T Convert<T>(ValueProviderResult result) {
            // ...statements omitted for brevity...
        }
    }
}

I need to fix two problems. The first is in the BindModel method, when I check to see whether I have been able to obtain values for all the properties and constructor parameters I need to set. The Numbers class defines the Accept property, which I have been setting using data from the request header. Media type formatters don’t have access to the request, and there is no global property that provides access to it, unlike in the MVC framework. To get the model binder working with the media type formatter, I have to relax the check that I perform to exclude the value for the Accept value.

...
if (data.All(x => x.Key == "accept" || x.Value != null)) {
    bindingContext.Model = CreateInstance(data);
    return true;
}
...

The second problem is that binders use the parameter name, which is available through the ModelBindingContext.ModelName property. Media type formatters don’t have access to details about which parameter they are being used to bind and can’t provide the model binder with the name. To make my custom model binder work in this situation, I have added support for working with an empty string as the model name, which is the value that the binder is presented with from the ModelBindingContext that I created in the UrlNumbersFormatter class.

...
if (prefix != string.Empty && context.ValueProvider.ContainsPrefix(prefix)) {
...

Now that the model binder can be used by the media type formatter, all that remains is to register the UrlNumbersFormatter class with Web API, as shown in Listing 17-14.

Listing 17-14. Registering a Media Type Formatter in the WebApiConfig.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.ValueProviders;
using ExampleApp.Infrastructure;
using ExampleApp.Models;

namespace ExampleApp {
    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {

            config.DependencyResolver = new NinjectResolver();

            config.MapHttpAttributeRoutes();

            // ...routing statements omitted for brevity...

            config.Services.Add(typeof(ValueProviderFactory),
                new HeaderValueProviderFactory());

            config.Services.Insert(typeof(ModelBinderProvider), 0,
                new SimpleModelBinderProvider(typeof(Numbers), new NumbersBinder()));

            config.Formatters.Add(new XNumbersFormatter());
            config.Formatters.Insert(0, new UrlNumbersFormatter());
        }
    }
}

Notice that I have used the Insert method to register the UrlNumbersFormatter class. Media type formatters are queried in the order in which they are placed in the HttpConfiguration.Formatters collection, and this means I must ensure that my custom formatter appears before the JQueryMvcFormUrlEncodedFormatter class if I want it to deserialize the request data.

Simplifying the Custom Media Type Formatter

The media type formatter that I created in the previous section demonstrated how you can use model binders, which is helpful if you have already invested time and effort in the code that can instantiate difficult classes. If you don’t have a model binder that you want to use, then you can read the data values directly from the FormDataCollection object. Listing 17-15 shows how I simplified the UrlNumbersFormatter class so that it gets the data values and instantiates the Numbers class directly.

Listing 17-15. Simplifying the Media Type Formatter in the UrlNumbersFormatter.cs File

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using ExampleApp.Models;

namespace ExampleApp.Infrastructure {
    public class UrlNumbersFormatter : FormUrlEncodedMediaTypeFormatter {

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

        public override bool CanReadType(Type type) {
            return type == typeof(Numbers);
        }

        public override async Task<object> ReadFromStreamAsync(Type type,
            Stream readStream, HttpContent content, IFormatterLogger formatterLogger) {

            FormDataCollection fd = (FormDataCollection)
                await base.ReadFromStreamAsync(typeof(FormDataCollection),
                    readStream, content, formatterLogger);

            return new Numbers(
                GetValue<int>("First", fd, formatterLogger),
                GetValue<int>("Second", fd, formatterLogger)) {
                Op = new Operation {
                    Add = GetValue<bool>("Add", fd, formatterLogger),
                    Double = GetValue<bool>("Double", fd, formatterLogger)
                }
            };
        }

        private T GetValue<T>(string name, FormDataCollection fd,
                IFormatterLogger logger) {
            T result = default(T);
            try {
                result = (T)System.Convert.ChangeType(fd[name], typeof(T));
            } catch {
                logger.LogError(name, "Cannot Parse Value");
            }
            return result;
        }

    }
}

This class uses the same techniques I employed in the “Creating a Custom Media Type Formatter” section, except that I use the base class to read and parse the data from the request body.

Handling JSON Requests

The JsonMediaTypeFormatter class is responsible for deserializing content in requests that are encoded with application/json or text/json MIME types (which are equivalent—both MIME types are JSON). It relies on the excellent Json.Net package to handle the JSON data. One of the reasons that I like the Json.Net package so much is that I have found it will decode even the sloppiest JSON data, which makes it well-suited for web services that have to deal with a wide range of clients, including those written by third parties with a less than complete understanding of the JSON format. Listing 17-16 shows how I have changed the Ajax request sent by jQuery so that the data is encoded via JSON.

Listing 17-16. Changing the jQuery Request Encoding to JSON in the bindings.js File

var viewModel = ko.observable({
    first: 2, second: 5, op: { add: true, double: true }
});
var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function (requestType) {
    $.ajax("/api/bindings/sumnumbers", {
        type: "POST",
        data: JSON.stringify(viewModel()),
        contentType: "application/json",
        success: function (data) {
            gotError(false);
            response("Total: " + data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

$(document).ready(function () {
    ko.applyBindings();
});

I have altered the structure of the view model so that there is an op property that is set to an object with add and double properties. I have also used the contentType property to specify that the request contains JSON data and used the JSON.stringify method to serialize the view model object into a JSON string like this:

{"first":2,"second":5,"op":{"add":true,"double":true}}

Notice how closely the JSON representation matches the definition of the view model in the JavaScript code. The JsonMediaTypeFormatter class will be matched to the request based on the MIME type and will create an instance of the Numbers class.

UNDERSTANDING THE JSON.STRINGIFY METHOD

The JSON.stringify method takes an object and serializes it into the JSON format. The JSON object that defines the stringify method—and its counterpart, JSON.parse—isn’t part of jQuery. Instead, it is provided by the browser as part of a set of global JavaScript objects that provide commonly used functionality.

All modern browsers have a built-in implementation of JSON.stringify, but if you find yourself having to support older browsers, then you can get an implementation from https://github.com/douglascrockford/JSON-js that you can include in your application. The file is small, especially if you use the minified version. You can see which browsers have built-in support for JSON.stringify at http://caniuse.com/json.

Creating Complex Types

The Json.Net package that JsonMediaTypeFormatter relies on can instantiate classes with parameterless constructors and settable properties, in just the same way as the media type handler for URL-encoded data.

Json.Net provides many options for customizing the instantiation process based on the JSON data, including attributes that can be applied to the model class to help instantiation. You can get full details of both approaches—and the rest of the Json.Net features—at http://james.newtonking.com.

In this section, I am going to demonstrate how to use a different Json.Net feature: LINQ to JSON. I like working with LINQ and find it endlessly useful for wrangling different formats into usable data. Listing 17-17 shows the contents of the JsonNumbersFormatter.cs file that I added to the Infrastructure folder.

Listing 17-17. The Contents of the JsonNumbersFormatter.cs File

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using ExampleApp.Models;
using Newtonsoft.Json.Linq;

namespace ExampleApp.Infrastructure {
    public class JsonNumbersFormatter : MediaTypeFormatter {
        long bufferSize = 256;

        public JsonNumbersFormatter() {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
        }

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

        public override bool CanReadType(Type type) {
            return type == typeof(Numbers);
        }

        public async override Task<object> ReadFromStreamAsync(Type type,
            Stream readStream, HttpContent content, IFormatterLogger formatterLogger) {

            byte[] buffer = new byte[Math.Min(content.Headers.ContentLength.Value,
                bufferSize)];
            string jsonString = Encoding.Default.GetString(buffer, 0,
                await readStream.ReadAsync(buffer, 0, buffer.Length));

            JObject jData = JObject.Parse(jsonString);
            return new Numbers((int)jData["first"], (int)jData["second"]) {
                Op = new Operation {
                    Add = (bool)jData["op"]["add"],
                    Double = (bool)jData["op"]["double"]
                }
            };
        }
    }
}

I access the LINQ to JSON feature through this statement:

...
JObject jData = JObject.Parse(jsonString);
...

The result is an implementation of the IEnumerable<KeyValuePair<string, JToken>>, where the JToken class describes one property from the JSON string I read from the request body. You can use the standard LINQ query syntax or extension methods to process the JSON data, which is useful if you are processing an array of data.

It is more helpful to be able to access all of the JSON properties when instantiating a single object, and JSON to LINQ helpfully presents the data values through array-style indexers. In the listing, I used the indexers to get the four data values I require to create an instance of the Numbers class. Listing 17-18 shows the statement I added to the WebApiConfig.cs file to register the media type formatter.

Listing 17-18. Registering a Media Type Formatter in the WebApiConfig.cs File

using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.ValueProviders;
using ExampleApp.Infrastructure;
using ExampleApp.Models;

namespace ExampleApp {
    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {

            config.DependencyResolver = new NinjectResolver();

            // ...routing statements omitted for brevity...

            config.Services.Add(typeof(ValueProviderFactory),
                new HeaderValueProviderFactory());

            config.Services.Insert(typeof(ModelBinderProvider), 0,
                new SimpleModelBinderProvider(typeof(Numbers), new NumbersBinder()));

            config.Formatters.Add(new XNumbersFormatter());
            config.Formatters.Insert(0, new UrlNumbersFormatter());
            config.Formatters.Insert(0, new JsonNumbersFormatter());
        }
    }
}

Handling XML Requests

Dealing with XML data can be tricky because there are so many ways in which the same data can be expressed. If you have control over the clients that will consume your web service, then you should use one of the other data formats I described in this chapter. The most common need to support XML arises in a web service that has to support legacy clients, under which circumstances you will have to adapt to process whatever format—or formats—you are sent. Using the built-in XML media type serializer involves carefully formatting the data sent by the client and preparing the model class for use by the web service.

jQuery doesn’t have built-in support for generating XML data from a JavaScript object, but in Listing 17-19 you can see how I have manually formatted the data I will process in the web service.

Listing 17-19. Using jQuery to Send XML Data in the bindings.js File

var viewModel = ko.observable({
    first: 2, second: 5, op: { add: true, double: true }
});
var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function (requestType) {
    $.ajax("/api/bindings/sumnumbers", {
        type: "POST",
        data: "<Numbers>"
                + "<First>" + viewModel().first + "</First>"
                + "<Op>"
                    + "<Add>" + viewModel().op.add + "</Add>"
                    + "<Double>" + viewModel().op.double + "</Double>"
                + "</Op>"
                + "<Second>" + viewModel().second + "</Second>"
                + "</Numbers>",
        contentType: "application/xml",
        success: function (data) {
            gotError(false);
            response("Total: " + data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

$(document).ready(function () {
    ko.applyBindings();
});

Image Note  This code is messy because jQuery lacks built-in support for XML. Clients that send XML will generally have a better mechanism for creating the data they will send to the web service.

The result of the changes in Listing 17-19 is that the body of the HTTP request will contain the following XML fragment:

<Numbers>
    <First>2</First>
    <Op>
        <Add>true</Add>
        <Double>true</Double>
    </Op>
    <Second>5</Second>
</Numbers>

I find using the built-in media type formatter to be awkward because there are some important constraints on the way that XML data has to be structured. These can be worked around by changing the way the model class is configured—which I explain shortly—but these changes just create a different rigid data structure.

The first constraint is that the name and capitalization of each attribute name much exactly match the class or property name that it corresponds to. That means the top-level element must be Numbers, for example, and not numbers, and certainly not something like myNumbersXML.

The second constraint—and the one that I find the most problematic—is that the attributes must be organized in alphabetical order. This is the reason the Second attribute follows the Op attribute—because, of course, the letter O appears before S in the alphabet. I explain how to change the order of the attributes shortly, but it is possible only to create a different enforced order and not to create a more flexible approach (for that you need a custom media type formatter such as the one I describe in the next section).

The XML serializer that the built-in media type formatter uses will instantiate only the objects that have been annotated with the DataContract attribute and will set only the properties that have been decorated with the DataMember attribute. Both attributes are defined in the System.Runtime.Serialization namespace, and you can see how I have applied them to the Numbers and Operation classes in Listing 17-20.

Listing 17-20. Applying the Data Contract Attributes in the BindingModels.cs File

using System.Runtime.Serialization;

namespace ExampleApp.Models {

    [DataContract(Namespace="")]
    public class Numbers {

        public Numbers() { /* do nothing */ }

        public Numbers(int first, int second) {
            First = first; Second = second;
        }

        [DataMember]
        public int First { get; set; }
        [DataMember]
        public int Second { get; set; }
        [DataMember]
        public Operation Op { get; set; }
        public string Accept { get; set; }
    }

    [DataContract(Namespace="")]
    public class Operation {
        [DataMember]
        public bool Add { get; set; }
        [DataMember]
        public bool Double { get; set; }
    }
}

I have set the Namespace property of the DataContract attributes to the empty string ("") so that the serializer won’t expect an xmlns attribute on the top-level element of the data that is received from the client.

The DataContract and DataMember attributes are defined in an assembly that is not added to Web API projects by default. Select Add Reference from the Visual Studio Project menu, click the Framework section, and locate and check the option for the System.Runtime.Serialization assembly, as shown in Figure 17-2.

9781484200865_Fig17-02.jpg

Figure 17-2. Adding the System.Runtime.Serialization assembly

The DataMember attribute defines properties that can be used to change the way that the XML data is processed, as described in Table 17-8. The problem with these properties, however, is that they just create a different kind of rigid data structure that the client has to adhere to, and the media type formatter won’t deserialize the request if there is a mismatch between the data from the client and the format implied by the attributes.

Table 17-8. The Properties Defined by the DataMember Attribute

Name

Description

IsRequired

When true, the serializer will not deserialize the data if it does not contain a value for the property to which the attribute has been applied. Missing data feeds an error into the model state, which is used for validation. I describe model state and validation in Chapter 18. The default value is false.

Name

Set the name of the XML element from which the value for the property will be read. The default behavior is to use the name of the property.

Order

When set, this specifies the position of the element in the XML data that will be used to read a value for the property. This overrides the alphabetic order that is the default behavior.

Creating Complex Types from XML Data

When trying to instantiate classes using XML, I avoid treating the elements and attributes as a document with namespaces and schemas. Instead, I use LINQ to mine the XML data for key-value pairs. This approach has its limitations—not least that it incurs the overhead of XML without getting any of the benefits that structured data offers—but in most web services the use of XML is a legacy holdover, and the task at hand is to support XML clients with the minimum of effort. To that end, I created a class file called XmlNumbersFormatter.cs in the Infrastructure folder and used it to create the media type formatter shown in Listing 17-21.

Listing 17-21. The Contents of the XmlNumbersFormatter.cs File

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using ExampleApp.Models;

namespace ExampleApp.Infrastructure {
    public class XmlNumbersFormatter : MediaTypeFormatter {
        long bufferSize = 256;

        public XmlNumbersFormatter() {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
        }

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

        public override bool CanReadType(Type type) {
            return type == typeof(Numbers);
        }

        public async override Task<object> ReadFromStreamAsync(Type type,
            Stream readStream, HttpContent content, IFormatterLogger formatterLogger) {

            byte[] buffer = new byte[Math.Min(content.Headers.ContentLength.Value,
                bufferSize)];
            XElement xmlData = XElement.Parse(Encoding.Default.GetString(buffer, 0,
                await readStream.ReadAsync(buffer, 0, buffer.Length)));

            Dictionary<string, string> items = new Dictionary<string, string>();
            GetKvps(xmlData, items);

            if (items.Count == 4) {
                return new Numbers(
                    GetValue<int>(items["first"], formatterLogger),
                    GetValue<int>(items["second"], formatterLogger)) {
                        Op = new Operation {
                            Add = GetValue<bool>(items["add"], formatterLogger),
                            Double = GetValue<bool>(items["double"], formatterLogger)
                        }
                    };
            } else {
                formatterLogger.LogError("", "Wrong Number of Items");
                return null;
            }
        }

        private void GetKvps(XElement elem, Dictionary<string, string> dict) {
            if (elem.HasElements) {
                foreach (XElement innerElem in elem.Elements()) {
                    GetKvps(innerElem, dict);
                }
            } else {
                dict.Add(elem.Name.LocalName.ToLower(), elem.Value);
            }
        }

        private T GetValue<T>(string value, IFormatterLogger logger) {
            T result = default(T);
            try {
                result = (T)System.Convert.ChangeType(value, typeof(T));
            } catch {
                logger.LogError("", "Cannot Parse Value");
            }
            return result;
        }
    }
}

You will recognize some of the code and techniques from earlier custom media type formatters in this chapter. In this case, I read the body of the request and use the XElement.Parse method to enter the world of XML to LINQ. I enumerate the XML elements and create a dictionary of key-value pairs, which I then use to instantiate the Numbers class (using the constructor that defines parameters) and set its properties. This is less elegant than treating the XML data as a stream that is handled only once, but it has the benefit of not enforcing a rigid order in which the XML elements must appear. Listing 17-22 shows how I registered the media type formatter in the WebApiConfig.cs file.

Listing 17-22. Registering the Custom Media Type Formatter in the WebApiConfig.cs File

using System.IO;
using System.Text;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.ValueProviders;
using ExampleApp.Infrastructure;
using ExampleApp.Models;

namespace ExampleApp {
    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {

            config.DependencyResolver = new NinjectResolver();

            // ...routing statements omitted for brevity...

            config.Services.Add(typeof(ValueProviderFactory),
                new HeaderValueProviderFactory());

            config.Services.Insert(typeof(ModelBinderProvider), 0,
                new SimpleModelBinderProvider(typeof(Numbers), new NumbersBinder()));

            config.Formatters.Add(new XNumbersFormatter());
            config.Formatters.Insert(0, new UrlNumbersFormatter());
            config.Formatters.Insert(0, new JsonNumbersFormatter());
            config.Formatters.Insert(0, new XmlNumbersFormatter());
        }
    }
}

I have used the Insert method once again because I need to ensure that Web API uses my custom media type formatter before the built-in ones.

Customizing the Model Binding Process

Web API delegates the entire process of binding values for parameters to an implementation of the IActionValueBinder interface, which is defined in the System.Web.Http.Controllers namespace. Listing 17-23 shows the definition of the interface.

Listing 17-23. The IActionValueBinder Interface

namespace System.Web.Http.Controllers {
    public interface IActionValueBinder {
        HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor);
    }
}

The interface defines a GetBinding method. The important thing to note about the IActionValueBinder interface is that the GetBinding method operates on action methods and is being asked to find bindings for all of the parameters defined by an action—not just a single parameter.

You can see this in the classes that the IActionValueBinder interface uses. The GetBinding method is passed an instance of the HttpActionDescriptor class, which I introduced in Chapter 9 and where I listed the four most important members, which I have repeated in Table 17-9. There are other members, but they are not useful for the purposes of parameter bindings.

Table 17-9. Selected Members Defined by the HttpActionDescriptor Class

Name

Description

ActionName

Returns the name of the action method

ReturnType

Returns the Type that the action method returns

SupportedHttpMethods

Returns a collection of HttpMethod objects that represent the HTTP verbs that can be used to target the action method

GetParameters()

Returns a collection of HttpParameterDescription objects that represent the action method parameters

The HttpActionBinding class, which is returned by the GetBinding method, is a wrapper around the HttpActionDescriptor and an array of HttpParameterBinding objects that are used to get values for the parameters defined by an action method. The HttpActionBinding class defines a constructor with the following signature:

...
public HttpActionBinding(HttpActionDescriptor actionDescriptor,
    HttpParameterBinding[] bindings) {
...

The members defined by the HttpActionBinding class are not important in this chapter—it is enough to know that the purpose of an implementation of the IActionValueBinder interface is to be able to create an HttpActionBinding object using this constructor. In the sections that follow, I’ll show you how to change the behavior of the default IActionValueBinder implementation and how to create a custom one. Table 17-10 puts changing the action value binder in context.

Table 17-10. Putting Changing the Action Value Binder in Context

Question

Answer

What is it?

A custom action value binder allows you to change the way that Web API locates values for action method parameters.

When should you use it?

Use this feature with caution because it takes a lot of effort to create a complete binding system and a lot of testing to make sure it works.

What do you need to know?

You can override the GetParameterBinding method of the DefaultActionValueBinder class if you want to change the default behavior but still take advantage of features such as value providers, model binders, and media type formatters.

Changing the Behavior of the Default Action Value Binder

All of the functionality that I have described since Chapter 14—value providers, model binders, and media type formatters—is provided by the DefaultActionValueBinder class, which is the Web API default implementation of the IActionValueBinder interface.

There are no configuration options for changing the behavior of the DefaultActionValueBinder class, but it is possible to create a subclass and override the method that defines the default policy for how values are sought for parameters. As a reminder, here is the default sequence that yields an HttpParameterBinding object for a single parameter:

  1. If the parameter has been decorated with a subclass of the ParameterBindingAttribute, then call the attribute’s GetBinding method.
  2. Try to obtain an HttpParameterBinding object from the parameter binding rules collection.
  3. For simple types, proceed as though the FromUri attribute has been applied to the parameter.
  4. For complex types, proceed as though the FromBody attribute has been applied to the parameter.

This sequence is implemented in the GetParameterBinding method of the DefaultActionValueBinder class. To demonstrate how to change the sequence, I created a file called CustomActionValueBinder.cs in the Infrastructure folder and used it to define the class shown in Listing 17-24.

Listing 17-24. The Contents of the CustomActionValueBinder.cs File

using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;

namespace ExampleApp.Infrastructure {
    public class CustomActionValueBinder : DefaultActionValueBinder {

        protected override HttpParameterBinding GetParameterBinding(
            HttpParameterDescriptor parameter) {

            if (parameter.ParameterBinderAttribute != null) {
                return parameter.ParameterBinderAttribute.GetBinding(parameter);
            }

            HttpParameterBinding binding =
                parameter.Configuration.ParameterBindingRules.LookupBinding(parameter);
            if (binding != null) {
                return binding;
            }

            if (parameter.ParameterType.IsPrimitive
                    || parameter.ParameterType == typeof(string)) {
                return parameter.BindWithAttribute(new ModelBinderAttribute());
            }

            return new FromBodyAttribute().GetBinding(parameter);
        }

    }
}

Image Note  Although the IActionValueBinder interface deals with an entire action method in one go, the GetParameterBinding method in the DefaultActionValueBinder class deals with one parameter at a time. The DefaultActionValueBinder implementation of the GetBinding method calls the GetParameterBinding method for each parameter defined by the action method described by the HttpActionDescriptor class.

This class follows the same sequence of the DefaultActionValueBinder class but with one important difference: for simple types, I act as though the ModelBinder attribute has been applied, rather than the FromUri attribute. The FromUri attribute excludes any value provider factory class that does not implement the IUriValueProviderFactory interface. By using the ModelBinder attribute—which I described in Chapter 15—I allow all value provider factories to participate in the binding process.

Image Tip  There is a second difference between CustomActionValueBinder and DefaultActionValueBinder: I check only for primitive types and strings, rather than the full set of simple types. If you override the GetParameterBinding method in a real project, take care to consider how you draw the line between types you will obtain from the URL and those you will obtain from the body.

Listing 17-25 shows how I registered the CustomActionValueBinder class as the implementation of the IActionValueBinder interface that Web API should use.

Listing 17-25. Registering an Action Value Binder in the WebApiConfig.cs File

using System.IO;
using System.Text;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.ValueProviders;
using ExampleApp.Infrastructure;
using ExampleApp.Models;
using System.Web.Http.Controllers;

namespace ExampleApp {
    public static class WebApiConfig {
        public static void Register(HttpConfiguration config) {

            config.DependencyResolver = new NinjectResolver();

            // ...routing statements omitted for brevity...

            config.Services.Add(typeof(ValueProviderFactory),
                new HeaderValueProviderFactory());

            config.Services.Insert(typeof(ModelBinderProvider), 0,
                new SimpleModelBinderProvider(typeof(Numbers), new NumbersBinder()));

            config.Formatters.Add(new XNumbersFormatter());
            config.Formatters.Insert(0, new UrlNumbersFormatter());
            config.Formatters.Insert(0, new JsonNumbersFormatter());
            config.Formatters.Insert(0, new XmlNumbersFormatter());

            config.Services.Replace(typeof(IActionValueBinder),
                new CustomActionValueBinder());
        }
    }
}

I used the HttpConfiguration.Services.Replace method to replace the DefaultActionValueBinder with a CustomActionValueBinder object.

Creating a Custom Action Value Binder

You can completely replace the process used to bind parameter values by directly implementing the IActionValueBinder interface. There is little reason to do this because there is a lot of flexibility in how the DefaultActionValueBinder can be used. But, if you have a compelling need to completely change the way that binding works, then this is the technique to use. As a demonstration, Listing 17-26 shows how I updated the CustomActionValueBinder class to implement the IActionValueBinder interface, rather than derive from DefaultActionValueBinder.

Listing 17-26. Implementing the IActionValueBinder Interface in the CustomActionValueBinder.cs File

using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Linq;

namespace ExampleApp.Infrastructure {
    public class CustomActionValueBinder : IActionValueBinder {

        public HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor) {
            return new HttpActionBinding(
                actionDescriptor,
                actionDescriptor.GetParameters()
                    .Select(p => GetParameterBinding(p)).ToArray()
            );
        }

        protected HttpParameterBinding GetParameterBinding(
                HttpParameterDescriptor parameter) {

            if (parameter.ParameterBinderAttribute != null) {
                return parameter.ParameterBinderAttribute.GetBinding(parameter);
            }

            HttpParameterBinding binding =
                parameter.Configuration.ParameterBindingRules.LookupBinding(parameter);
            if (binding != null) {
                return binding;
            }

            if (parameter.ParameterType.IsPrimitive
                    || parameter.ParameterType == typeof(string)) {
                return parameter.BindWithAttribute(new ModelBinderAttribute());
            }

            return new FromBodyAttribute().GetBinding(parameter);
        }
    }
}

The changes are simple because I am reproducing the behavior of the default class, and all of the complexity of the model binding process is contained in the value providers, model binders, and media type formatters that Web API includes.

Image Tip  You can elect to use as many or as few of the existing binding classes as you require, but before you embark on a project to replace the model binding process, I recommend taking a moment to consider the problem you are trying to solve. The default binding process is flexible and customizable, and smaller changes made within the default process are easier to test and maintain than a completely new process.

Summary

In this chapter, I explained how media type formatters can be used to bind complex types from the body of a request. I showed you how deserialization works by creating and using a custom media type formatter and by using the built-in media type formatters. I explained the limitations on the classes that the built-in formatters will instantiate and demonstrated how to override this behavior to deserialize classes that require special handling. I finished this chapter by demonstrating how to replace the class that drives the parameter and model binding processes with a custom implementation. In the next chapter, I show you the features that Web API provides to ensure that the data you bind from requests is what you expected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.83.199