CHAPTER 22

image

Controllers and Actions

In this chapter, I continue describing the Web API dispatch process and focus on controllers and action methods. I explain how controllers work in Web API and describe the dispatch process implemented by the default controller class, ApiController. Along the way, I show you how to resolve a common routing problem, explain how requests are mapped to action methods in RESTful Web API controllers, and show you how to customize request dispatching. Table 22-1 summarizes this chapter.

Table 22-1. Chapter Summary

Problem

Solution

Listing

Create a controller.

Define a class that implements the IHttpController interface and use the services collection to access Web API features such as parameter and model binding.

1–5

Create a controller that follows the action method model without having to handle the request directly.

Define a class that is derived from ApiController.

6

Specify the HTTP verbs that an action can handle.

Use the RESTful naming convention or apply an HTTP verb attribute.

7

Avoid routing conflicts between controllers that follow the RESTful naming convention and those that do not.

Use direct routing or restrict convention-based routes (either with a constraint or with a different prefix).

8–10

Customize the way that action methods are invoked.

Create an implementation of the IHttpActionInvoker interface.

11

Customize the way that action methods are selected.

Create an implementation of the IHttpActionSelector interface.

12

Apply a custom configuration to a controller.

Create an implementation of the IControllerConfiguration interface.

13–14

Preparing the Example Project

I will continue working with the Dispatch project from the previous chapter, but to prepare for this chapter, I am going to tidy up the URL routes I defined. Listing 22-1 shows the Today controller, from which I have removed the direct routes.

Listing 22-1. Removing the Direct Routes from the TodayController.cs File

using System;
using System.Web.Http;

namespace Dispatch.Controllers {

    public class TodayController : ApiController {

        [HttpGet]
        public string DayOfWeek() {
            return DateTime.Now.ToString("dddd");
        }

        [HttpGet]
        public string DayOfWeek(int day) {
            return Enum.GetValues(typeof(DayOfWeek)).GetValue(day).ToString();
        }

        [HttpGet]
        public int DayNumber() {
            return DateTime.Now.Day;
        }
    }
}

Instead of direct routes, I have defined a convention-based route in the WebApiConfig.cs file that allows the action methods in the Today controller to be reached, and I have removed the custom constraints and other additions from Chapter 21, as shown in Listing 22-2.

Listing 22-2. Revising the URL Routing Configuration in the WebApiConfig.cs File

using System.Web.Http;

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

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

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

Understanding Controllers

Controllers are the classes where the world of Web API delivers HttpRequestMessages into your custom application logic so that you can transform them into HttpResponseMessages. This transformation will usually involve interactions with the model, which is another of the big components in the Model View Controller pattern. As I explained in Chapter 4, web services don’t have views because they deliver data back to the client, rather than components for a user interface (although some people regard the media type formatters to be the equivalent of a view because they transform the data in a way that can be consumed by the client).

Figure 22-1 shows the dispatch process as I left it in Chapter 20, and in this chapter I dig into the details of how an HttpRequestMessage object is processed, starting with the definition of a Web API controller and then turning to the default implementation classes that are used in most Web API applications.

9781484200865_Fig22-01.jpg

Figure 22-1. The Web API dispatch process

Table 22-2 puts controllers into context.

Table 22-2. Putting Controllers into Context

Question

Answer

What is it?

Controllers contain the logic required to handle a request and are the point at which the HttpResponseMessage object is created so it can be relayed back along the dispatcher chain and used to create a response for the client.

When should you use it?

Controllers are used by the HttpControllerDispatcher class and fully integrated into the request pipeline. No specific action is required to use controllers.

What do you need to know?

Controllers are defined by the IHttpController interface, but most applications are better served by deriving from the ApiController class, which takes care of a lot of behind-the-scenes work.

Creating a Controller

Controllers are defined by the IHttpController interface in the System.Web.Http.Controllers namespace. Here is the definition of the IHttpController interface:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace System.Web.Http.Controllers {
    public interface IHttpController {
        Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext,
            CancellationToken cancellationToken);
    }
}

As I explained in Chapter 19, the HttpControllerDispatcher class calls the ExecuteAsync method in order to receive a Task that will yield an HttpResponseMessage when it completes. The arguments to the ExecuteAsync method are an HttpControllerContext object, which is used to provide information about the request and the overall application, and a CancellationToken, which is used to signal cancellation for long-lived processes. As a reminder, Table 22-3 describes the properties defined by the HttpControllerContext class.

Table 22-3. The Properties Defined by the HttpControllerContext Class

Name

Description

Configuration

Returns the HttpConfiguration object that should be used to service the request. As I explain in Chapter 22, controllers can be given their own configuration to work with.

Controller

Returns the IHttpController instance. This is not entirely useful when the HttpControllerContext is being passed an argument to the controller.

ControllerDescriptor

Returns the HttpControllerDescriptor that led to the controller being instantiated.

Request

Returns the HttpRequestMessage that describes the current request.

RequestContext

Returns the HttpRequestContext that provides additional information about the request.

RouteData

Returns the IHttpRouteData object that contains the routing data for the request. See Chapters 20 and 21 for details.

Most projects will use the default implementation of the IHttpController interface—the ApiController class—to create web services because there are lots of useful built-in features. But it is easy enough to create a custom implementation, and it helps put the role of the controller in context within the dispatch process. Listing 22-3 shows the contents of the CustomController.cs file, which I added to the Controllers folder.

Listing 22-3. The Contents of the CustomController.cs File

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Routing;

namespace Dispatch.Controllers {
    public class CustomController : IHttpController {

        public Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(
            HttpControllerContext context, CancellationToken cancellationToken) {

            return Task<HttpResponseMessage>.Factory.StartNew(() => {

                IHttpRouteData rd = context.RouteData;
                object result = null;
                if (rd.Values.ContainsKey("action")) {

                    switch (rd.Values["action"].ToString().ToLowerInvariant()) {
                        case "dayofweek":
                            if (rd.Values.ContainsKey("day")) {
                                int dayValue;
                                if (int.TryParse((string)rd.Values["day"],
                                        out dayValue)) {
                                    result = DayOfWeek(dayValue);
                                } else {
                                    return context.Request.CreateErrorResponse(
                                        HttpStatusCode.BadRequest, "Cannot parse data");
                                }
                            } else {
                                result = DayOfWeek();
                            }
                            break;
                        case "daynumber":
                            result = DayNumber();
                            break;
                        default:
                            return context.Request.CreateErrorResponse(
                                HttpStatusCode.NotFound, "Cannot parse data");
                    }
                }

                return result == null
                    ? context.Request.CreateResponse(HttpStatusCode.OK)
                    : context.Request.CreateResponse(HttpStatusCode.OK, result);
            });
        }

        ///////////////////////////////
        // Action Methods Start Here //
        ///////////////////////////////

        public string DayOfWeek() {
            return DateTime.Now.ToString("dddd");
        }

        public string DayOfWeek(int day) {
            return Enum.GetValues(typeof(DayOfWeek)).GetValue(day).ToString();
        }

        public int DayNumber() {
            return DateTime.Now.Day;
        }
    }
}

This class implements the same action methods as the Today controller that I added to the project. The difference is that my new controller has to take responsibility for implementing the ExecuteAsync method, selecting and invoking the action method, and generating an HttpResponseMessage that can be returned to the client.

To test the new controller, I need to change the URL that the client requests, as shown in Listing 22-4.

Listing 22-4. Changing the Request URL in the today.js File

var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function () {
  $.ajax("/api/custom/dayofweek/1", {
        type: "GET",
        success: function (data) {
            gotError(false);
            response(data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

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

Notice that I have changed the segment that will match the name of the controller and added a segment that will be mapped to the day variable by the route I added in Listing 22-3. I have done this so I can demonstrate how my manual parameter binding works—and how this can be improved upon in later examples.

To test the new controller, start the application and use the browser to navigate to the /Home/Today URL. Click the Get Day button, and the client will send a request, which will be matched by the route I defined at the start of the chapter; this leads to the ExecuteAsync method of my custom controller class being invoked. The result is created and sent back to the client, as shown in Figure 22-2. Since the client always specifies the same day, the result will always be Monday.

9781484200865_Fig22-02.jpg

Figure 22-2. Sending a request to a custom implementation of the IHttpController interface

Using Built-in Services and Features

When you implement a controller directly from the IHttpController interface, you lose the built-in features provided by the ApiController class (which I describe in the next section), but you can still use the core Web API services and features, such as model binding. This means you don’t have to reinvent important features, although there can be a lot of work to get to the point where a feature can be used. In Listing 22-5, I have modified the Custom controller class to use the model binding feature to get the argument value required to call the DayOfWeek action method.

Listing 22-5. Using the Built-in Parameter Binding Feature in the CustomController.cs File

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Web.Http.Routing;
using System.Web.Http;
using System.Reflection;

namespace Dispatch.Controllers {
    public class CustomController : IHttpController {

        public Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(
            HttpControllerContext context, CancellationToken cancellationToken) {


            return Task.Run<HttpResponseMessage>(async () => {

                IHttpRouteData rd = context.RouteData;
                object result = null;
                if (rd.Values.ContainsKey("action")) {

                    switch (rd.Values["action"].ToString().ToLowerInvariant()) {
                        case "dayofweek":
                if (rd.Values.ContainsKey("day")) {
                    int dayValue = await GetValue<int>("day", context,
                         cancellationToken);
                    result = DayOfWeek(dayValue);
                            } else {
                                result = DayOfWeek();
                            }
                            break;
                        case "daynumber":
                            result = DayNumber();
                            break;
                        default:
                            return context.Request.CreateErrorResponse(
                                HttpStatusCode.NotFound, "Cannot parse data");
                    }
                }

                return result == null
                    ? context.Request.CreateResponse(HttpStatusCode.OK)
                    : context.Request.CreateResponse(HttpStatusCode.OK, result);
            });
        }

        private async Task<T> GetValue<T>(string name, HttpControllerContext ctx,
                CancellationToken token) {

            HttpControllerDescriptor ctrlDescriptor = new HttpControllerDescriptor(
                ctx.Configuration, "Custom", this.GetType());
            MethodInfo methodInfo
                = GetType().GetMethod("DayOfWeek", new Type[] { typeof(int)});

            IActionValueBinder binder
                = ctx.Configuration.Services.GetActionValueBinder();
            HttpActionBinding binding = binder.GetBinding(
                new ReflectedHttpActionDescriptor(ctrlDescriptor, methodInfo));
            HttpActionContext actionCtx = new HttpActionContext(ctx, new
                ReflectedHttpActionDescriptor(ctrlDescriptor, methodInfo));

            await binding.ExecuteBindingAsync(actionCtx, token);

            return actionCtx.ActionArguments.ContainsKey(name)
                ? (T)Convert.ChangeType(actionCtx.ActionArguments[name], typeof(T))
                : default(T);
        }

        ///////////////////////////////
        // Action Methods Start Here //
        ///////////////////////////////

        public string DayOfWeek() {
            return DateTime.Now.ToString("dddd");
        }

        public string DayOfWeek(int day) {
            return Enum.GetValues(typeof(DayOfWeek)).GetValue(day).ToString();
        }

        public int DayNumber() {
            return DateTime.Now.Day;
        }
    }
}

The GetValue method I added to the controller uses parameter binding to get the int value required to call the DayOfWeek method. To get the value, I have to create a number of classes to provide the context needed to process the request. These classes are created behind the scenes when you use the ApiController, but the advantage of this approach is that it can be applied to all method parameters, rather than needing to hard-code knowledge of the action methods in the ExecuteAsync method. I am not going to go into detail about the implementation of the GetValue method because—as I explain in the next section—there is little reason to implement this functionality in a real project.

IMPLEMENTING NEW KINDS OF CONTROLLER

The Custom controller I defined in Listing 22-3 and Listing 22-5 show you how you can create a custom implementation of the IHttpController interface and still benefit from Web API features such as parameter and model binding. That said, the controller follows the model provided by the ApiController class that I have been using as the base class for all of my web service controllers so far in this book: a request is matched to action methods—some of which require arguments—that produce data or HttpResponseMessage objects that can be returned to the client.

Re-creating this model in a custom implementation of the IHttpController interface isn’t a good idea when there is a fully featured and well-tested alternative in the ApiController class, which I describe in the next section. The only time it makes sense to implement the IHttpController interface directly is when you require a completely different approach for transforming HttpRequestMessage objects into HttpResponseMessage objects. I occasionally have to create a custom implementation as a wrapper around legacy code that can’t readily be exposed through action methods, but this is a rare occurrence. For most applications, the ApiController class should be used as the base for controllers.

Understanding the ApiController Dispatch Process

The ApiController class, defined in the System.Web.Http namespace, is the base class used for most Web API controllers. The ApiController class implements the IHttpController interface and provides two important areas of functionality: dispatching requests to action methods and helping developers to keep action methods as simple as possible.

Action methods are the basic unit of logic used to process a request to create a result, expressed as a standard C# method. The ApiController class makes it possible to use C# methods as actions through its implementation of the IHttpController.ExecuteAsync method, which takes care of locating an action method for the request, using parameter and model binding to get the method arguments, and processing the method result to create an HttpResponseMessage object that can be returned through the message handler chain and, ultimately, sent to the client. It is this process that I describe in the sections that follow. Table 22-4 puts the ApiController dispatch process in context.

Table 22-4. Putting the ApiController Dispatch Process in Context

Question

Answer

What is it?

The ApiController class is the built-in implementation of the IHttpController interface and adds support for important convenience features such as action methods, action results, and filters.

When should you use it?

You should use the ApiController class as the base for all of your controllers. Implement the IHttpController interface directly only if you need to create a completely different approach to handling requests.

What do you need to know?

The ApiController class relies on implementations of the IHttpActionSelector and IHttpActionInvoker interfaces to select and invoke action methods. You can change the way that the ApiController class behaves by creating custom implementations, which I demonstrate in the “Customizing the Controller Dispatch Process” section.

As you may expect, there are a number of key dispatch tasks that the ApiController delegates to implementations of interfaces that are obtained from the services collection. I describe each interface in turn in the sections that follow and describe the customization opportunities in the “Customizing the Controller Dispatch Process” section. To provide an overview of the dispatch process, Figure 22-3 shows a revised diagram of the overall request dispatch process.

9781484200865_Fig22-03.jpg

Figure 22-3. Adding ApiController to the request dispatch process

Preparing the Example Controller

To describe the dispatch process, I need a controller that is derived from the ApiController class. Listing 22-6 shows how I have changed the Custom controller so that it is derived from ApiController, rather than implementing the IHttpController interface directly.

Listing 22-6. Deriving from the ApiController Class in the CustomController.cs File

using System;
using System.Web.Http;

namespace Dispatch.Controllers {
  public class CustomController : ApiController {

        public string DayOfWeek() {
            return DateTime.Now.ToString("dddd");
        }

        public string DayOfWeek(int day) {
            return Enum.GetValues(typeof(DayOfWeek)).GetValue(day).ToString();
        }

        public int DayNumber() {
            return DateTime.Now.Day;
        }
    }
}

The change I have highlighted in the listing is the change from implementing the IHttpController interface to deriving from the ApiController class. Since I am creating a derived controller, I have removed the implementation of the ExecuteAsync method, the GetValue method that I used to access the parameter binding feature, and the namespaces required by both.

To test the effect of the changes, start the application and navigate to the /Home/Today URL with the browser. When you click the Get Day button, the client will display a 405 (Method Not Allowed) response, as shown in Figure 22-4.

9781484200865_Fig22-04.jpg

Figure 22-4. The effect of deriving from the ApiController class

As I explained briefly in Chapter 19, the ApiController class selects action methods using a specific sequence. In the sections that follow, I describe the sequence that is used and explain why it doesn’t match the request from the client to the action methods defined by the Custom controller.

Understanding the Action Selection Process

The ApiController class delegates the selection of the action method for a request to an implementation of the IHttpActionSelector interface, which is defined in the System.Web.Http.Controllers namespace. Here is the definition of the IHttpActionSelector interface:

using System.Linq;

namespace System.Web.Http.Controllers {
    public interface IHttpActionSelector {

        HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);

        ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor
            controllerDescriptor);
    }
}

The SelectAction method is called when the controller needs to identify the action method that will be used to process the request. The parameter to the SelectAction method is an HttpControllerContext object, which describes the controller that is handling the request. (I described the HttpControllerDescriptor class in Chapter 19, but Table 22-5 recaps the properties that it defines.)

Table 22-5. The Properties Defined by the HttpControllerContext Class

Name

Description

Configuration

Returns the HttpConfiguration object that should be used to service the request. As I explain in the “Creating a Controller-Specific Configuration” section, controllers can be given their own configuration to work with.

Controller

Returns the IHttpController instance that is handling the request.

ControllerDescriptor

Returns the HttpControllerDescriptor that led to the controller being instantiated. See Chapter 22 for details.

Request

Returns the HttpRequestMessage that describes the current request.

RequestContext

Returns the HttpRequestContext that provides additional information about the request.

RouteData

Returns the IHttpRouteData object that contains the routing data for the request. See Chapters 20 and 21 for details.

Image Tip  The GetActionMapping method returns information about the set of action methods that can be selected from the specified controller. This method is used by the direct routing system—which I described in Chapter 21—so that each action method can be inspected for Route attributes. It is also useful when creating a custom IHttpActionSelector implementation, which I described in the “Creating a Custom IHttpActionSelector Implementation” section.

The result from the SelectAction method is an HttpActionDescriptor object, which defines the properties and methods described in Table 22-6.

Table 22-6. The Properties and Methods Defined by the HttpActionDescriptor Class

Name

Description

ActionBinding

Returns an HttpActionBinding that describes how the parameters defined by the action method will be bound to values from the request.

ActionName

Returns the name of the action method.

Configuration

Returns the HttpConfiguration object for the action method. See the “Creating a Controller-Specific Configuration” section for details about how to create controller-specific configurations.

ControllerDescriptor

Returns the HttpControllerDescriptor object that describes the controller that contains the action method. See Chapter 19.

ExecuteAsync(controller,arguments, cancelToken)

Executes the action method. The arguments are an HttpControllerContext object; an IDictionary<string, object> that contains the arguments for the action method, indexed by name; and a CancellationToken that can be monitored for cancellation in action methods that take time to complete.

GetCustomAttributes<T>()

Returns a collection of attributes of type T.

GetFilterPipeline()

Returns a collection of FilterInfo objects that describe the filters that have been applied to the action method. See Chapters 23 and 24 for details of filters.

GetFilters()

Returns a collection of IFilter implementation objects that represent the filters applied to the action method. See Chapters 23 and 24 for details.

GetParameters()

Returns a collection of HttpParameterDescriptor objects that describe the parameters defined by the action method.

ResultConverter

Returns an implementation of the IActionResultConverter interface that will convert the response from the action method into an HttpResponseMessage object.

ReturnType

Returns the Type produced by the action method when it is executed.

SupportedHttpMethods

Returns a collection of HttpMethod values that specify which HTTP methods the action method can support.

Understanding the Default Action Method Selection Process

The default implementation of the IHttpActionSelector interface is the ApiControllerActionSelector class, which is defined in the System.Web.Http.Controllers namespace. The process that the ApiControllerActionSelector class follows to select an action method is described in Table 22-7. I have taken some liberties in describing the process to make it easier to follow.

Table 22-7. The Selection Process Used by the ApiControllerActionSelector Class

Step

Description

1

Reflection is used to identify candidate action methods. For a method to be selected at this stage, it must be a normal method (not a constructor, for example), must be defined in a class that is derived from ApiController, and must not be annotated with the NonAction attribute.

2

The candidate action methods are inspected for the Route attribute to see whether the request can be mapped directly. If direct routing has been used, action methods whose direct route does not match the request are discarded.

3

The route data is inspected to see whether an action value has been extracted from the request. If so, the set of candidate action methods is checked to see whether there are matches for the name. Candidates that do not match the name are discarded if an action value is provided.

4

The names of the candidate action methods are checked to see whether they follow the Web API RESTful convention. I describe this convention in the “Understanding the RESTful Naming Convention” section, but the simple version is that the method name is checked to see whether it contains an HTTP verb. For example, a method called GetProducts is assumed to be able to handle GET requests. The naming convention is used to discard candidate action methods that do not support the request HTTP verb.

5

Where the naming convention is not used, the HTTP verb attributes, such as HttpGet and HttpPost, are used to discard any candidate actions that do not support the requested HTTP verb.

6

The number and type of parameters are used to discard any candidate action methods that cannot be matched to the routing data, as described in Chapters 20 and 21.

The process starts with all of the potential action methods defined by the controller and eliminates the ones that can’t handle the request at each stage. At the end of the final selection step, there should be exactly one action method remaining, which the ApiControllerActionSelector class describes using an HttpActionDescriptor object.

Image Tip  If there are no suitable action methods, then the selector hasn’t been able to match the request to one of the methods defined by the controller. If there is more than one method left, then the selector has been unable to differentiate between the action methods and doesn’t know which method should be used. Both outcomes are a problem, and both will result in an error message being sent to the client. I describe Web API error handling in Chapter 25.

Understanding the RESTful Naming Convention

One of the most important aspects of the selection process is the way that the name of the action method is used to figure out which HTTP methods the method can handle. This is the basis of how Web API makes creating a RESTful web service simple. When I created the example project in Chapter 19, I defined the Products controller, as follows:

using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using Dispatch.Models;

namespace Dispatch.Controllers {
    public class ProductsController : ApiController {
        private static List<Product> products = new List<Product> {
                new Product {ProductID = 1, Name = "Kayak", Price = 275M },
                new Product {ProductID = 2, Name = "Lifejacket", Price = 48.95M },
                new Product {ProductID = 3, Name = "Soccer Ball", Price = 19.50M },
                new Product {ProductID = 4, Name = "Thinking Cap", Price = 16M },
            };

        public IEnumerable<Product> Get() {
            return products;
        }

        public Product Get(int id) {
            return products.Where(x => x.ProductID == id).FirstOrDefault();
        }

        public Product Post(Product product) {
            product.ProductID = products.Count + 1;
            products.Add(product);
            return product;
        }
    }
}

I have used the simplest possible naming scheme and have used HTTP verbs as the action method names. The selection process looks at the names to figure out which method should be targeted for the request HTTP verb. Methods that have the same name—such as the Get methods in the Products controller—are disambiguated by the parameters they define, which are mapped against values in the routing data, as described in Chapters 20 and 21. The Products controller is relatively simple, but when combined with the default route defined in the WebApiConfig.cs file, it produces the RESTful web service API described in Table 22-8.

Table 22-8. The RESTful Web Service Defined by the Product Controller

HTTP Verb

URL

Action Method

GET

/api/products

Get()

GET

/api/products/2

Get(int)

POST

/api/products

Post(product)

Image Tip  The RESTful naming convention works only for the GET, POST, PUT, and DELETE verbs. You will need to apply an HTTP verb attribute for other request types. See the “Explicitly Specifying HTTP Verbs” section for details.

I like to keep the names I use in RESTful controllers as simple as possible, which is why I used just the HTTP verb names in the Products controller. You can use more friendly names if you prefer, and the ApiControllerActionSelector class will still select your action methods as long as their name begins with one of the verbs. A common alternative is to include the name of the model class in the method name, such that Get() would be replaced with GetProducts(), Get(id) with GetProduct(id) or GetProductById(id), and so on. The ApiControllerActionSelector class does not take into account any part of the action method name other than the HTTP verb, which means you are free to adopt any naming scheme that you find easy to work with.

Explicitly Specifying HTTP Verbs

As part of the selection process, the ApiControllerActionSelector class checks to see which HTTP verbs candidate action methods can support. For controllers that follow the RESTful naming convention, the HTTP verb is taken from the method name: an action method called Get or GetProducts is assumed to be able to handle HTTP get requests.

For non-RESTful controllers, details of which HTTP verbs are supported must be explicitly specified, and it is for this reason that sending a request to the Custom controller results in a 405 (Method Not Allowed) response, as shown in Figure 22-4. (Remember that HTTP verbs are also known as HTTP methods.)

Support for HTTP verbs is specified by applying attributes that implement the IActionHttpMethodProvider interface, which is defined in the System.Web.Http.Controllers namespace. Here is the definition of the interface:

using System.Collections.ObjectModel;
using System.Net.Http;

namespace System.Web.Http.Controllers {

    public interface IActionHttpMethodProvider {

        Collection<HttpMethod> HttpMethods { get; }
    }
}

The interface defines the HttpMethods get-only property, which returns a collection of HttpMethod values that specify the verbs that an action method supports. Web API includes a set of attributes in the System.Web.Http namespace that implement the IActionHttpMethodProvider interface and use the HttpMethods property to return the HttpMethod values that represent most commonly used HTTP verbs, as listed in Table 22-9.

Table 22-9. The Web API Attributes That Implement the IActionHttpMethodProvider Interface

Name

Description

AcceptVerbs

Declares that the action method supports one or more HTTP verbs (see the text following the table)

HttpGet

Declares that the action method supports the GET verb

HttpPost

Declares that the action method supports the POST verb

HttpDelete

Declares that the action method supports the DELETE verb

HttpPut

Declares that the action method supports the PUT verb

HttpPatch

Declares that the action method supports the PATCH verb

HttpOptions

Declares that the action method supports the OPTIONS verb

HttpHead

Declares that the action method supports the HEAD verb

The attributes whose name starts with Http declare support for a single HTTP verb and are defined for the most commonly used verbs. Use the AcceptVerbs attribute if you want to declare support for less commonly used verbs or multiple verbs. In Listing 22-7, you can see how I have applied attributes to the Custom controller.

Image Note  The HEAD verb is a little odd because it asks the web service to process the request as it would normally but send back only the headers. Supporting the HEAD verb in a web service controller is unusual, and I have done so only to demonstrate how verb attributes are used.

Listing 22-7. Specifying HTTP Verbs in the CustomController.cs File

using System;
using System.Web.Http;

namespace Dispatch.Controllers {

    public class CustomController : ApiController {

        [AcceptVerbs("GET", "HEAD")]
        public string DayOfWeek() {
            return DateTime.Now.ToString("dddd");
        }

        [HttpGet]
        [HttpHead]
        public string DayOfWeek(int day) {
            return Enum.GetValues(typeof(DayOfWeek)).GetValue(day).ToString();
        }

        [HttpGet]
        public int DayNumber() {
            return DateTime.Now.Day;
        }
    }
}

I have used the AcceptVerbs attribute to specify that the parameterless DayOfWeek method can handle GET and HEAD requests. When using the AcceptVerbs attribute, the verbs are specified as strings. The AcceptVerbs method is the simplest way to declare that an action method supports one of the verbs for which is there no predefined attribute.

Image Caution  Most web services require only a small number of verbs (usually GET, POST, PUT, and DELETE), and using other verbs—especially less well-known ones like PATCH or PURGE—is likely to cause problems, especially if you are delivering a web service for which third-party developers will write clients. I recommend careful consideration if you find yourself needing to support a verb for which there is no built-in attribute.

I have also specified that the DayOfWeek method that takes an int parameter supports the GET and HEAD verbs, but I have done so by applying two of the built-in verbs. I recommend you try to avoid creating action methods that support multiple verbs that have different meanings, but this technique can be useful if you want to treat POST and PUT the same way, which is a common web service convention (as described in Chapter 4).

Image Tip  If you apply verb attributes to action methods that follow the RESTful naming convention, then the name of the method will not be taken into account during the action method selection process. Or, put another way, the verbs specified by the attributes take precedence over the verb specified by the method name. I recommend relying on just the method name, since a mismatch between the method name and the supported HTTP verb causes confusion.

For the DayNumber method, I have used the HttpGet attribute to specify support for GET requests. This is the usual approach to using the verb attributes, such that one attribute is applied to each action method.

USING COMMON SENSE TO RESOLVE CLASHES IN PHILOSOPHY

Some developers have a mild obsession about the Don’t Repeat Yourself (DRY) principle, which aims to reduce duplication by ensuring that every operation is written just once (see http://en.wikipedia.org/wiki/Don't_repeat_yourself). This is an excellent principle—and one I follow myself—but it can be taken too far, and I often encounter controllers that contain a single action method to which all the verb attributes have been applied. When I ask why this has been done, the answer is always “because we follow DRY.”

When taken to an extreme, DRY starts to interfere with the principles of the Web API model, which encourages distinct action methods for each operation that the web service provides, unless those operations are essentially indistinguishable. That means there will always be a degree of duplication since most action methods will need to access the model repository, validate user data, and handle errors. Duplication can be reduced by defining nonaction methods that contain common code, but collapsing action methods together in the name of deduplication causes long-term maintenance problems as the web service evolves because all of the request handling code is squashed together, making changes difficult to apply and test.

DRY is a good principle to follow, but it would be better expressed as Don’t Repeat Yourself Unless Doing So Prevents Long-Term Problems. If you find that your controllers look like the direct implementation of the IHttpController interface that I created at the start of the chapter, then dial back on the deduplication and use common sense to strike a balance between the principles and patterns that you follow.

Now that I have specified the HTTP verbs that the action methods in my non-RESTful controller support, the default process is able to select methods to handle requests. To see the effect of the verb attributes, start the application, use the browser to navigate to the /Home/Today URL, and click the Get Day button. Unlike the error message displayed in Figure 22-4, the client will receive a 200 (OK) response and the requested data, as shown in Figure 22-5.

9781484200865_Fig22-05.jpg

Figure 22-5. The effect of applying HTTP verb attributes to a non-RESTful controller

Understanding the RESTful/Non-RESTful Routing Problem

Before continuing to describe the ApiController dispatch process, I am going to switch topics and describe a common problem that I deliberately introduced into the example project, explain what causes it, and illustrate how it can be avoided.

Previously, I added a route to the WebApiConfig.cs file that specified an action variable so that I could target requests to the Today controller and, more recently, the Custom controller. At the time I noted that adding the route caused a problem, which I can describe now that I have explained how action methods are selected and how the RESTful naming convention works.

Understanding the Problem

To see the problem, start the application, use the browser to navigate to the /Home/Index URL, and click each of the Get All, Get One, and Post buttons in turn. The Get All and Post buttons will work as expected—the client will request the /api/products URL, and the HTTP verb will be used to select the (parameterless) Get method or the Post method. When the Get One button is clicked, the client will request the /api/products/2 URL, and Web API will respond with a 404 (Not Found) error, as shown in Figure 22-6.

9781484200865_Fig22-06.jpg

Figure 22-6. A problem with a RESTful controller

As a reminder, here is the routing configuration for the example application:

using System.Web.Http;

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

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

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

This is a common problem when mixing Web API controllers that follow the RESTful naming convention with those that don’t. For RESTful controllers, the action method selection relies on the action method name starting with the HTTP verb that the action supports, using this routing template:

...
routeTemplate: "api/{controller}/{id}"
...

The non-RESTful controllers rely on the other route, which defines an action variable to match requests:

...
routeTemplate: "api/{controller}/{action}/{day}",
...

The non-RESTful route matches requests for RESTful controllers. A request for a URL such as /api/products works because the non-RESTful route template will match only three- or four-segment URLs and so the request is passed on to the RESTful route, which will match two- or three-segment URLs (the final segment for both templates is optional).

For a three-segment URL such as /api/products/2—which is what the client sends when the Get One button is clicked—the non-RESTful route matches the request and assigns 2 as the value of the action variable. The request is passed through the Web API dispatch process until it reaches the ApiControllerActionSelector class, which detects the presence of the action value in the route data (step 3 in Table 22-7) but can’t match it to a method in the ProductsController class and returns the 404 (Not Found) result.

Solving the Problem with Route Specificity

There are two ways to solve this problem. The first is to create more specific routes, either by constraining convention-based routes or by using direct routes. This approach works well in applications that contain a lot of one kind of controller (RESTful or non-RESTful) and only a small number of the other kind. In Listing 22-8, you can see how I have narrowed the scope of the route for the non-RESTful controllers.

Listing 22-8. Narrowing the Non-RESTful Route in the WebApiConfig.cs File

using System.Web.Http;
using System.Web.Http.Routing.Constraints;

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

            config.Routes.MapHttpRoute(
                name: "ActionMethods",
                routeTemplate: "api/{controller}/{action}/{day}",
                defaults: new { day = RouteParameter.Optional },
        constraints: new { controller = new RegexRouteConstraint("today|custom")}
            );

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

I have added a constraint to the first route so that it will match only those requests whose second segment—the controller segment—is either today or custom. This ensures that requests for the RESTful Products controller won’t be matched and will be routed correctly. To test the change, start the application, navigate to the /Home/Index URL with the browser, and click the Get One button. Rather than the error shown in Figure 22-6, you will see a Success response, and the client will display the details of a data object.

Image Caution  This problem cannot be solved by re-ordering the routes in the WebApiConfig.cs file, which just has the effect of changing the requests that are mismatched but doesn’t address the underlying problem.

Solving the Problem with a Route Template Prefix

I do not like constraining routes in this way because it means that there is a list of controller names that has to be kept synchronized with the classes in the application—something that adds to the testing burden and that can cause problems once the application is deployed. Direct routes are a more elegant solution to the problem, but I prefer not to use them, as I explained in Chapter 20.

The approach I prefer to take is to create a separate prefix for one category of controller so that, for example, RESTful controllers are reached through URLs with the normal /api prefix and non-RESTful controllers are reached through a different prefix, such as /api/nonrest. Listing 22-9 shows the changes I made to the WebApiConfig.cs file to implement this change.

Listing 22-9. Changing the Non-RESTful Controller Route Template in the WebApiConfig.cs File

using System.Web.Http;

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

            config.Routes.MapHttpRoute(
                name: "ActionMethods",
                routeTemplate: "api/nrest/{controller}/{action}/{day}",
                defaults: new { day = RouteParameter.Optional }
            );

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

This change means that the non-RESTful route will match URLs with four or five segments and the RESTful route will match two- and three-segment URLs, ensuring that requests won’t be matched by the wrong route. The drawback of this technique is that it requires the client to make requests with the right prefix, as shown in Listing 22-10.

Listing 22-10. Changing the URL Prefix in the today.js File

var response = ko.observable("Ready");
var gotError = ko.observable(false);

var sendRequest = function () {
  $.ajax("/api/nrest/custom/dayofweek/1", {
        type: "GET",
        success: function (data) {
            gotError(false);
            response(data);
        },
        error: function (jqXHR) {
            gotError(true);
            response(jqXHR.status + " (" + jqXHR.statusText + ")");
        }
    });
};

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

Understanding Filters

Having selected a method through the IHttpActionSelector interface, the ApiController classes executes the filters that have been applied to the method. As with the MVC framework, Web API filters inject additional logic into the request dispatch process and are used to implement cross-cutting concerns, which are functionality that is used throughout the application but doesn’t fit neatly into the MVC pattern without breaking the separation-of-concerns principle.

Image Tip  Filters support a design pattern called Aspect-Oriented Programming, which is described at http://en.wikipedia.org/wiki/Aspect-oriented_programming.

Web API filters are defined through the IFilter interface, which is defined in the System.Web.Http.Filters namespace. The IFilter interface isn’t used directly but provides the base from which the interfaces that describe the five different kinds of Web API filter are defined, as described in Table 22-10.

Table 22-10. The Web API Filter Types and Interfaces

Filter Type

Interface

Description

Authentication

IAuthenticationFilter

This kind of filter is used to require users or clients to be authenticated before action methods can be executed.

Authorization

IAuthorizationFilter

This kind of filter is used to restrict access to action methods to specific users or groups.

Action

IActionFilter

This kind of filter is used to manipulate the request or response.

Exception

IExceptionFilter

This kind of filter is used to handle exceptions thrown by the action method or another kind of filer.

Override

IOverrideFilter

This kind of filter is used to tailor the behavior of other filters for individual action methods.

Image Caution  The Web API filter interface names are the same as the equivalent interfaces in the MVC Framework. Be careful when creating filters because it is easy to get the namespaces mixed up and create an MVC filter rather than one for Web API.

I describe filters in detail in Chapters 23 and 24, but I wanted to introduce them in this chapter because they are part of the ApiController dispatch process. Filters are supported by the ApiController class and are not delegated to an interface like some of the other dispatch tasks, which means they are not available when you create a controller from the IHttpController interface.

Understanding the Action Method Execution Process

At this point, the ApiController—or its delegates—has selected an action method and prepared the filters that have been applied to it. The next step is to execute the action method and obtain the HttpResponseMessage object that will be passed back along the dispatch chain and used to send the response to the client.

Action method execution is delegated to an implementation of the IHttpActionInvoker interface, which is defined as follows:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace System.Web.Http.Controllers {

    public interface IHttpActionInvoker {

        Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext,
            CancellationToken cancellationToken);
    }
}

The IHttpActionInvoker interface defines the InvokeActionAsync method, which is required to asynchronously execute the action described by the HttpActionContext argument (which is the result of the selection process) and return an HttpResponseMessage that can be used to send the response to the client.

The HttpActionContext argument provides the means to execute the action method through its ActionDescriptor property, which returns an instance of the HttpActionDescriptor class. The HttpActionDescriptor, in turn, defines the ExecuteAsync method, which performs parameter and model binding, executes the selected action method, and generates the result.

The default implementation of the IHttpActionInvoker interface is the ApiControllerActionInvoker class, defined in the System.Web.Http.Controllers namespace. This class is responsible for converting the result of the action method into an HttpResponseMessage object and is the component that allows the ApiController class to support built-in C# types and the IHttpActionResult interface and its implementations, which I described in Chapter 11.

Customizing the Controller Dispatch Process

There are several ways in which the ApiController dispatch process can be customized, and I describe them in the following sections. The default behavior will suit most applications, but if you do perform customizations, then make sure you test thoroughly and that you really can’t get what you need through other features, such as filters (which I describe in Chapters 23 and 24).

The ApiController class provides a set of properties that provide access to context objects that are useful for creating customizations. The ApiController context properties are described in Table 22-11.

Table 22-11. The Context Properties Defined by the ApiController Class

Name

Description

ActionContext

Returns an HttpActionContext object that describes the currently executing action and that provides many of the context objects that the ApiController class exposes through the properties in this table

Configuration

Returns the HttpConfiguration object that should be used to process the request

ControllerContext

Returns the HttpControllerContext for this request, as described in Chapter 19

ModelState

Returns the ModelStateDictionary object, used by the model validation process that I describe in Chapter 18

Request

Returns the HttpRequestMessage object that describes the current request

RequestContext

Returns the HttpRequestContext object for the request

User

Returns an implementation of the IPrincipal interface that identifies the user associated with the current request, as described in Chapters 23 and 24

You can use these properties in your action methods as well, but this isn’t usually required since action methods in a web service controller are generally focused on getting data to or from the repository. Action methods don’t usually interact directly with the HttpRequestMessage and HttpResponseMessage objects and instead rely on parameter/model binding and result conversion to get data from the request and create a result. Table 22-12 puts customizing the ApiController dispatch process into context.

Table 22-12. Putting Customizing the ApiController Dispatch Process in Context

Question

Answer

What is it?

The ApiController class relies on the IHttpActionSelector and IHttpActionInvoker interfaces to select and execute action methods. Custom implementations of these interfaces allow you to change the dispatch process.

When should you use it?

The built-in implementations are suitable for almost all web services, and custom implementations should be created with caution and tested thoroughly.

What do you need to know?

You can selectively apply custom implementations by creating a controller-specific configuration, as described in the “Creating a Controller-Specific Configuration” section.

Creating a Custom IHttpActionInvoker Implementation

The IHttpActionInvoker interface has two responsibilities: it executes the action method, and it converts the result it produces into an HttpResponseMessage object. There is no real value in changing the way that methods are invoked, but creating a custom implementation of the IHttpActionInvoker interface can be a useful way of providing special handling for return types. Listing 22-11 shows the contents of the CustomActionInvoker.cs class file that I added to the Infrastructure folder.

Listing 22-11. The Contents of the CustomActionInvoker.cs File

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;

namespace Dispatch.Infrastructure {
    public class CustomActionInvoker : IHttpActionInvoker {

        public async Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext
                actionContext, CancellationToken cancellationToken) {

            object result = await actionContext.ActionDescriptor.ExecuteAsync(
                actionContext.ControllerContext, actionContext.ActionArguments,
                    cancellationToken);

            if (result is HttpResponseMessage) {
                return (HttpResponseMessage)result;
            } else if (result is IHttpActionResult) {
                return await ((IHttpActionResult)result).ExecuteAsync(cancellationToken);
            } else if ( actionContext.ActionDescriptor.ReturnType != typeof(string)) {
                return actionContext.ActionDescriptor.ResultConverter.Convert(
                    actionContext.ControllerContext, result);
            } else {
                return new ValueResultConverter<string[]>().Convert(
                    actionContext.ControllerContext, new string[] { (string)result });
            }
        }
    }
}

This implementation of the IHttpActionInvoker executes the action method and checks the result that the method produces. It handles four types of result type.

  • If the action method result is an HttpResponseMessage object, then the CustomActionInvoker class returns the object without modification as the result of the InvokeActionAsync method.
  • If the action method result is an implementation of the IHttpActionResult interface, then the CustomActionInvoker class calls the ExecuteAsync method to create an HttpResponseMessage object, which is returned as the result of the InvokeActionAsync method. (I described the IHttpActionResult interface and its ExecuteAsync method in Chapter 19.)
  • If the result is a string, then the CustomActionInvoker class creates a string array with the result as the only element and uses a result converter to create the HttpResponseMessage that I need as the result of the InvokeActionAsync method.
  • For all other result types, the CustomActionInvoker class uses the result converter provided by the HttpActionContext.ControllerContext property to create the HttpResponseMessage object.

At the heart of my custom implementation is the use of result converters, which take a result type and create an HttpResponseMessage that can be returned through the dispatch process. Result converters are defined by the IActionResultConverter interface.

namespace System.Web.Http.Controllers {
    public interface IActionResultConverter {
        HttpResponseMessage Convert(HttpControllerContext controllerContext,
            object actionResult);
    }
}

The Convert method accepts an HttpControllerContext object and the result from the action method and is required to return an HttpResponseMessage. There are two built-in Web API implementations of the IActionResultConverter interface, the ValueResultConverter and VoidResultConverter classes, both of which are defined in the System.Web.Http.Controllers namespace.

The built-in implementations are simple. The ValueResultConverter class calls the HttpRequestMessage.CreateResponse extension method I described in Chapter 11 to produce an HttpResponseMessage object with a 200 (OK) status code and, in doing so, encodes the result data using the media type formatters I described in Part 2.

The VoidResultConverter class also calls the CreateResponse extension method, but with a 204 (No Content) status code. As you might imagine from the name, the VoidResultConverter is used when action methods are defined with the C# void keyword.

The ValueResultConverter class is strongly typed, and that means I have to create an instance of ValueResultConverter<string[]> in my custom IHttpActionInvoker because the object returned by the HttpActionContext.ActionDescriptor.ResultConverter property has been set up to handle the declared result type of the action method.

...
return new ValueResultConverter<string[]>().Convert(
    actionContext.ControllerContext, new string[] { (string)result });
...

I have to create an instance only when I want an HttpResponseMessage that contains data of a type that is different from the one returned by the action method.

Image Note  I am not going to register or test the CustomActionInvoker class until the “Creating a Controller-Specific Configuration” section, where I show you how to apply it to a single controller class.

Creating a Custom IHttpActionSelector Implementation

A custom implementation of the IHttpActionSelector interface allows you to take control of the way that requests are matched to an action method. Listing 22-12 shows the contents of the CustomActionSelector.cs file that I added to the Infrastructure folder.

Listing 22-12. The Contents of the CustomActionSelector.cs File

using System;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Web.Http;
using System.Web.Http.Controllers;

namespace Dispatch.Infrastructure {
    public class CustomActionSelector : IHttpActionSelector {

        public ILookup<string, HttpActionDescriptor>
            GetActionMapping(HttpControllerDescriptor descriptor) {
            return descriptor.ControllerType.GetMethods()
                .Where(x => x.IsPublic
                    && !x.IsSpecialName
                    && x.GetCustomAttribute<NonActionAttribute>() == null)
                .Select(x => (HttpActionDescriptor)
                     new ReflectedHttpActionDescriptor(descriptor, x))
                .OrderBy(x => x.GetParameters().Count)
                .ToLookup(x => x.ActionName, StringComparer.OrdinalIgnoreCase);
        }

        public HttpActionDescriptor SelectAction(HttpControllerContext context) {
            if (context.RouteData.Values.ContainsKey("action")) {
                string actionName = (string)context.RouteData.Values["action"];
                return GetActionMapping(context.ControllerDescriptor)
                    [actionName].First();
            } else {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }
    }
}

There are few compelling requirements for a custom implementation of the IHttpActionSelector interface, which is why the CustomActionSelector class does something that would not be useful in a real project: it selects action methods by name, and if there are multiple methods with the same name, then the one with the fewest parameters is used.

The IHttpActionSelector interface provides information about action methods through the abstract HttpActionDescriptor class. You can create a concrete implementation of HttpActionDescriptor, but it is simpler to use the ReflectedHttpActionDescriptor class, which performs all the reflection required to create and populate the HttpActionDescriptor properties.

I use the GetActionMapping method to create an enumeration of the action methods available in the specified controller and filter that enumeration in the SelectAction method based on the value of the action routing variable.

Image Note  I don’t implement the RESTful naming convention in my custom IHttpActionSelector implementation, which means that only requests that can be matched by a route template that has an action variable segment are supported.

Creating a Controller-Specific Configuration

The custom implementations of the IHttpActionInvoker and IHttpActionSelector interfaces that I created in the previous section are not especially useful. In fact, they work well only with the Custom controller, which has action methods that return string values (which is what I look for in the CustomActionInvoker class) and methods with the same name but different numbers of parameters (which is what the CustomActionSelector handles).

If I replaced the default implementations in the services collection with my custom classes in the WebApiConfig.cs file, they would be applied to all controllers. In the case of the CustomActionInvoker class, this means that requests to controllers relying on the RESTful naming convention would not be routed correctly.

I can resolve this problem by creating a configuration that uses my custom classes and applying it selectively to the controllers whose action method selection and execution I want to change. This is done by creating an attribute class that implements the IControllerConfiguration interface, defined as follows:

namespace System.Web.Http.Controllers {

    public interface IControllerConfiguration {

        void Initialize(HttpControllerSettings controllerSettings,
            HttpControllerDescriptor controllerDescriptor);
    }
}

The interface defines the Initialize method, which is used to populate an HttpControllerSettings object passed as a method parameter. The other parameter is an HttpControllerDescriptor object, which provides context information about the controller that is being processed (and which I described in Chapter 19). The HttpControllerSettings class is used to override configuration settings using the properties defined in Table 22-13.

Table 22-13. The Properties Defined by the HttpControllerSettings Class

Name

Description

Formatters

Returns the collection of media type formatters (see Part 2)

ParameterBindingRules

Returns the collection of parameter binding rules (see Part 2)

Services

Returns the collection of services (see Part 2)

Creating a Custom IControllerConfiguration Interface

There is no default implementation of the IControllerConfiguration, but it is easy to create one. Listing 22-13 shows the contents of the CustomControllerConfigAttribute.cs class file that I added to the Infrastructure folder.

Listing 22-13. The Contents of the CustomControllerConfigAttribute.cs File

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

namespace Dispatch.Infrastructure {

    public class CustomControllerConfigAttribute : Attribute, IControllerConfiguration {

        public void Initialize(HttpControllerSettings controllerSettings,
                HttpControllerDescriptor controllerDescriptor) {

            controllerSettings.Services.Replace(typeof(IHttpActionSelector),
                new CustomActionSelector());
            controllerSettings.Services.Replace(typeof(IHttpActionInvoker),
                new CustomActionInvoker());
        }
    }
}

Custom implementations of the IControllerConfiguration interface are derived from the Attribute class so they can be applied to controller classes. My implementation of the Initialize method uses the HttpControllerDescriptor method to replace the default implementations of the IHttpActionSelector and IHttpActionInvoker interfaces with instances of my custom classes.

Listing 22-14 shows how I have applied the CustomControllerConfigAttribute attribute to the Custom controller.

Listing 22-14. Applying the CustomControllerConfigAttribute in the CustomController.cs File

using System;
using System.Web.Http;
using Dispatch.Infrastructure;

namespace Dispatch.Controllers {

  [CustomControllerConfig]
    public class CustomController : ApiController {

        [AcceptVerbs("GET", "HEAD")]
        public string DayOfWeek() {
            return DateTime.Now.ToString("dddd");
        }

        [HttpGet]
        [HttpHead]
        public string DayOfWeek(int day) {
            return Enum.GetValues(typeof(DayOfWeek)).GetValue(day).ToString();
        }

        [HttpGet]
        public int DayNumber() {
            return DateTime.Now.Day;
        }
    }
}

The effect of applying the CustomControllerConfig attribute is that the custom selector and invoker classes are used to handle requests that target the Custom controller, while other requests for other controllers will be handled by the default implementations.

Image Tip  This is not specific to the ApiController class. Custom configurations can be applied to any IHttpController implementation.

Summary

In this chapter, I described how Web API controllers work and, in particular, the dispatch process that the default controller base class provides. I explained how action methods are selected and invoked, touched upon request filters, and demonstrated how the default dispatch process can be customized. In Chapter 23, I begin describing filters, which allow extra logic to be injected into the dispatch process.

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

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