CHAPTER 13

image

Using the Built-in Media Formatters

In this chapter, I focus on the built-in media type formatter classes, which are used to serialize data model objects into JSON and XML. I start by showing you how to control the way that the default media type formatters are used when there is no match between the formats they support and the request Accept header and then show you how to manage the serialized data that the formatters produce.

My emphasis in this chapter is on the JSON format, rather than XML. JSON has become the dominant data format for HTTP web services because it is relatively concise, easy to work with (especially in JavaScript code), and supported by all the major programming languages and web application development platforms. As you will learn, JSON serialization is performed by the latest versions of a popular and well-maintained open source .NET package, while XML serialization is performed using classes that have been around since .NET 1.1 and .NET 3.0. Table 13-1 summarizes this chapter.

Table 13-1. Chapter Summary

Problem

Solution

Listing

List the built-in type formatters.

Enumerate the collection returned by the HttpConfiguration.Formatters property.

1–4

Change the order in which formatters are queried to serialize a type by the match-on-type feature.

Manipulate the collection returned by the HttpConfiguration.Formatters property.

5

Enable or disable the match-on-type feature.

Use the bool constructor argument defined by the DefaultContentNegotiator class.

6–9

Indent JSON data.

Set the JsonMediaTypeFormatter.Indent property to true.

10

Select a format for date values.

Set the SerializerSettings.DateFormatHandling property.

11–16

Escape dangerous characters in serialized JSON data.

Set the SerializerSettings.StringEscapeHandling property.

17

Include or exclude null and default values in serialized JSON data.

Set the SerializerSettings.DefaultValueHandling property.

18, 19

Process XML at the client.

Use jQuery to locate elements contained in an XMLDocument object.

20, 21

Preparing the Example Project

I am going to continue working with the ExampleApp project from the previous chapter, but I need to disable the custom media formatter that I created so I can focus on the built-in ones instead. Listing 13-1 shows the simplified WebApiConfig.cs file.

Listing 13-1. Disabling a Custom Media Formatter in the WebApiConfig.cs File

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

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

            config.DependencyResolver = new NinjectResolver();

            //config.Services.Replace(typeof(IContentNegotiator),
            //  new CustomNegotiator());

            config.MapHttpAttributeRoutes();

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

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

            //MediaTypeFormatter prodFormatter = new ProductFormatter();
            //prodFormatter.AddQueryStringMapping("format", "product",
            //    "application/x.product");
            //prodFormatter.AddRequestHeaderMapping("X-UseProductFormat", "true",
            //    StringComparison.InvariantCultureIgnoreCase, false,
            //    "application/x.product");
            //prodFormatter.AddUriPathExtensionMapping("custom",
            // "application/x.product");
            //config.Formatters.Add(prodFormatter);
        }
    }
}

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 have commented out the statements that configure and register the ProductFormatter media formatter class, which means that only the built-in formatters will be used.

Working with the Built-in Media Type Formatters

Web API includes a set of four built-in media formatters. All of the built-in media type formatters participate in the model binding process I describe in Chapters 1417, but there are two that are interesting in this chapter because they are used to serialize object to generate JSON or XML data so it can be sent to the client. In the sections that follow, I show you how to manage and configure the built-in formatters. Table 13-2 puts the built-in formatters in context.

Table 13-2. Putting the Built-in Media Type Formatters in Context

Question

Answer

What are they?

The built-in media type formatters are responsible for serializing data into the JSON and XML formats.

When should you use them?

These media type formatters are configured for use by default and will be selected by the content negotiation process, which I explained in Chapter 12, to produce data in a format that can be consumed by the client.

What do you need to know?

The default content negotiator class will select the first media type formatter that is able to serialize the data model type if there is no match between the Accept header sent by the client and the formats available through the media type formatter classes. See the “Dealing with Type Matching During Negotiation” section for details.

Listing the Built-in Media Type Formatters

As I explained in Chapter 12, Web API maintains a collection of media type formatters that is accessed through the HttpConfiguration.Formatters property. In addition to the Add, Insert, Remove, and RemoveAt methods that I described in Chapter 12, the MediaTypeFormatterCollection class that is returned by the Formatters property defines convenience properties that provide direct access to three of the four built-in media type formatters, as described in Table 13-3.

Table 13-3. The Convenience Properties Defined by the MediaTypeFormattingCollection Class

Name

Description

FormUrlEncodedFormatter

Returns an instance of the FormUrlEncodedMediaTypeFormatter class, which is used to parse form data in the model binding process

JsonFormatter

Returns an instance of the JsonMediaTypeFormatter class, which serializes data into the JSON format

XmlFormatter

Returns an instance of the XmlMediaTypeFormatter class, which serializes data into the XML format

The MediaTypeFormattingCollection class is enumerable, which makes it easy to list the available formatters and establish their relative order in the collection. As you’ll learn, ordering the formatters can change the data format that is used to serialize data for a response.

I am going to display details of the built-in media type using the MVC framework, which allows me to demonstrate the technique required to render Razor views that use classes from the System.Net.Http namespace. I started by adding a class file called FormattersController.cs to the Controllers folder and using it to define the MVC framework controller shown in Listing 13-2.

Listing 13-2. The Contents of the FormattersController.cs File

using System.Web.Http;
using System.Web.Mvc;

namespace ExampleApp.Controllers {
    public class FormattersController : Controller {

        public ActionResult Index() {
            return View(GlobalConfiguration.Configuration.Formatters);
        }
    }
}

This is an MVC framework controller with an Index action method that renders the default view, passing in the collection of media type formatters obtained through the static GlobalConfiguration.Configuration property. To create the view, I right-clicked the Index method in the code editor, selected Add View, and accepted the default settings. Visual Studio created the Views/Formatters/Index.cshtml file, which I used to define the view shown in Listing 13-3.

Listing 13-3. The Contents of the Index.cshtml File in the Views/Formatters Folder

@model IEnumerable<System.Net.Http.Formatting.MediaTypeFormatter>
@{ ViewBag.Title = "Formatters";}

<div class="panel panel-primary">
    <div class="panel-heading">Media Type Formatters</div>
    <table class="table table-striped">
        <thead>
            <tr><th>Name</th><th>MIME Types</th></tr>
        </thead>
        <tbody>
            @foreach (var formatter in Model) {
                <tr>
                    <td>@formatter.GetType().Name</td>
                    <td>
                        @((string)string.Join(", ",
                            formatter.SupportedMediaTypes.Select(x => x.MediaType)))
                    </td>
                </tr>
            }
        </tbody>
    </table>
</div>

The view contains a table element that I populate using Razor and the view model data to display the name of each media type formatter and the MIME types it supports. If you start the application and request the /formatters URL, you will see an error message like this:

Compiler Error Message: CS0012: The type 'System.Net.Http.Headers.MediaTypeHeaderValue' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.

This problem is caused because Razor doesn’t automatically pick up the reference to the System.Net.Http assembly, which is obtained from the global assembly cache and not from one of the NuGet packages I installed in Chapter 10. To resolve this problem, I need to add an assembly reference in to the compilation configuration section in the Web.config file (the one in the root of the folder, not the one in the Views folder), as shown in Listing 13-4.

Listing 13-4. Adding a Reference to the System.Net.Http Assembly in the Web.config File

...
<system.web>
  <compilation debug="true" targetFramework="4.5.1">
    <assemblies>
      <add assembly="System.Net.Http, Version=4.0.0.0, Culture=neutral,
          PublicKeyToken=b03f5f7f11d50a3a"/>
    </assemblies>
  </compilation>
  <httpRuntime targetFramework="4.5.1" />
  <pages>
    <namespaces>
      <add namespace="System.Web.Helpers" />
      <add namespace="System.Web.Mvc" />
      <add namespace="System.Web.Mvc.Ajax" />
      <add namespace="System.Web.Mvc.Html" />
      <add namespace="System.Web.Routing" />
      <add namespace="System.Web.WebPages" />
    </namespaces>
  </pages>
</system.web>
<system.webServer>
...

I have changed the compilation element so that it contains the assemblies element, which is used to manage the collection of explicit references to runtime assemblies. I define a new reference using an add element to create a reference to the System.Net.Http assembly, using the version and public key from the error message.

Image Tip  Remember that Web API doesn’t use the Web.config file and that the changes I made in the listing are required only by the MVC framework. See my Pro ASP.NET MVC 5 Platform book for details of the ASP.NET configuration system, how it works, and how it can be customized.

This change allows you to access the Web API configuration information—and other components—from MVC controllers and views. Start the application and request the /formatters URL to see the contents of the media type formatters collection, as shown in Figure 13-1.

9781484200865_Fig13-01.jpg

Figure 13-1. The collection of Web API media type formatters

WHAT ABOUT BSON?

BSON is binary JSON and is, as its name suggests, a binary variation on the JSON specification. BSON is used most widely by the MongoDB database but has been proposed as a more efficient and expressive alternative to JSON—a proposal that has not been universally welcomed, and, as I write this, there are active and heated discussions about the efficiency benefits. You can learn more about the BSON specification at http://bsonspec.org.

BSON may find a wider role in the future, but the limiting factor at the moment is that there is little support for BSON in clients, and no JavaScript implementations are available for clients running in browsers. This means that it is not possible to receive BSON data and have it automatically parsed to JavaScript objects the way that JSON data is.

Web API includes a BSON media formatter (the BsonMediaTypeFormatter class in the System.Net.Http.Formatting namespace), but it is disabled by default. I don’t describe BSON or cover the BsonMediaTypeFormatter in this book because the BSON specification is not usable in clients developed using the MVC framework.

Dealing with Type Matching During Negotiation

I showed you the contents of the media type formatter collection because there is a confusing quirk in the content negotiation process that relies on the order in which the formatters appear in the list shown in Figure 13-1.

Most of the time, the order of the formatters doesn’t matter because clients will send an Accept header that specifies a format that Web API can support. You saw this in Chapter 12 when I used Chrome to send a GET request to the /api/products URL. Chrome sends an Accept header that gives preference to XML, and that’s the format that was sent back. You can see in Figure 13-1 that the XmlMediaTypeFormatter class is the formatter responsible for the application/xml and text/xml MIME types, and this is the formatter that is selected for the Chrome request. In this situation, the order of the formatters does not have any impact on the format of the data sent to the client.

You can test this process explicitly by using Postman to send a GET request to the /api/products URL with the following Accept header:

application/x.product;q=1.0, application/xml;q=0.9, application/json;q=0.5

The Accept header specifies a first preference for the application/x.product format that I created in Chapter 12. I disabled the media type formatter for the custom MIME type in Listing 13-1, so the content negotiator won’t be able to find a formatter to produce this format, even though it is the one that the client would prefer to receive.

The next most preferred format is application/xml, which has a higher q value than the only other format that the client is willing to accept, which is application/json. The content negotiator selects the XmlMediaTypeFormatter class to serialize the data returned by the GetAll action method in the Products controller, even though it is second in the collection of media type formatters illustrated in Figure 13-1. Figure 13-2 shows the XML data that is returned by the web service.

9781484200865_Fig13-02.jpg

Figure 13-2. Selecting a data format during normal negotiation

This is the behavior that I described in Chapter 11, but it bears repetition for two reasons. The first is that it is how the data format for most requests will be selected for web browser clients, because both the browser and jQuery will sent an Accept header that specifies either XML or JSON directly.

The other reason I have emphasized the default behavior is because the default content negotiator does something odd when there is no match between the data formats that the web service can use to serialize data and the formats that the client is willing to accept. To see what happens, change the value of the Postman Accept header to the following:

application/x.product;q=1.0

This header specifies that the client will accept only the application/x.product format, for which there is no media type formatter available in the application. When you send the request, Web API responds with the following data:

[{"ProductID":1,"Name":"Kayak","Price":275.0},
 {"ProductID":2,"Name":"Lifejacket","Price":48.95},
 {"ProductID":3,"Name":"Soccer Ball","Price":19.50},
 {"ProductID":4,"Name":"Thinking Cap","Price":16.0}]

The default content negotiator class has responded with JSON data, which is unexpected since the client has indicated that it can’t process JSON data.

The DefaultContentNegotiator class, which I described in Chapter 11, has a feature called match-on-type that is enabled by default and is used to select a formatter when there the Accept header doesn’t specify a format that Web API can work with.

The content negotiator calls the CanWriteType method of each of the available formatters and will use the first one that returns true for the data type that is to be serialized. As Figure 13-1 shows, the JSON media type formatter is first on the list, and that’s why the request for the application/x.product MIME type produced JSON data, even though it isn’t a format that the client would accept. In the following sections, I’ll describe how you can take control of this process and demonstrate how to disable it entirely.

Changing the Media Formatter Order

You can change the data format that is selected by the match-on-type feature by re-ordering the media formatters in the MediaTypeFormatterCollection collection, using the methods described in Table 13-4.

Table 13-4. The Methods Defined by the MediaTypeFormattingCollection for Manipulating the Collection

Name

Description

Add(formatter)

Adds a new formatter to the collection

Insert(index, formatter)

Inserts a formatter at the specified index

Remove(formatter)

Removes the specified formatter

RemoveAt(index)

Removes the formatter at the specified index

The easiest way to change the order is to use the convenience properties I described in Table 13-3 to obtain a reference to the formatter object that you want to move and use it as an argument to the methods in Table 13-4. Listing 13-5 shows how I have promoted the XML formatter in the collection using the WebApiConfig.cs file. (I have removed the commented out statements from previous examples.)

Listing 13-5. Changing the Order of the Media Type Formatters in the WebApiConfig.cs File

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

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

            config.DependencyResolver = new NinjectResolver();

            config.MapHttpAttributeRoutes();

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

            MediaTypeFormatter xmlFormatter = config.Formatters.XmlFormatter;
            config.Formatters.Remove(xmlFormatter);
            config.Formatters.Insert(0, xmlFormatter);
        }
    }
}

I removed the XML formatter from the collection and inserted it back at position 0, making it the first formatter that will be asked whether it can serialize the data type when there are no matching formats from the Accept header.

Image Tip  Working with the MediaTypeFormatterCollection object is awkward. The convenience properties return the instances of the formatters that are created automatically during the Web API configuration process. If you remove or replace a formatter, the corresponding convenience property will return null.

Start the application and use the browser to request the /formatters URL; you will see that the XmlMediaTypeFormatter class appears first in the collection. If you use Postman to send a GET request to the /api/products URL with an Accept header that specifies just the application/x.product format, you will receive XML data rather than JSON.

Disabling the Match-on-Type Feature

Changing the order of the formatters doesn’t address the underlying problem with the match-on-type feature, which is sending a format to the client that it may not be able to process. The best outcome will be that the client misstated its Accept preferences and is able to process the format after all—but isn’t a solid foundation for making data format choices. A more common outcome is that the client will assume that it is dealing with the format it asked for, which either generates an error or almost—but not quite—works.

You can see an example of a data format almost working by starting the application and using the browser to request the /Home/Index URL. Click the Refresh link to send an Ajax request that targets the /api/products URL with a GET request whose Accept header contains only the application/x.product MIME type (because the request is being created by the jQuery code that I set up in Chapter 12 to process the custom data format).

The custom format negotiator uses the match-on-type feature to select the XML formatter, which has the effect shown in Figure 13-3.

9781484200865_Fig13-03.jpg

Figure 13-3. The effect of sending a client an unknown format

Few web service clients check to see whether the format they received is the one that they asked for. In this case, the jQuery code I wrote in Chapter 12 assumes that it has received the expected format and tries to break it up for processing but does so using separators that are not present in the XML data. The result is a single row in the table that contains the complete XML data response in the first column.

One of the reasons that web services don’t check the received data format is that the match-on-type feature doesn’t follow the HTTP specification, which states that the web service should send the client a 406 (Not Acceptable) response if there is no match between the data formats in the Accept header and the ones supported by the application. This is a much better outcome because it doesn’t assume that the client is mistaken about the data formats that it is able to process.

The DefaultContentNegotiator class defines a constructor argument that disables the match-on-type feature. Listing 13-6 shows how to set this option when using the NinjectResolver class that I created in Chapter 10 for dependency injection.

Listing 13-6. Disabling the Match-on-Type Feature in the NinjectResolver.cs File

...
private void AddBindings(IKernel kernel) {
    kernel.Bind<IRepository>().To<Repository>().InSingletonScope();
    kernel.Bind<IContentNegotiator>().To<DefaultContentNegotiator>()
        .WithConstructorArgument("excludeMatchOnTypeOnly", true);
}
...

I have defined a mapping between the IContentNegotiator interface and the DefaultContentNegotiator class and used the Ninject WithConstructorArgument method to set a value for the excludeMatchOnTypeOnly constructor argument. When Web API asks Ninject to provide an implementation of the IContentNegotiator interface, an instance of the DefaultContentNegotiator class will be created with the constructor argument of true, equivalent to calling this:

new DefaultContentNegotiator(true)

A true value for the constructor argument disables the match-on-type feature and causes the web service to send a 406 (Not Acceptable) message to the client, as shown in Figure 13-4.

9781484200865_Fig13-04.jpg

Figure 13-4. Getting a 406 (Not Acceptable) response from the web service

Image Tip  If you don’t receive a 406 (Not Acceptable) response, you may have forgotten to add the Accept header to the request. You must specify the application/x.product MIME type so that no media type formatter can be selected based on content type.

You will need to take a more direct approach if you are not using dependency injection in your application. Listing 13-7 shows how to disable match-on-type in the WebApiConfig.cs file.

Listing 13-7. Disabling the Match-on-Type Feature in the WebApiConfig.cs File

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

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

            config.DependencyResolver = new NinjectResolver();

            config.MapHttpAttributeRoutes();

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

            MediaTypeFormatter xmlFormatter = config.Formatters.XmlFormatter;
            config.Formatters.Remove(xmlFormatter);
            config.Formatters.Insert(0, xmlFormatter);

            config.Services.Replace(typeof(IContentNegotiator),
                new DefaultContentNegotiator(true));
        }
    }
}

I use the HttpConfiguration.Services property to get the ServicesContainer object that contains the Web API service objects. I create a new instance of the DefaultContentNegotiator class, using the constructor argument to disable the match-on-type feature, and tell Web API to use this class as the implementation of the IContentNegotiator interface with the Replace method.

Handling a Not Acceptable Response in the Client

To deal with 406 (Not Acceptable) responses, I need to add support for displaying errors to the user. First, I have defined a Knockout observable array that will contain the errors that are to be displayed along with some HTML elements that will present the errors to the user. Listing 13-8 shows the changes I made to the Index.cshtml file in the Views/Home folder.

Listing 13-8. Preparing to Display Errors in the Views/Home/Index.cshtml File

@model IEnumerable<ExampleApp.Models.Product>
@{ ViewBag.Title = "Index";}

@section Scripts {
    <script>
        var products = ko.observableArray(
            @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model)));
        var errors = ko.observableArray();
    </script>
    <script src="~/Scripts/exampleApp.js"></script>
}

<div class="alert alert-danger" data-bind="visible: errors().length">
    <p><strong>Something has gone wrong:</strong></p>
    <ul data-bind="foreach: errors">
        <li data-bind="text: $data"></li>
    </ul>
</div>

<div class="panel panel-primary">
    <div class="panel-heading">RSVPs</div>
    <table id="rsvpTable" class="table table-striped">
        <thead>
            <tr><th>ID</th><th>Name</th><th>Price</th></tr>
        </thead>
        <tbody data-bind="foreach: products">
            <tr>
                <td data-bind="text: ProductID"></td>
                <td data-bind="text: Name"></td>
                <td data-bind="text: Price"></td>
                <td>
                    <button class="deleteBtn btn btn-danger btn-xs"
                            data-bind="click: deleteProduct">
                        Delete
                    </button>
                </td>
            </tr>
        </tbody>
    </table>
</div>
<button data-bind="click: getProducts" class="btn btn-primary">Refresh</button>

The new observable array is called errors, and the HTML elements I have added are displayed when the observable array contains one or more items. I have styled the new elements using the Bootstrap alert style, and I enumerate the contents of the errors array to generate an li element for each of them using the Knockout foreach binding. Listing 13-9 shows the changes that I have made to the exampleApp.js file in the Scripts folder to respond to the 406 (Not Accepted) status code using the errors observable array.

Listing 13-9. Responding to a Not Acceptable Response in the exampleApp.js File

$(document).ready(function () {

    deleteProduct = function (data) {
        $.ajax("/api/products/" + data.ProductID, {
            type: "DELETE",
            success: function () {
                products.remove(data);
            }
        })
    };

    getProducts = function () {
    errors.removeAll();
        $.ajax("/api/products", {
            headers: { "X-UseProductFormat": "true" },
        //dataType: "text",
        accepts: {
            "*": "application/x.product"
        },
            success: function (data) {
                products.removeAll();
                var arr = data.split(",");
                for (var i = 0; i < arr.length; i += 3) {
                    products.push({
                        ProductID: arr[i],
                        Name: arr[i + 1],
                        Price: arr[i + 2]
                    });
                }
            },
            error: function (jqXHR) {
                switch (jqXHR.status) {
                    case 406:
                        errors.push("Request not accepted by server");
                        break;
                }
            }
        })
    };
    ko.applyBindings();
});

To handle the response from the server, I have used the error setting to specify a callback function. The argument passed to the callback function is a jqXHR object, and I check to see kind of error I am dealing with by checking the status property. For the 406 (Not Acceptable) status code, I add a new item to the errors observable array. (I remove any items in the errors array when the getProducts function is invoked so that errors don’t accumulate through several attempts.)

In addition to the callback function, I have changed the way I use the dataType and accepts settings. By default, jQuery adds */* to the Accept header for Ajax requests, which indicates that any data type is acceptable—and that’s not what I require for this example. To disable the default header, I have commented out the statement that sets the dataType setting and change the accepts setting to override the default value that jQuery adds to all requests, which is associated with a property called "*", like this:

...
accepts: {
    "*": "application/x.product"
},
...

This isn’t a technique that you will need to use in many situations, other than when including */* in the Accept header results in a data format that you can’t process—and this rarely happens since JSON has become the de facto standard for web services.

To test the changes, start the application, navigate to the /Home/Index URL, and click the Refresh button. jQuery will make an Ajax request to the Web API web service that contains the following Accept header:

Accept: application/x.product

The web service is unable to produce serialized data in that format and sends back the 406 status code, which results in the error display shown in Figure 13-5.

9781484200865_Fig13-05.jpg

Figure 13-5. Displaying an error to the user

Working with the JSON Media Type Formatter

The JsonMediaTypeFormatter class is responsible for producing JSON data. Behind the scenes, the JSON data is generated by the Json.Net package, which is an open source library that has become the most popular JSON package for .NET applications. Table 13-5 puts the JsonMediaTypeFormatter in context.

Table 13-5. Putting the JsonMediaTypeFormatter in Context

Question

Answer

What is it?

The JSON media type formatter is responsible for serializing objects into the JSON data format.

When should you use it?

The formatter will be selected automatically during the content negotiation process.

What do you need to know?

The serialization work is done by an open source library called Json.Net. There are a number of options that can be specified to control the JSON that the Json.Net package produces, which can be useful for ensuring compatibility with clients that expect JSON to be structured in a specific way. See the “Configuring Json.Net” section for details.

Configuring the JSON Media Type Formatter

Configuring the JsonMediaTypeFormatter class is really about configuring Json.Net. The default settings are fine most of the time, but you will find that some older clients can be picky about the data they process, and it can be useful to tweak the output, especially if you are using Web API to re-implement a legacy web service and are unable to update the clients at the same time. Table 13-6 shows the configuration members that the JsonMediaTypeFormatter class defines.

Table 13-6. The JsonMediaTypeFormatter Configuration Methods

Name

Description

Indent

When set to true, the JSON will be indented, making it easier to read.

MaxDepth

Sets the maximum depth of object allowed when reading JSON data during the model binding process.

UseDataContractJsonSerializer

When set to true, the DataContractJsonSerializer, rather than the Json.Net package, will be used to produce JSON data.

SerializerSettings

Gets or sets the JsonSerializerSettings object used to configure serialization.

CreateDefaultSerializerSettings()

Creates a JsonSerializerSettings object configured with the defaults used by the media type formatter.

Changing the Underlying JSON Serializer

You can replace the Json.Net package with the Microsoft DataContractJsonSerializer class by setting the UseDataContractJsonSerializer property to true. The DataContractJsonSerializer class is slower and less fully featured than Json.Net, but it can be useful if you are re-implementing a legacy web service that used the DataContractJsonSerializer class and you want to preserve the quirks of its JSON formatting so that you don’t have to make changes in the clients. For all other situations, the Json.Net package should be used—it is faster, is more flexible, and produces JSON that is easily consumed by clients.

Indenting the JSON Data

The easiest way to configure the JSONMediaTypeFormatter is through the convenience property defined by the MediaTypeFormatterCollection class. Listing 13-10 shows how I have used this property to set the value of the Indent property in the WebApiConfig.cs file.

Listing 13-10. Configuring the JSON Media Type Formatter in the WebApiConfig.cs File

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

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

            config.DependencyResolver = new NinjectResolver();

            config.MapHttpAttributeRoutes();

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

            MediaTypeFormatter xmlFormatter = config.Formatters.XmlFormatter;
            config.Formatters.Remove(xmlFormatter);
            config.Formatters.Insert(0, xmlFormatter);

            config.Services.Replace(typeof(IContentNegotiator),
                new DefaultContentNegotiator(true));

            JsonMediaTypeFormatter jsonFormatter = config.Formatters.JsonFormatter;
            jsonFormatter.Indent = true;
        }
    }
}

Setting the Indent property causes the JsonMediaTypeFormatter class to indent the JSON so that each object and property is defined on its own line, indented so that it is easier to read. Start the application and use Postman to send a GET request to /api/products with an Accept header of application/json; you will receive data like this:

[
  {
    "ProductID": 1,
    "Name": "Kayak",
    "Price": 275.0
  },
  {
    "ProductID": 2,
    "Name": "Lifejacket",
    "Price": 48.95
  },
...

I have shown only the first two Product objects because the indented JSON takes up a lot of space. Setting the Indent property to true makes it easier to read the JSON that the media type formatter produces, but it creates larger HTTP responses, and the extra characters added to indent the data can cause problems with poorly written client JSON parsers.

Configuring Json.Net

The design of the JsonMediaTypeFormatter class doesn’t hide the fact that it usually depends on the Json.Net package. In fact, the SerializerSettings property and the CreateDefaultSerializerSettings method operate directly on the Json.Net.JsonSerializerSettings class, which is part of the Json.Net package and not part of Web API at all. The Json.Net classes are defined in the Newtonsoft.Json namespace.

The CreateDefaultSerializerSettings method creates a new instance of the JsonSerializerSettings class with the default settings used by Web API. The SerializerSettings property is used to get or set the JsonSerializerSettings object that is used to configure Json.Net when the JsonMediaTypeFormatter class reads and writes JSON data. (I explain how JSON is read in Chapter 17 when I describe the model binding process.)

In Table 13-7, I have listed the properties defined by the JsonSerializerSettings class that you may want to change in a Web API project and the default values that JsonMediaTypeFormatter uses. They largely relate to data types for which the JSON specification doesn’t contain a definition and some kind of agreement between the client and the web service about how they are expressed.

Table 13-7. The Most Useful SerializerSettings Properties

Name

Description

DateFormatHandling

Specifies how dates are written in JSON, expressed as a value from the DateFormatHandling enumeration. The values are IsoDateFormat (the default), which writes dates as 2015-01-20T09:20Z, and MicrosoftDateFormat, which preserves compatibility with earlier Microsoft web services. See the “Handling JSON Dates” section for details.

DateFormatString

Overrides the DateFormatHandling property and sets a custom format for dates. The value used when DateFormatHandling is IsoDateFormat is yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK.

DefaultValueHandling

Specifies how default values are handled, expressed using the DefaultValueHandling enumeration. The default value is Include, but see the “Handling Default Values” section for further details.

NullValueHandling

Specifies whether properties that are null are included in JSON data, using a value from the NullValueHandling enumeration. The default value is Include, meaning that the properties are included. The other value available is Ignore, which omits such properties from the JSON data.

StringEscapeHandling

Specifies how string values are escaped in the JSON data, using a value from the StringEscapeHandling enumeration.  The default value is Default, but see the “Handling String Escaping” section for more details.

Image Tip  See http://james.newtonking.com/json/help/index.html for full details of the properties defined by the JsonSerializerSettings class, including the ones that I have not included in this chapter.

Creating the Example Controller and Client

Some of the JSON serializer options are worth further explanation. I get into the details in the sections that follow, but first I need to enhance the example project so that I can demonstrate the formatting features. First I added a class file called FormatsController.cs to the Controllers and used it to define the controller shown in Listing 13-11.

Listing 13-11. The Contents of the FormatsController.cs File

using System;
using System.Web.Http;

namespace ExampleApp.Controllers {
    public class FormatsController : ApiController {

        public object GetData() {
            return new {
                Time = DateTime.Now,
                Text = "Joe <b>Smith</b>",
                Count = 0
            };
        }
    }
}

The Web API controller defines a single action method that returns a dynamic object containing Time, Text, and Count properties. I’ll use these properties to demonstrate different formatting options shortly.

I need to have some way to target the Web API controller, so I added an action method to the MVC Home controller, as shown in Listing 13-12.

Listing 13-12. Adding an Action Method in the HomeController.cs File

using System.Web.Mvc;
using ExampleApp.Models;

namespace ExampleApp.Controllers {
    public class HomeController : Controller {
        IRepository repo;

        public HomeController(IRepository repoImpl) {
            repo = repoImpl;
        }

        public ActionResult Index() {
            return View(repo.Products);
        }

        public ActionResult Formats() {
            return View();
        }
    }
}

The new action method, Formats, calls the View method to render the default view. There is no view model data required because I use Ajax to send an HTTP request to the web service. Listing 13-13 shows the contents of the Views/Home/Formats.cshtml file, which I created to be rendered by the Formats action method.

Listing 13-13. The Contents of the Formats.cshtml File

@{ ViewBag.Title = "Formats"; }

@section Scripts {
    <script>
        $(document).ready(function () {
            $.ajax("/api/formats", {
                success: function (data) {
                    dataObject = ko.observable(data);
                    ko.applyBindings();
                }
            });
        });
    </script>
}

<div class="panel panel-primary">
    <div class="panel-heading">RSVPs</div>
    <table id="rsvpTable" class="table table-striped">
        <thead><tr><th>Property</th><th>Value</th></tr></thead>
        <tbody>
            <tr><td>Time</td><td data-bind="text: dataObject().Time"></td></tr>
            <tr><td>Text</td><td data-bind="text: dataObject().Text"></td></tr>
            <tr><td>Count</td><td data-bind="text: dataObject().Count"></td>
            </tr>
        </tbody>
    </table>
</div>

The JavaScript code in this view uses jQuery to make an Ajax request as soon as the document is ready. The success callback for the Ajax request assigns the JavaScript object that has been parsed from the JSON data to a variable called dataObject and calls the Knockout applyBindings method so that the properties of the data object are displayed in the HTML table element via the Knockout text bindings I added to the td elements. To see the effect of these additions, start the application and request the /Home/Formats URL. The result is shown in Figure 13-6.

9781484200865_Fig13-06.jpg

Figure 13-6. Displaying JSON data

The three properties sent in the JSON from the web service as displayed just as they are received. In the following sections, I’ll show you how to control the JSON output produced by the web service to get different effects.

Handling JSON Dates

Dates are a source of difficulty in any environment because of the multitude of ways that they can be expressed and the endless permutations of regional calendars and time zones. The situation is made worse only when using JSON because the format acts as a neutral interchange between two different programming languages and has no definitive definition for how dates should be expressed.

The best approach—and the one most widely used in web services—is to express dates so they are easily processed in JavaScript. This is the default option used by the Json.Net package, so no changes are required within Web API. In Listing 13-14, you can see the changes that I made to the script element in the Formats.cshtml file to process the date value in JavaScript.

Listing 13-14. Processing a Date Value in the Formats.cshtml File

...
<script>
    $(document).ready(function () {
        $.ajax("/api/formats", {
            success: function (data) {
                dataObject = ko.observable(data);
                var date = new Date(data.Time);
                dataObject().Time = date.toLocaleTimeString();
                ko.applyBindings();
            }
        });
    });
</script>
...

JavaScript has a built-in Date type, and instances are created by calling new Date and using the string generated by the JsonMediaTypeFormatter as the constructor argument. Once you have a Date object, there are a range of methods you can use to get information about the date and time specified. I used the toLocateTimeString method to obtain a time string, as shown in Figure 13-7.

9781484200865_Fig13-07.jpg

Figure 13-7. Processing a date value

The SerializerSettings.DateFormatHandling setting can be set to the DateFormatHandling.MicrosoftDateFormat value if you need to generate dates for compatibility with clients that rely on an older format that Microsoft used to promote, where dates are expressed like this:

{ "Time": "/Date(1396385762063+0100)/", "Text": "Joe <b>Smith</b>", "Count": 0}

Listing 13-15 shows how I have enabled the Microsoft date format in the WebApiConfig.cs file.

Listing 13-15. Enabling the Microsoft Date Format in the WebApiConfig.cs File

using System.Web.Http;
using ExampleApp.Infrastructure;
using System.Net.Http.Formatting;
using System;
using Newtonsoft.Json;

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

            config.DependencyResolver = new NinjectResolver();

            config.MapHttpAttributeRoutes();

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

            MediaTypeFormatter xmlFormatter = config.Formatters.XmlFormatter;
            config.Formatters.Remove(xmlFormatter);
            config.Formatters.Insert(0, xmlFormatter);

            config.Services.Replace(typeof(IContentNegotiator),
                new DefaultContentNegotiator(true));

            JsonMediaTypeFormatter jsonFormatter = config.Formatters.JsonFormatter;
            jsonFormatter.Indent = true;
            jsonFormatter.SerializerSettings.DateFormatHandling
                = DateFormatHandling.MicrosoftDateFormat;
        }
    }
}

The JavaScript Date object can’t process this kind of format, so some additional manipulation is required. Listing 13-16 shows the changes I made to the script element in the Formats.cshtml view to process the Microsoft date format. This incantation extracts the numerical value from the date string and uses it to create a Date object and can be used verbatim when you are working with the legacy format.

Listing 13-16. Processing a Microsoft Date Value in the Formats.cshtml File

...
<script>
    $(document).ready(function () {
        $.ajax("/api/formats", {
            success: function (data) {
                dataObject = ko.observable(data);
                var date = new Date(parseInt(data.Time.replace("/Date(", "")
                     .replace(")/", ""), 10));
                dataObject().Time = date.toLocaleTimeString();
                ko.applyBindings();
            }
        });
    });
</script>
...

Handling String Escaping

By default, only control characters are escaped in string values when generating JSON data. The StringEscapeHandling setting allows you to change this behavior by specifying a value from the StringEscapeHandling enumeration, which defines the values shown in Table 13-8.

Table 13-8. The Values Defined by the StringEscapeHandling Enumeration

Value

Description

Default

Only control characters are escaped.

EscapeNonAscii

Control characters and non-ASCII characters are escaped.

EscapeHtml

HTML characters and control characters are escaped.

In any web application, it is important to guard against interpreting text as HTML if it has not been escaped. This prevents script injection, where data values are crafted to include script elements that contain JavaScript that attacks the application or the user. The data that I return from the web service contains some benign HTML, as follows:

{ "Time": "/Date(1396385762063+0100)/", "Text": "Joe <b>Smith</b>", "Count": 0}

I have used the b element to add emphasis to part of the value for the Text property. The Knockout text binding automatically escapes dangerous HTML characters, which is why the word Smith isn’t shown in bold in Figure 13-7.

Relying on the client to escape dangerous HTML characters isn’t enough when working with web services. You should also escape dangerous characters in the web service itself because the set of clients—or the developers who write the client—may change, presenting the risk that your web service may be used as an attack vector to undermine them. Listing 13-17 shows how I have enabled the EscapeHtml option from Table 13-8 in the WebApiConfig.cs file.

Listing 13-17. Enabling HTML Character Escaping in the WebApiConfig.cs File

using System.Web.Http;
using ExampleApp.Infrastructure;
using System.Net.Http.Formatting;
using System;
using Newtonsoft.Json;

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

            // ...other statements omitted for brevity...


            JsonMediaTypeFormatter jsonFormatter = config.Formatters.JsonFormatter;
            jsonFormatter.Indent = true;
            jsonFormatter.SerializerSettings.DateFormatHandling
                = DateFormatHandling.MicrosoftDateFormat;
            jsonFormatter.SerializerSettings.StringEscapeHandling
                = StringEscapeHandling.EscapeHtml;
        }
    }
}

There is no change in the content rendered by the MVC controller, but if you use Postman to send a GET request to the /api/formats URL with an Accept header of application/json, you will see that the dangerous HTML characters have been escaped, like this:

{ "Time": "/Date(1396421325274+0100)/", 
    "Text": "Joe u003cbu003eSmithu003c/bu003e", "Count": 0 }

Image Tip  Postman formats HTML content in the Pretty view of the result data. Be sure to select the Raw view to see the characters sent by the web service.

Handling Default Values

The DefaultValueHandling setting specifies how default values for properties are handled in Json data. Default values are null for object and nullable properties, zero for numeric properties, and false for bool properties. The DefaultValueHandling setting is defined using a value from the DefaultValueHandling enumeration, which defines the values shown in Table 13-9.

Table 13-9. The Values Defined by the DefaultValueHandling Enumeration

Value

Description

Include

This is the default value, and it includes properties with default values in the JSON data.

Ignore

This setting excludes properties with default values from the JSON data.

Populate

This setting is used when deserializing JSON data. It sets the default value for properties in C# objects when there is no corresponding property in the JSON data. Deserialization is part of the model binding process, which I describe in Chapter 14.

IgnoreAndPopulate

This setting combines the Ignore and Populate values.

Image Tip  There is also a NullValueHandling setting that applies only to null values.

The Include value is the default, which means that the Count property in my example data object is included in the JSON that the web service generates, even though its value is zero.

{ "Time": "/Date(1396421325274+0100)/", 
    "Text": "Joe u003cbu003eSmithu003c/bu003e", "Count": 0 }

Listing 13-18 shows how I have set DefaultValueHandling to exclude any property that has the default value in the WebApiConfig.cs file.

Listing 13-18. Ignoring Default Values in the WebApiConfig.cs File

using System.Web.Http;
using ExampleApp.Infrastructure;
using System.Net.Http.Formatting;
using System;
using Newtonsoft.Json;

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

            // ...other statements omitted for brevity...

            JsonMediaTypeFormatter jsonFormatter = config.Formatters.JsonFormatter;
            jsonFormatter.Indent = true;
            jsonFormatter.SerializerSettings.DateFormatHandling
                = DateFormatHandling.MicrosoftDateFormat;
            jsonFormatter.SerializerSettings.StringEscapeHandling
                = StringEscapeHandling.EscapeHtml;
            jsonFormatter.SerializerSettings.DefaultValueHandling
                = DefaultValueHandling.Ignore;
        }
    }
}

The effect is that the Count property is omitted from the JSON data:

{ "Time": "/Date(1396421578038+0100)/",
    "Text": "Joe u003cbu003eSmithu003c/bu003e"}

Omitting properties with default values means that the client has to be able to work without the missing properties or be able to reconstruct them with default values. Listing 13-19 shows how I updated the JavaScript code in the Formats.cshtml file to add the Count property if it is missing.

Listing 13-19. Re-creating Missing Properties in the Formats.cshtml File

...
<script>
    $(document).ready(function () {
        $.ajax("/api/formats", {
            success: function (data) {
                if (!("Count" in data)) {
                    data.Count = 0;
                }
                dataObject = ko.observable(data);
                var date =
                    new Date(parseInt(data.Time.replace("/Date(", "")
                    .replace(")/", ""), 10));
                dataObject().Time = date.toLocaleTimeString();
                ko.applyBindings();
            }
        });
    });
</script>
...

Image Tip  I prefer to include properties that have default values because it means that the client always works on a ­consistent representation of the data objects and doesn’t require any prior knowledge about properties that may be missing.

Using the XML Media Type Formatter

The XmlMediaTypeFormatter class is responsible for serializing model objects into XML data, and like the JSON media type formatter, it relies on other classes to generate the serialized data. In this case, the System.Runtime.DataContractSerializer class is used by default. XML used to be the predominant data format for web services but has been all but replaced by JSON, which is compact and easier to work with. There are JSON libraries available for just about every combination of platform and programming language, so the only reason to use XML is for compatibility with legacy clients. Table 13-10 puts the XmlMediaTypeFormatter class into context.

Table 13-10. Putting the XmlMediaTypeFormatter Class in Context

Question

Answer

What is it?

The XML media type formatter is responsible for serializing objects into the XML data format.

When should you use it?

The formatter will be selected automatically during the content negotiation process.

What do you need to know?

The classes that are used to produce XML data are old, slow, and inflexible. They don’t support recent .NET and C# features, such as dynamic objects. XML support in Web API is largely so that web services can support clients originally developed to consume web services created with legacy Microsoft web service tools.

Image Tip  In real projects, I use the HttpConfiguration.Configuration.Formatters.Remove method to take the XmlMediaTypeFormatter out of the media type formatter collection for applications that don’t need to support legacy clients. Not only is JSON widely supported and easier to work with, but supporting only one data format reduces the amount of unit and integration testing required for the project.

WHAT HAPPENED TO XML WEB SERVICES?

The term XML web services was used in the early 2000s to describe heavily structured web services that were carefully described by different XML documents and standards, including the Simple Object Access Protocol (SOAP) and the Web Service Description Language (WSDL). These standards were used to create loosely coupled clients and services but required complex XML documents that were difficult to work with. These days, those web services that still use XML use the format only to describe fragments of data without the overhead of precise type and service descriptions—rather like the JSON strings that you have seen in other examples but expressed using XML elements and attributes instead of JavaScript-style objects and properties.

Configuring the XML Media Type Formatter

Table 13-11 shows the configuration members defined by the XmlMediaTypeFormatter class.

Table 13-11. The XmlMediaTypeFormatter Configuration Methods

Name

Description

Indent

When set to true, the XML will be indented, making it easier to read (but more verbose).

MaxDepth

Sets the maximum depth of object allowed when reading XML data during the model binding process.

UseXmlSerializer

When set to true, the XmlSerializer class will be used to produce XML data.

WriterSettings

Gets the XmlWriterSettings object used to configure serialization.

I am not going to go into any detail about configuring the XmlMediaTypeFormatter class because XML is the lesser data format in Web API applications and because the default configuration works fine for most applications.

The DataContractSerializer class was introduced in .NET 3.0 and is the default serializer used by the XmlMediaTypeFormatter class to create XML. You can configure the serializer by changing the property values of the XmlWriterSettings object returned by the WriterSettings property—although most of the properties have little impact beyond basic formatting. You can find a complete list of the properties defined by the XmlWriterSettings class at http://goo.gl/iMDEFZ.

If you set the WriterSettings property to true, the XmlMediaTypeFormatter will use the XmlSerializer class, which has been around since .NET 1.1. Both classes are rather poor, and it is a measure of how little XML is used in web services that the choice available is a class from 2006 or a class from 2003 and that no non-Microsoft alternative package has entered the mainstream as a replacement. The only reason to use XML in Web API applications is to preserve compatibility with legacy clients, and you should use JSON for projects where this is not a requirement. There is an old, but still useful, comparison of the two XML serializer classes at http://goo.gl/gz0lyH that can help you understand the strengths and (many) weaknesses of each class.

Getting the Xml Media Type Formatter Working

The first task is to get the XmlMediaTypeFormatter class working because at the moment it isn’t able to serialize the data returned by the GetData action method in the Formats controller and the client-side code doesn’t support XML at the moment.

Updating the Web API Controller

The problem with the Web API controller is that you can’t return dynamic objects from action methods. This means you need to create the equivalent of view model classes in the MVC framework to return results from action methods. The only time I find this frustrating is when I can’t return an enumeration of dynamically created objects from a LINQ select clause. Listing 13-20 shows how I have replaced the dynamic object I used for the JSON formatter with a simple class that defines the same properties.

Listing 13-20. Defining a Model Object in the FormatsController.cs File

using System;
using System.Web.Http;

namespace ExampleApp.Controllers {
    public class FormatsController : ApiController {

        public DataObject GetData() {
            return new DataObject {
                Time = DateTime.Now,
                Text = "Joe <b>Smith</b>",
                Count = 0
            };
        }
    }

    public class DataObject {
        public DateTime Time { get; set; }
        public string Text { get; set; }
        public int Count { get; set; }
    }
}

The DataObject class defines the DateTime, string, and int properties that I need to represent the data. To test the XML serialization, start the application and use Postman to send a GET request to the /api/formats API. (There is no need to specify an Accept header because the WebApiConfig.cs file sets up the XmlMediaTypeFormatter class as the first in the formatter collection and Postman sends an Accept header of */* if one isn’t explicitly specified.) You will receive the following output:

<DataObject xmlns:i="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://schemas.datacontract.org/2004/07/ExampleApp.Controllers">
<Count>0</Count>
<Text>Joe <b>Smith</b></Text>
<Time>2014-04-02T19:27:30.8006076+01:00</Time>
</DataObject>

Updating the Client JavaScript Code

jQuery automatically parses XML data received from Ajax to create an XMLDocument object, which—as the name suggests—is a representation of an XML document, provided by the browser. The API for XMLDocument is awkward to work with, and the simplest way to create JavaScript objects from XML data is to use jQuery methods that are usually used to handle HTML. Listing 13-21 shows how I have updated the script element in the Formats.cshtml file to process the XML data that is returned by the Web API controller.

Listing 13-21. Processing XML Data in the Formats.cshtml File

...
<script>
  $(document).ready(function () {
     $.ajax("/api/formats", {
        dataType: "xml",
        success: function (data) {
          var props = ["Time", "Text", "Count"];
          var jsObject = {};
          for (var i = 0; i < props.length; i++) {
            jsObject[props[i]] = $(data).find(props[i]).text();
          }
          dataObject = ko.observable(jsObject);
          ko.applyBindings();
        }
     });
  });
</script>
...

Setting dataType to xml when making the Ajax requests tells jQuery to treat the data as XML and pass the XMLDocument object to the success callback function. Within the callback, I created an array of the properties that I need to extract from the XML and use jQuery to get values for each of them.

...
jsObject[props[i]] = $(data).find(props[i]).text();
...

There are three parts to the jQuery statement. The $(data) part creates a jQuery wrapper around the XMLDocument object, which means that the jQuery methods can be used. The find method locates all of the elements of a specific type, and the text method returns the combined text content of the matching elements. The effect of this JavaScript and jQuery code is that I create an object with the properties for which I have defined Knockout bindings, populated with the values from the XML data.

Summary

In this chapter, I explained how to work with the built-in media type formatters. I explained how the default content negotiator matches media type formatters based on data types and how you can override this behavior to create a response that is more in keeping with the HTTP standard. I described how to work with the JSON media type formatter, showing you the configuration options defined by the media type formatter itself and the JSON serializer that it depends on. I finished the chapter by showing you how to work with the XML media type formatter and explained that XML has taken a back seat to JSON in web services and that the classes that the media type formatter can use to generate XML are old and mostly available for backward compatibility with legacy clients. In the next chapter, I describe the parameter and model binding features in which media type formatters play a part.

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

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