CHAPTER 9

image

Controllers and Actions

If we think of the ASP.NET Web API framework as a human body, the controller and actions part is the heart of that body. Many components of the Web API framework are glued together by the ApiController class, which is the default controller implementation provided out of the box. Filters, model binding, formatters, action selection—these are all part of the controller execution process.

In this chapter, we will first explain how a request comes into a controller. Then we will take a different approach and get started with the hard way: implementing our own controller to understand what is really going on. Later on, we will look at ApiController to discover the possibilities and features it offers.

Overview

In ASP.NET Web API, the controller part is one of the components whose main responsibility is to process the business logic in view of request inputs and to return a proper result in the desired format. The controller class should be an implementation of the System.Web.Http.IHttpController interface, which has only one method, named ExecuteAsync, as shown in Listing 9-1.

Listing 9-1.  System.Web.Http.IHttpController

public interface IHttpController {

    Task<HttpResponseMessage> ExecuteAsync(
        HttpControllerContext controllerContext,
        CancellationToken cancellationToken);
}

As was seen in Chapter 8, the first part of the processing pipeline, the part that deals with the request, is the routing component. If there is a route match, the request is taken to a higher level in the pipeline. This is the part where the message handlers are invoked (the responsibilities of the message handlers and how they fit inside the pipeline are explained in Chapter 10). If there is no message handler registered to run, then the request is directly taken to HttpRoutingDispatcher, which we explain in the next section.

Overview of HttpRoutingDispatcher

HttpRoutingDispatcher examines the matched route and chooses which message handler to call to process the request. HttpRoutingDispatcher is a message handler itself and is derived from the HttpMessageHandler abstract class.

The decision being made here is based on whether the matched route has a handler attached to it or not. We will see in Chapter 10 how to attach a custom handler to a route in some detail, including examples. Briefly,  however, if IHttpRoute.Handler is null, then HttpRoutingDispatcher delegates it to the HttpControllerDispatcher, which is itself a message handler. If there is a handler attached to the matched route, the request will be processed by that message handler from that point on.

In terms of the controller component inside the framework, this is a life-altering decision, because if there is a handler attached to the matched route, the controller component will not be there unless the custom handler handles it specifically. So what we can take from this is that the HttpControllerDispatcher is the one that dispatches the request to an appropriate controller and processes the request accordingly.

The main responsibility of the HttpControllerDispatcher message handler is to select the appropriate controller type, activate the selected controller type, and invoke the ExecuteAsync method of the selected controller. The HttpControllerDispatcher is also the one that returns a “404 Not Found” HttpResponseMessage if there is no controller match.

The controller type selection is delegated to a controller selector service, which is an implementation of IHttpControllerSelector interface. Before moving forward, let’s go through the controller type selection logic in the next subsection.

Controller Type Selection

The controller type selection is separated from the HttpControllerDispatcher implementation and performed by a service that is an implementation of the IHttpControllerSelector interface. The controller selector service is registered through the ServicesContainer, which was explained in the “Configuration” section in Chapter 1.

The IHttpControllerSelector interface has two working methods, as shown in Listing 9-2. We care only about the SelectController method in the present case.

Listing 9-2.  IHttpControllerSelector Interface

public interface IHttpControllerSelector {

    HttpControllerDescriptor SelectController(HttpRequestMessage request);
    IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
}

By default, the DefaultHttpControllerSelector is registered as a controller selector service. The controller selection will be performed by this class unless you replace it with a custom implementation, as was also explained in Chapter 1’s discussion of how to replace an existing service (see the “Configuration” section). To find a controller, the DefaultHttpControllerSelector looks for some specific characteristics, as follows:

  • Public classes that implement System.Web.Http.IHttpController interface.
  • Public classes that have a suffix of Controller. This lookup is case insensitive.
  • Public classes that have the same string value as the controller parameter of the matched route before the Controller suffix. For example, if the controller parameter of the matched route is Cars, the class name should be CarsController (case insensitive).

image Note  The DefaultHttpControllerSelector class actually delegates some of the action to other services. These are assembly resolver (IAssembliesResolver) and controller type resolver (IHttpControllerTypeResolver) services. The default implementations of these services are also provided through ServicesContainer asDefaultAssembliesResolver and DefaultHttpControllerTypeResolver and can be replaced with your own implementations. However, we won’t go through these services in this book because it is highly unlikely that you will want to replace these services with your own implementations.

According to these criteria, the DefaultHttpControllerSelector class also checks whether there are multiple matched controllers after the lookup is completed. If this is the case, the DefaultHttpControllerSelector throws an ambiguous controller exception, indicating that there are multiple matched controllers for the request. If there is only one matched controller type, the DefaultHttpControllerSelector will create an HttpControllerDescriptor instance and return it to its caller, which is the HttpControllerDispatcher.

From this point on, the HttpControllerDescriptor instance is responsable to construct the controller through its SelectController method. The activation of the controller type is also handled by a service: IHttpControllerActivator which has only one method named Create. By default, the DefaultHttpControllerActivator is registered as IHttpControllerActivator implementation. The DefaultHttpControllerActivator works with the dependency resolver, which we will dig into in Chapter 14, in order to construct the controller instance. If the dependency resolver is not able to create a controller instance, the DefaultHttpControllerActivator’s Create method tries to perform this action itself assuming that the controller has a parameterless constructor. If the controller doesn’t have a parameterless constructor and there is no custom dependency resolver registered, the DefaultHttpControllerActivator’s Create method will throw an exception indicating this situation.

There are some words here which needs to be applied and I indicated them by putting suffix. Also, can we add the below one as note after this paragraph:

It’s also worth pointing out here that unlike the message handlers, which we will see in Chapter 12, a controller instance is activated for each request and disposed at the end of the request.

Creating a Controller

As has been mentioned in previous sections of this chapter, the controller class needs to be an implementation of the IHttpController interface. Listing 9-1 shows that the IHttpController interface has only one method, named ExecuteAsync. The ExecuteAsync method is invoked by the HttpControllerDispatcher to process the request and return an HttpResponseMessage instance. The ExecuteAsync method actually returns a Task<HttpResponseMessage> instance. This means that the method is capable of asynchronous processing.

The ExecuteAsync method also takes two parameters. One is HttpControllerContext, and the other is a CancellationToken. The HttpControllerContext holds all the necessary information about the request and the controller, including the request message, route information, controller information, and the like, as shown in Table 9-1.

Table 9-1. HttpControllerContext Class Properties

Name Type Description
Configuration HttpConfiguration Holds the configuration information for the request.
Request HttpRequestMessage Represents the HttpRequestMessage of the request.
RouteData IHttpRouteData Holds the matched route information for the request.
ControllerDescriptor HttpControllerDescriptor Holds the description and configuration for a controller as HttpControllerDescriptor object.
Controller IHttpController Holds the controller instance itself.

These properties provide a controller enough information to have an idea about the request and return an appropriate response. In the next subsection, we will see an example of a simple controller.

IHttpController Interface and Your Own Controller

Creating a controller to handle the requests is a fairly easy task. All you need to do is create a class that implements IHttpController, handles the request inside the ExecuteAsync method, and then returns the response from there.

Listing 9-3 shows a sample controller that returns a list of cars for a GET request. As the controller class name is CarsController and assuming that the default route is used, any request that comes to /api/cars will be dispatched to CarsController.

Listing 9-3.  CarsController Sample

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http.Controllers;

public class CarsController : IHttpController {

    public Task<HttpResponseMessage> ExecuteAsync(
        HttpControllerContext controllerContext,
        CancellationToken cancellationToken) {

        var request = controllerContext.Request;

        if (request.Method != HttpMethod.Get) {

            var notAllowedResponse =
                new HttpResponseMessage(
                  HttpStatusCode.MethodNotAllowed);

            return Task.FromResult(notAllowedResponse);
        }

        var cars = new[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };

        var response =
            request.CreateResponse(HttpStatusCode.OK, cars);

        return Task.FromResult(response);
    }
}

Inside the ExecuteAsync method, we perform several operations.

  • First, we check whether or not the request is a GET request.
  • If the request is not a GET request, then we return a “405 Method Not Allowed” response.
  • If the request is a GET request, we continue to the next step and return a list of Car instances. In order to return this list, we use the CreateResponse extension method of HttpRequestMessage, because we want to return the response in the format that best matches what the client has asked for. The CreateResponse method performs the content negotiation, as we will see in Chapter 12.
  • In order to return the response message in either case, we use the FromResult method of the Task class to create a completed Task<HttpResponseMessage> object. Because we don’t actually run anything asynchronously here, we want to avoid the cost of a thread switch, and the FromResult method gives us the ability to create a pre-completed Task object, as we have seen in Chapter 2.

Most of the operations just listed are not actually related to business logic. We are trying to adapt to the framework, which is not a very good programming model. Besides, the sample in Listing 9-3 deals only with GET requests. The experience would be worse if we were dealing with other types of HTTP requests.

In order to have a better programming model, the ASP.NET Web API framework includes an abstract controller base class, named ApiController, that implements the IHttpController interface. We will explain and use this abstract class for the rest of this chapter.

ApiControllers: Out of the Box IHttpController Implementation

The System.Web.Http.ApiController abstract controller class provides a very clean programming model and request-processing pipeline containing various types of components. When you use ApiController class as a base controller class, you will get actions, action selection logic, filters, and content negotiation, all of them free.

At the fundamental level, ApiController is no different from a typical controller. As was explained before, the HttpControllerDispatcher has no knowledge of the exact type of your controller class, nor is it concerned about it. The only thing it knows is that the controller class implements the IHttpController interface, and hence it will invoke the ExecuteAsync method of that controller. When you use the ApiController class as the base class of your controller, the same applies. However, under the hood, there are a few important operations that are triggered by the ExecuteAsync method of the ApiController. They include invoking the filters, processing the action selection logic, parameter binding, and invoking the selected action, among others.

image Note  In this chapter, we mention filters, which are one of the extensibility points of the framework. But we won’t cover them in this chapter. Instead, we have dedicated Chapter 11 to filters.

Your First Controller Derived From ApiController

Let’s try to implement a controller that holds the same logic as Listing 9-3 but uses ApiController as the base class this time. Listing 9-4 shows how it is implemented.

Listing 9-4.  CarsController with ApiController

public class CarsController : ApiController {

    public string[] Get() {

        return new string[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

When we send a GET request to /api/cars, we get the same result as in Listing 9-3 but obviously with a very clean programming model. Inside our CarsController in Listing 9-4, we didn’t perform an HTTP method check, and we are returning just the object we care about. When a request comes to this CarsController controller class, the action selection logic is performed, at which we will look closely in the Action Selection” section farther along in this chapter. The default convention here is that the action name should be the same name as the HTTP method name through which the request was made or with which it starts such as “GetCars.” The lookup here is case insensitive.

Before moving forward, let’s get to know the members of the ApiController abstract class first.

ApiController Members

ApiController exposes some properties and provides some methods that make it easy to work with the request. This subsection will list and explain those properties and methods. Table 9-2 lists public properties of the ApiController class.

Besides the properties listed in Table 9-2, there are three different public virtual methods of the ApiController class, as Listing 9-5 shows.

Table 9-2. ApiController Properties

Name Type Description
Configuration HttpConfiguration Holds the configuration information for the request.
ControllerContext HttpControllerContext Holds the context class that contains information for a single HTTP operation for the given controller.
ModelState ModelStateDictionary Holds the ModelStateDictionary dictionary object that carries the model state after the model binding process.
Request HttpRequestMessage Represents the HttpRequestMessage of the request.
Url UrlHelper A helper class to work generate URLs by providing route values.
User IPrincipal Holds the principal object that is set to Thread.CurrentPrincipal.

Listing 9-5.  ApiController Public Methods

public abstract class ApiController : IHttpController, IDisposable {
    
    //Lines omitted for brevity
  
    protected virtual void Dispose(bool disposing);
    
    public virtual Task<HttpResponseMessage> ExecuteAsync(
        HttpControllerContext controllerContext, CancellationToken cancellationToken);
        
    protected virtual void Initialize(HttpControllerContext controllerContext);
}

The Initialize method sets up the proper public properties before the ExecuteAsync method logic is executed.

Controller Actions

One of the benefits of using the provided base controller class ApiController is the notion of action methods. Action methods are the public methods that we can provide in order to process the request and provide a response message. They are invoked on the basis of the action selection logic.

Controller action methods enable separation of the logic of each request. However, all actions under a controller can reach the context of that controller. For example, any private properties of the controller can be accessed by any action methods under the controller.

First of all, let’s set up the infrastructure that we will use from now on in the chapter. We will have a CarsContext class, from which we will then be able to pull and manipulate our car gallery data (Listing 9-6).

Listing 9-6.  CarsContext Class

public class Car {

    public int Id { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
    public int Year { get; set; }
    public float Price { get; set; }
}
public class CarsContext {

    private static int _nextId = 9;
    private static object _incLock = new object();

    // data store
    readonly static ConcurrentDictionary<int, Car> _carsDictionary =
        new ConcurrentDictionary<int, Car>(
          new HashSet<KeyValuePair<int, Car>> {
            new KeyValuePair<int, Car>(
                1,
                new Car {
                    Id = 1,
                    Make = "Make1",
                    Model = "Model1",
                    Year = 2010,
                    Price = 10732.2F
                }
            ),
            new KeyValuePair<int, Car>(
                  2,
                  new Car {
                      Id = 2,
                      Make = "Make2",
                      Model = "Model2",
                      Year = 2008,
                      Price = 27233.1F
                  }
            ),

            //Lines omitted for brevity . . .
    });

    public IEnumerable<Car> All {
        get {
            return _carsDictionary.Values;
        }
    }

    public IEnumerable<Car> Get(Func<Car, bool> predicate) {

        return _carsDictionary.Values.Where(predicate);
    
}

    public Car GetSingle(Func<Car, bool> predicate) {

        return _carsDictionary.Values.FirstOrDefault(predicate);
    }

    public Car Add(Car car) {

        lock (_incLock) {

            car.Id = _nextId;
            _carsDictionary.TryAdd(car.Id, car);
            _nextId++;
        }

        return car;
    }}

With our little data class in place, we can expose the data we have so that it can be consumed by our clients. Let’s make it possible to retrieve the list of all cars first. Listing 9-7 shows the implementation.

Listing 9-7.  CarsController with a Get Action

public class CarsController : ApiController {

    private readonly CarsContext _carsCtx = new CarsContext();

    public IEnumerable<Car> Get() {

        return _carsCtx.All;
    }
}

As you can see, by directly returning the IEnumerable<Car> object, we will get the content negotiation by default. This means that the list of cars will be sent to the client in the proper format. In addition, we have registered our default route, as Listing 9-8 shows.

Listing 9-8.  Our Web API Route Registration

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    var routes = config.Routes;

    routes.MapHttpRoute(
        "DefaultHttpRoute",
        "api/{controller}/{id}",
        new { id = RouteParameter.Optional }
    );
}

Figure 9-1 shows the sample GET request being made against this service and the response we get back.

9781430247258_Fig09-01.jpg

Figure 9-1. GET request against /api/cars

As the request is a GET request, the Get method of the CarsController has been invoked. In the request headers, we indicate that we want to get the response in XML format. The server honors the request and returns the response in that format.

Now, let’s make a POST request to the same URI and see what happens ( Figure 9-2).

9781430247258_Fig09-02.jpg

Figure 9-2. POST request against /api/cars

For this POST request, we got a response containing the “405 Method Not Allowed” status code because we haven’t defined a method named POST or starting with Post in the CarsController class. Now we will create a method to process the POST requests to insert a new car object in the cars collection. Listing 9-9 shows the implementation of the PostCar action method.

Listing 9-9.  CarsController with a Post Action

public class CarsController : ApiController {

    private readonly CarsContext _carsCtx =
        new CarsContext();

    //Lines omitted for brevity

    public HttpResponseMessage PostCar(Car car) {

        var createdCar = _carsCtx.Add(car);
        var response = Request.CreateResponse(
            HttpStatusCode.Created, createdCar);
            
        response.Headers.Location = new Uri(
            Url.Link("DefaultHttpRoute", new {
                id = createdCar.Id }));

        return response;
    }
}

This time, we have an action method with a parameter. This action parameter will be bound at runtime according to the request body (to be discussed in depth in Chapter 12). So if we want to add a new car, we need to include the car object in the message body of our POST request in the correct format. On the other hand, we also created an HttpResponseMessage instance through the CreateResponse extension method for the HttpRequestMessage. In this way, we can control the HTTP response message being sent. We also have set the status code of this response message as “201 Created,” and the response will carry the created car object inside the response message body.  You will see how this feature can be used in the section “Returning a Response Message” later in this chapter. Lastly, we have created the absolute URI of the created resource through the Url.Link helper method and send it inside the response as the location header value. The Url.Link method accepts the route name and the route values as a parameter to construct the proper URI.

Figure 9-3 shows a sample POST request made against the HTTP service.

9781430247258_Fig09-03.jpg

Figure 9-3. POST request against /api/cars

As the raw request pane in Figure 9-3 shows, the car object is sent in JSON format, and the content type header is set to application/json. The proper formatter binds the action method parameter accordingly on the basis of the request headers and request message body. Make a GET request to retrieve all cars, and you’ll see that the new car object just added is also inside the list (Figure 9-4).

9781430247258_Fig09-04.jpg

Figure 9-4. GET request against /api/cars after the POST request

Let’s now enable the client to edit a car record on the server side. Since the most convenient HTTP method for modifications is the PUT method, we will name the controller action PutCar. Also, don’t forget that we have an optional route parameter named id, which we have omitted so far. We’ll take advantage of this parameter for an action to determine which record is requested for modification.

Listing 9-10 shows the implementation of the PutCar action method.

Listing 9-10.  CarsController with a Put Action

public class CarsController : ApiController {

    private readonly CarsContext _carsCtx = new CarsContext();

    //Lines omitted for brevity

    public Car PutCar(int id, Car car) {

        car.Id = id;

        if (!_carsCtx.TryUpdate(car)) {

            var response =
                Request.CreateResponse(HttpStatusCode.NotFound);
                
            throw new HttpResponseException(response);
        }

        return car;
    }
}

First, let’s try to update the record we got from the request. If the update is a success, then we’ll return the updated record. If the update fails, we’ll create a new instance of an HttpResponseMessage and throw an HttpResponseException that includes the HttpResponseMessage just created.

Let’s make a PUT request against /api/cars/1 and try to update the car object whose Id is 9, which is the entry we just created (see Figure 9-5).

9781430247258_Fig09-05.jpg

Figure 9-5. PUT request against /api/cars/1

As expected, we get a “200 OK” response, along with the updated car record in JSON format. Last of all, the DeleteCar action method is identical to the PutCar action method, as Listing 9-11 shows.

Listing 9-11.  CarsController with a Delete Action Method

public class CarsController : ApiController {

    private readonly CarsContext _carsCtx = new CarsContext();
    
    //Lines omitted for brevity

    public HttpResponseMessage DeleteCar(int id) {

        if (!_carsCtx.TryRemove(id)) {

            var response =
                Request.CreateResponse(HttpStatusCode.NotFound);
                
            throw new HttpResponseException(response);
        }

        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

Inside the DeleteCar action method, we are trying to delete the record, first of all, on the basis of the id parameter we got. If the car doesn’t exist, the delete operation will fail. So, we do as we did in the PutCar method and get a response containing the “404 Not Found” status code. If the record requested for deletion does exist, we fire the DELETE operation and return a “200 OK” response, which indicates that the operation has been successful.

Figure 9-6 shows the details of the DELETE request against /api/cars/1.

9781430247258_Fig09-06.jpg

Figure 9-6. DELETE request against /api/cars/1

So far, controller action methods have really helped us separate the implementations of different operations, and we were able to set up the basics quickly. You may notice that we don’t have any multiple action methods that serve for the same HTTP verb. For example, what if we need a way to get a single car record based on its id? Do we need to create another controller for this? Absolutely not. But in order to understand how this works, we’ll have to step back and take a good look at the action selection logic.

Action Selection

The appropriate action inside a given controller is selected on the basis of the registered action selector service. This action selector service needs to implement the IHttpActionSelector interface, which has the signature shown in Listing 9-12.

Listing 9-12.  IHttpActionSelector Interface

public interface IHttpActionSelector {

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

The SelectAction method of the IHttpActionSelector interface will be invoked to select the proper action. The responsibilities of the SelectAction method are as follows:

  • finding the proper action method according to the HttpControllerContext object passed in as a parameter;
  • throwing an HttpResponseException that holds the status code 404 if no proper action method is found;
  • populating an instance of HttpActionDescriptor on the basis of the selected action method and the HttpControllerContext object passed in as a parameter and returning it.

The action selector service is registered through the ServicesContainer, which was explained in the “Configuration” section in Chapter 1. By default, ApiControllerActionSelector is registered as an action selector. It performs the action selection unless you replace it with your own implementation.

To explain its working roughly, the ApiControllerActionSelector does everything listed above as the core responsibilities of the action selection service. However, the most important point here is the logic behind the action selection rather than its responsibilities. This logic in ApiControllerActionSelector involves routing extensively. There are two key things that determine how the proper action method is selected. One of them is based on a route parameter named action and the HTTP method the request came through. The other one is based solely on the HTTP method the request came through. We decided to name them Action-Based Routing and HTTP Method–Based Routing for ease of reference. In the next two subsections we will look into both routings separately.

HTTP Method–Based Routing

HTTP method–based routing looks for the method names inside the controller and filters out the ones that have the exact name of the HTTP method or the prefix that equals the HTTP method name. This comparison is case insensitive.

Since we’ve been working with this approach in this section so far, we are familiar with it, but our car gallery API application has a problem we haven’t found a solution for. We need to find two methods that will process the GET requests such that one of them will return a single car value based on the car’s id. The ApiControllerActionSelector has a way of selecting the actions on the basis of their parameters. For example, if we have another Get method inside the CarsController, one that accepts a parameter named id, and the request contains either a route or a query string value named id, this Get method will be selected as the appropriate action method and invoked. Now, let’s see this in action. Listing 9-13 shows the implementation of this second Get method.

Listing 9-13.  CarsController with a Second Get Action

public class CarsController : ApiController {

    private readonly CarsContext _carsCtx =
        new CarsContext();
    
    //Lines omitted for brevity

    public Car GetCar(int id) {

        var carTuple = _carsCtx.GetSingle(id);

        if (!carTuple.Item1) {

            var response =
                Request.CreateResponse(HttpStatusCode.NotFound);
                
            throw new HttpResponseException(response);
        }

        return carTuple.Item2;
    }
}

When we send a request against /api/cars/1 (Figure 9-7), we’ll be invoking the action method shown in Listing 9-13.

9781430247258_Fig09-07.jpg

Figure 9-7. GET request against /api/cars/1

Also, we can get the same result by sending a request against /api/cars?id=1 (Figure 9-8).

9781430247258_Fig09-08.jpg

Figure 9-8. GET request against /api/cars?id=1

image Note  It’s possible to use complex types as action parameters for GET requests and bind the route and query string values by marking the complex type parameters with FromUriAttribute. We explain this in Chapter 12. However, the default action selection logic only considers simple types (String, DateTime, Decimal, Guid, DateTimeOffset, TimeSpan), .NET Framework primitive types (Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, Single) and underlying simple types for the Nullable value types such as Nullable<System.Int32>. For example, if you have GetCars(Foo foo) and GetCars(Bar bar) methods in your controller, you will get the ambiguous action error as the complex types are completely ignored by the ApiControllerActionSelector. However, WebAPIDoodle NuGet package contains a custom action selector which we can use to support complex types for action selection. You may find more information about this from http://www.tugberkugurlu.com/archive/complex-type-action-parameters-with-complextypeawareactionselector-in-asp-net-web-api-part-1 and http://www.tugberkugurlu.com/archive/complex-type-action-parameters-with-complextypeawareactionselector-in-asp-net-web-api-part-2.

Until this point, we’ve been telling you that the action method name needs to have the exact name of the HTTP method or the prefix that is the same as the HTTP method name. This is one way to proceed with the HTTP method–based routing. The other way is to provide an action HTTP method provider attribute for the action method.

HTTP Method Providers

An HTTP method provider is a .NET attribute that implements the IActionHttpMethodProvider interface and provides the supported HTTP methods for the controller action it is applied to. The IActionHttpMethodProvider interface is an internal interface. The seven available HTTP method providers are as follows:

  • HttpGetAttribute
  • HttpPostAttribute
  • HttpPutAttribute
  • HttpDeleteAttribute
  • HttpHeadAttribute
  • HttpPatchAttribute
  • HttpOptionsAttribute

If a controller action method has one of these attributes applied, that action method will process the requests coming through the HTTP method in question. Listing 9-14 shows an example for the usage of HTTP method providers.

Listing 9-14.  IActionHttpMethodProvider Interface

public class CarsController : ApiController {

    [HttpGet]
    public string[] Cars() {

        return new string[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

As you can see in the sample in Listing 9-14, the action method does not follow the naming convention described earlier, but it has the HttpGetAttribute applied. So when you make a request against /api/cars, you should get back the list of cars (Figure 9-9).

9781430247258_Fig09-09.jpg

Figure 9-9. GET request against /api/cars

Action-Based Routing

Besides the HTTP method–based routing, there is also another routing feature that markedly affects the default action selection logic. If there is a route parameter named action for the chosen route, this parameter will look up the proper action. When using action-based routing, the HTTP method provider attribute needs to be set explicitly. Otherwise, the action method will never be selected.

Listing 9-15 shows a sample route definition that contains a parameter named action.

Listing 9-15.  A Route for So-Called Action-Based Routing

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    var routes = config.Routes;

    routes.MapHttpRoute(
        "DefaultHttpRoute",
        "api/{controller}/{action}/{id}",
        new { id = RouteParameter.Optional }
    );
}

There is also the CarsController class, which contains an action method named List (Listing 9-16).

Listing 9-16.  CarsController with an Action Named List

public class CarsController : ApiController {

    [HttpGet]
    public string[] List() {

        return new string[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

When you make a request against /api/cars/list, you should get the cars list, as expected (Figure 9-10).

9781430247258_Fig09-10.jpg

Figure 9-10. GET request against /api/cars/list

Action Method Selectors and NonActionAttribute

An action method selector is a .NET attribute that implements the IActionMethodSelector interface and allows interference in the action method selection process. The IActionMethodSelector is an internal interface. There is only one implementation of this interface: the NonActionAttribute.

image Note  If you replace the default action selector, which is the ApiControllerActionSelector, the action method selectors won’t work unless you implement them manually. The ApiControllerActionSelector executes the defined action method selectors.

When you have a defined public method in your Web API controller and don’t want it treated as a controller action method, you can apply the NonActionAttribute to that method. It won’t then be available as an action method.

Listing 9-17 shows an example for this feature.

Listing 9-17.  NonActionAttribute Usage Sample

public class CarsController : ApiController {

    public string[] Get() {

        return GetCars();
    }

    [NonAction]
    public string[] GetCars() {

        return new string[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

We are using the default route for this example. If we make a GET request against /api/cars and don’t use the NonActionAttribute for the GetCars method, we should get an HTTP 500 response, indicating that multiple actions were found that match the request. If the NonActionAttribute is applied to the GetCars method, the GetCars method will not be treated as an action method anymore.

Return Values

We can use anything as a return value of a Web API controller action. No matter what the return value type of the action method is, the return value will be converted into an HttpResponseMessage object.

Now let’s go through the possible types of return values one by one.

Returning an Object

The most common occurrence is to return an object as a result. When an object instance is returned, the return value will be processed by the proper formatter according to the content negotiation process. Then the processed return value will be sent to the client, along with the response message. All these operations are done out of the box.

For instance, in nearly all of our examples so far, we’ve been returning a string[] object. We’ve seen that the response message carries the content in the proper format. Let’s look at the example in Listing 9-18.

Listing 9-18.  Returning string[] Object Inside an Action Method

public class CarsController : ApiController {

    public string[] Get() {

        return new string[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

When we make a GET request against /api/cars, we’ll get the response message, along with the cars list in the proper format (Figure 9-11). The format differs on the basis of the request, the registered formatters, and the registered content negotiation service. (Chapter 12 is entirely dedicated to formatters and content negotiation. We’ll see there how it matters.)

9781430247258_Fig09-11.jpg

Figure 9-11. GET request against /api/cars

Except for the message body, we don’t at the moment have any control over the returned response message inside the controller action, but we can solve this problem by explicitly creating and returning an HttpResponseMessage instance. Let’s talk about that next.

image Note  Actually, you always have control over the response message when you are hosting your application on ASP.NET. The Properties collection on the HttpRequestMessage instance will always carry the current System.Web.HttpContext as HttpContextBase type under the key "MS_HttpContext"; the modifications you make in the response message here will be directly reflected in the response message sent over the wire. However, this is not the recommended way to alter the response message. The best practice here is to write an application in an agnostic way. This approach doesn’t follow that pattern. The right way to alter the response message (headers, status code, etc.) inside a controller action method is to return an instance of the HttpResponseMessage class. We’ll give some examples in the next section.

Returning a Response Message

When returning an instance of HttpResponseMessage, you have explicit control over the response message to be sent to the client. We can define the response headers and response status code in the case of HttpResponseMessage. Listing 9-19 shows an example of this procedure, with an action method for an HTTP DELETE request.

Listing 9-19.  DeleteCar Controller Action That Returns an HttpResponseMessage Instance

public class CarsController : ApiController {

    public HttpResponseMessage DeleteCar(int id) {

        //Check here if the resource exists
        if (id != 1) {

            return new HttpResponseMessage(HttpStatusCode.NotFound);
        }

        //Delete the car object here

        var response = new HttpResponseMessage(HttpStatusCode.OK);
        return response;
    }
}

As usual with an HTTP DELETE request, the controller action first checks whether the resource exists according to the passed-in id parameter. If the resource exists, it deletes the associated object and returns a “200 OK” response (Figure 9-12). We’re able to set the status code here because we’re returning an HttpResponseMessage instance. We might go farther and alter the response headers as well if we want.

9781430247258_Fig09-12.jpg

Figure 9-12. DELETE request against /api/cars/1

However, we might need to send a properly formatted object inside the response message body and alter the response message at the same time. As we learned in Chapter 4, HttpResponseMessage has a property named Content, which is a type of the abstract HttpContent class. We can pass the object we want here. Listing 9-20 shows an example for an action method that handles HTTP GET requests to return a list of cars.

Listing 9-20.  PostCar Controller Action That Returns an HttpResponseMessage Instance with an Object

public class CarsController : ApiController {

    public HttpResponseMessage GetCars() {

        var cars = new string[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };

        HttpResponseMessage response =
            Request.CreateResponse<string[]>(HttpStatusCode.OK, cars);
        response.Headers.Add("X-Foo", "Bar");
        return response;
    }
}

What we’re returning here is an instance of HttpResponseMessage. We have a list of cars that we want to return as well. Also, we want to be able to handle the content negotiation automatically. All of our requirements can be fulfilled by the CreateResponse extension method for HttpRequestMessage class. Finally, we added a custom header to demonstrate that we have full control over the response.

When we make a GET request against /api/cars, we should get back a list of cars in a proper format and the custom response header we’ve set, too (Figure 9-13).

9781430247258_Fig09-13.jpg

Figure 9-13. GET request against /api/cars

Also, notice that we want the response message in XML format, and the server gives us what we want.

Throwing HTTP Exceptions (HttpResponseException)

When an exception is thrown inside an action, the exception filters will be invoked to handle it (more about this in Chapter 11). If there is no exception filter or an exception filter didn’t handle the exception, eventually the HttpControllerDispatcher will handle it and send back a proper HttpResponseMessage instance.

However, if the exception is a type of HttpResponseException, the exception will be handled by the ApiControllerActionInvoker immediately. The exception filters won’t be invoked. This gives you the ability to stop processing immediately and return with a proper HTTP status code. Also, if you want to terminate the request inside a controller action and if the return expression type is not HttpResponseMessage, you can throw HttpResponseException to stop the processing of the action method.

Listing 9-21 is an example of throwing HttpResponseException inside a controller action.

Listing 9-21.  A Sample Controller Action That Throws HttpResponseException

public class CarsController : ApiController {

    public string[] GetCars() {

        //stop processing and throw the HttpResponseException
        var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
        throw new HttpResponseException(response);
    }
}

The return type of the action method is string[]. Note that we are still able to alter the response message. When we send a GET request against /api/cars, we will get a “400 Bad Request” response (Figure 9-14).

9781430247258_Fig09-14.jpg

Figure 9-14. GET request against /api/cars

image Note  There is also another special case, one where we can return a Task or Task<T> object to handle the operation inside the controller action asynchronously. Chapter 16 covers this feature in depth.

Returning Error Responses

The ASP.NET Web API framework has a built-in mechanism to handle errors efficiently and send user-friendly error messages to the consumer. This built-in error-handling mechanism also works well with validation and several other components of the framework, as will be seen in Chapter 13.

When an unhandled exception occurs at the controller level and the exception handler doesn’t set any specific response message, the exception details are sent inside the message body with the “500 Internal Server Error” status code. Assuming the presence of the controller shown in Listing 9-22, the GetCars method will throw a DivideByZeroException.

Listing 9-22.  The CarsController Class

public class CarsController : ApiController {

    public string[] GetCars() {

        int left = 10,
            right = 0;

        var result = left / right;

        return new[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

When a request is sent to /api/cars, the response message body will carry the exception details (see Figure 9-15).

9781430247258_Fig09-15.jpg

Figure 9-15. A GET request against /api/cars with a “500 Internal Server Error” response and the response message body with the exception details

If the request is sent by indicating acceptance of the XML format, the response body will be in XML format. Note, too, that this sample is being run locally; that is why you see the exception details. As Chapter 1’s “Configuration” section shows, the IncludeErrorDetailPolicy is set by default to IncludeErrorDetailPolicy.LocalOnly, and the framework honors that setting here. Change this setting to Never, as shown in Listing 9-23, and you won’t see the exception details (see Figure 9-16).

Listing 9-23.  IncludeErrorDetailPolicy Set to Never

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;

    //Lines omitted for brevity...

    config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Never;

}

9781430247258_Fig09-16.jpg

Figure 9-16. A GET request against /api/cars with a “500 Internal Server Error” response and the response message body with limited exception details

What you see here is the HttpError class in play. When an exception occurs inside a controller action, an instance of an HttpError is serialized to the wire by the proper formatter. The HttpError message is just a Dictionary<string, object> object that provides some helper methods for creating errors that contain error messages, exceptions, or invalid model states.

The HttpError class has four constructors, which accept parameters to create over the existing error stacks a new HttpError message, such as an Exception instance of a ModelStateDictionary instance. Listing 9-24 shows the signature of the HttpError class.

Listing 9-24.  The Signature of the HttpError Class

[XmlRoot("Error")]
public sealed class HttpError : Dictionary<string, object>, IXmlSerializable {

    public HttpError();
    public HttpError(string message);
    public HttpError(Exception exception, bool includeErrorDetail);
    public HttpError(ModelStateDictionary modelState, bool includeErrorDetail);

    public string Message { get; set; }
}

If the framework didn’t provide such a feature to return an exception message by default, the same result could be achieved via the process shown in Listing 9-25.

Listing 9-25.  GetCars Action Method

public string[] GetCars() {

    try {

        int left = 10,
            right = 0;

        var result = left / right;

    }
    catch (DivideByZeroException ex) {

        var faultedResponse = Request.CreateResponse(
            HttpStatusCode.InternalServerError,
            new HttpError(ex, includeErrorDetail: true));

        throw new HttpResponseException(faultedResponse);
    }

    return new[] {
        "Car 1",
        "Car 2",
        "Car 3"
    };
}

A request now sent to /api/cars will generate the same result, shown in Figure 9-17, already seen in Figure 9-15.

9781430247258_Fig09-17.jpg

Figure 9-17. A GET request against /api/cars with a “500 Internal Server Error” response and the response message body with the exception details

Actually, the solution Listing 9-25 provides doesn’t behave quite the same as the built-in default solution, because the includeErrorDetail parameter is directly set to true. This wouldn’t honor the IncludeErrorDetail configuration settings. Of course, the value of the IncludeErrorDetailPolicy property could be retrieved from the controller’s Configuration property, but there would then be a need to determine the cases where the error details had to be included and where they didn’t.

To be able to honor the IncludeErrorDetailPolicy settings and make it easy to return error responses, there is an extension method for HttpRequestMessage. Called CreateErrorResponse, it has several overloads, as Listing 9-26 shows.

Listing 9-26.  The CreateErrorResponse Extension Method for HttpRequestMessage and Its Overloads

public static class HttpRequestMessageExtensions {

    public static HttpResponseMessage CreateErrorResponse(
        this HttpRequestMessage request,
        HttpStatusCode statusCode, Exception exception);

    public static HttpResponseMessage CreateErrorResponse(
        this HttpRequestMessage request,
        HttpStatusCode statusCode, HttpError error);

    public static HttpResponseMessage CreateErrorResponse(
        this HttpRequestMessage request,
        HttpStatusCode statusCode, ModelStateDictionary modelState);

    public static HttpResponseMessage CreateErrorResponse(
        this HttpRequestMessage request,
        HttpStatusCode statusCode, string message);

    public static HttpResponseMessage CreateErrorResponse(
        this HttpRequestMessage request,
        HttpStatusCode statusCode, string message, Exception exception);
        
    //Other extension methods are omitted for brevity . . .
}

All overloads of the CreateErrorResponse extension method return an HttpResponseMessage instance. Just like the CreateResponse extension method, this extension method also runs the content negotiation behind the scenes to return the response in a proper format. The CreateErrorResponse can be used to create error responses anywhere inside the ASP.NET Web API pipeline, including message handlers, filters, and controllers.

Listing 9-27 shows the GetCars implementation by using the CreateErrorResponse method.

Listing 9-27.  GetCars Action Method

public string[] GetCars() {

    try {

        int left = 10,
            right = 0;

        var result = left / right;

    }
    catch (DivideByZeroException ex) {

        var faultedResponse = Request.CreateErrorResponse(
            HttpStatusCode.InternalServerError, ex);

        throw new HttpResponseException(faultedResponse);
    }

    return new[] {
        "Car 1",
        "Car 2",
        "Car 3"
    };
}

A request now sent to /api/cars will again generate the same result as the one in Figure 9-15 (also shown in Figure 9-18) and will also honor the IncludeErrorDetailPolicy configuration settings.

9781430247258_Fig09-18.jpg

Figure 9-18. A GET request against /api/cars with a “500 Internal Server Error” response and the response message body with the exception details

The HttpError class can also be used for arbitrary error messages. As Listing 9-28 shows, one of the overloads of the CreateErrorResponse extension method accepts the HttpError instance. It’s also possible to construct a new HttpError instance with custom error details and pass it into the CreateErrorResponse method.

The sample shown in Listing 9-28 demonstrates this usage. The GetCar controller action method accepts an integer for the id parameter, but we only allow even numbers. So if the number is odd, we will construct a new HttpError message, add the message for the error, and return a new HttpResponseMessage instance through the CreateErrorResponse extension method of the HttpRequestMessage.

Listing 9-28.  GetCar Action Method

public HttpResponseMessage GetCar(int id) {

    if ((id % 2) != 0) {

        var httpError = new HttpError();
        httpError.Add("id",
            "Only "even numbers" are accepted as id.");

        return Request.CreateErrorResponse(
            HttpStatusCode.InternalServerError,
            httpError);
    }

    return Request.CreateResponse(
        HttpStatusCode.OK,
        string.Format("Car {0}", id));
}

When a request is sent against /api/cars/1, a “500 Internal Server Error” response is returned, because the id parameter supplied is not an even number (see Figure 9-19).

9781430247258_Fig09-19.jpg

Figure 9-19. A GET request against /api/cars/1 with a “500 Internal Server Error” response and the response message body with the exception details

What will mostly be leveraged is the CreateErrorResponse extension method along with the validation. Chapter 13 is dedicated solely to the validation topic, and there you’ll see samples showing how to efficiently use the CreateErrorResponse extension method to provide better validation error messages.

Per-Controller-Type Configuration

In some cases, we will want to have specific formatters or internal services for a controller type. For example, assume that we have two controllers, CarsController and VehiclesController, and for CarsController we want JSON to be the only supported return output and input media type. Also, as we want strong content negotiation, we explicitly require the client to specify the media type. In this and similar scenarios, the per-controller-type configuration feature of ASP.NET Web API comes in handy.

Per-controller-type configuration allows controllers to have a shadow copy of the global configuration, which can be modified as needed. The framework knows about per-controller-type configurations by examining the controller attributes to find an attribute that implements the System.Web.Http.Controllers IControllerConfiguration. The signature of the IControllerConfiguration interface is shown in Listing 9-29.

Listing 9-29.  The IControllerConfiguration interface

public interface IControllerConfiguration {

    void Initialize(
        HttpControllerSettings controllerSettings,
        HttpControllerDescriptor controllerDescriptor);
}

The IControllerConfiguration has only one method, Initialize. Initialize accepts two parameters of the types HttpControllerSettings and HttpControllerDescriptor.

HttpControllerSettings provides the overrideable pieces of the configuration. The entire HttpConfiguration object is not passed here directly, because certain parts, including message handlers and routes, obviously cannot be customized. In terms of the processing pipeline, it would be too late to customize them per-controller type. Listing 9-30 shows the signature of the HttpControllerSettings class.

Listing 9-30.  The HttpControllerSettings Class

public sealed class HttpControllerSettings {
    
    public MediaTypeFormatterCollection Formatters { get; }
    public ParameterBindingRulesCollection ParameterBindingRules { get; }
    public ServicesContainer Services { get; }
}

As can be predicted by looking at the HttpControllerSettings class, the Initialize method of the IControllerConfiguration implementation can change the internal services, formatters, and binding rules. After the Initialize method is run, the framework looks at the HttpControllerSettings instance and then creates a new shadow HttpConfiguration object and applies the changes. Parts that are unchanged will stay the same as the global configuration. Implementation of the necessary parts will make our CarsController and VehiclesController example (explained at the beginning of this section) work.

Listing 9-31 shows the OnlyJsonConfigAttribute class, which is the implementation of the IControllerConfiguration interface.

Listing 9-31.  The OnlyJsonConfigAttribute Class, Which Implements the IControllerConfiguration Interface

public class OnlyJsonConfigAttribute :
    Attribute, IControllerConfiguration {

    public void Initialize(
        HttpControllerSettings controllerSettings,
        HttpControllerDescriptor controllerDescriptor) {

        var jqueryFormatter = controllerSettings.Formatters
            .FirstOrDefault(x =>
                x.GetType() == typeof(
                    JQueryMvcFormUrlEncodedFormatter));
            
        controllerSettings.Formatters.Remove(
            controllerSettings.Formatters.XmlFormatter);
            
        controllerSettings.Formatters.Remove(
            controllerSettings
            .Formatters
            .FormUrlEncodedFormatter);
            
        controllerSettings.Formatters.Remove(
            jqueryFormatter);

        controllerSettings.Services.Replace(
            typeof(IContentNegotiator),
            new DefaultContentNegotiator(
                excludeMatchOnTypeOnly: true));
    }
}

Here we remove all formatters except the JsonMediaTypeFormatter instance; also, we replace the IContentNegotiator service with a new DefaultContentNegotiator. To remove the flexibility from the content negotiation, the excludeMatchOnTypeOnly parameter needs to be passed as true. Also, note that the Initialize method will run only once when the controller is initialized first. The result produced here will be cached during the application’s life cycle.

As for the controllers, Listing 9-32 shows the CarsController, which applies the OnlyJsonConfig attribute; Listing 9-33 shows the VehiclesController; it uses the global configuration and has no special configuration requirements.

Listing 9-32.  The CarsController, Which Has Its Own Configuration

[OnlyJsonConfig]
public class CarsController : ApiController {

    public string[] GetCars() {

        var foo = this.ControllerContext;

        return new[] {
            "Car 1",
            "Car 2",
            "Car 3"
        };
    }
}

Listing 9-33.  The VehiclesController, Which Uses the Global Configuration

public class VehiclesController : ApiController {

    public string[] GetVehicles() {

        return new[] {
            "Vehicle 1",
            "Vehicle 2",
            "Vehicle 3"
        };
    }
}

Furthermore, note that except for the route registration, there are no special requirements for the global configuration (see Listing 9-34).

Listing 9-34.  The Route Registration Configuration

protected void Application_Start(object sender, EventArgs e) {

    var config = GlobalConfiguration.Configuration;
    var routes = config.Routes;

    routes.MapHttpRoute(
        "DefaultHttpRoute",
        "api/{controller}"
    );
}

As you will see, when a GET request is sent to /api/vehicles without Accept headers, the response message will be in the JSON format because JsonMediaTypeFormatter is the first formatter inside the collection. The globally registered DefaultContentNegotiator works this way by default (see Figure 9-20).

9781430247258_Fig09-20.jpg

Figure 9-20. GET request against /api/vehicles with no Accept header

When a GET request is sent to /api/vehicles with the application/xml Accept header, the response comes in the expected XML format (see Figure 9-21).

9781430247258_Fig09-21.jpg

Figure 9-21. GET request against /api/vehicles with an application/xml Accept header

That is, VehiclesController works with the global configuration we have. Figure 9-22 shows a GET request sent to /api/cars with no Accept header. Let’s see what we get.

9781430247258_Fig09-22.jpg

Figure 9-22. GET request against /api/cars with no Accept header

As intended, the response is “406 Not Acceptable”, because the DefaultContentNegotiator was configured accordingly. If a GET request is sent to the same URI with an application/xml Accept header, the response will again be “406 Not Acceptable”, as there is no formatter that can write to XML (see Figure 9-23).

9781430247258_Fig09-23.jpg

Figure 9-23. GET request against /api/cars with an application/xml Accept header

But when a GET request is sent with an application/json Accept header, the response is the healthy and expected “200 OK” (see Figure 9-24).

9781430247258_Fig09-24.jpg

Figure 9-24. GET request against /api/cars with an application/json Accept header

The per-controller-type configuration feature provides really great flexibility. Multiple per-controller-type configurations and one global configuration can coexist within the context of a single application.

Summary

Controllers are among the main components of the ASP.NET Web API framework. They are supposed to be the place where the request message is processed and the main response message is generated. You saw, as we walked through this chapter, that the base controller type ApiController, which is provided out of the box, provides a nice programming model, and it covers nearly all of the different situations that you’ll need to know to be able to handle action methods, filters, and the like.

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

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