CHAPTER 23

image

Filters Part I

I touched on filters in Chapter 20 so that I could describe the dispatch process that the ApiController class implements. In this chapter, I describe how filters work in depth and demonstrate how they can be used to add cross-cutting concerns to a Web API application. Table 23-1 summarizes this chapter.

Table 23-1. Chapter Summary

Problem

Solution

Listing

Add logic to the dispatch pipeline.

Define an action filter by defining an attribute that implements the IActionFilter interface. Apply the attribute to the action method or controller where the logic is required.

1–2

Create an action method without having to manage continuation functions.

Derive the class from the ActionFilterAttribute class.

3

Terminate the request handling process in a filter.

Create a short-circuiting filter that generates an HttpResponseMessage object rather than invoking the continuation function. If using the attribute base classes, then set the HttpActionContext.Response property.

4–6

List the filters that have been applied to an action method.

Enumerate the filter pipeline.

7, 8, 14

Apply a filter to all of the action methods in an application.

Define a global filter.

9, 10

Associate a user identity with a request.

Create an authentication filter.

11–13

Preparing the Example Project

I am going to continue using the Dispatch project I created in Chapter 19. No changes are required for this chapter.

Understanding Filters

Filters inject extra logic into the ApiController dispatch process and provide a simple and elegant mechanism to implement features that operate across multiple components in the MVC pattern, known as cross-cutting concerns. The most common uses for filters are applying authentication and authorization, handling errors, and measuring performance.

Filters are attributes that implement the IFilter interface, which is defined in the System.Web.Http.Filters namespace. Here is the definition of the interface:

namespace System.Web.Http.Filters {
    public interface IFilter {
        bool AllowMultiple { get; }
    }
}

The only member defined by the interface is the AllowMultiple property, which specifies whether more than one instance of a specific filter can be used. It is unusual to work directly with the IFilter interface because there is a set of interfaces, all of which are derived from IFilter, that define different kinds of filter. Table 23-2 lists these interfaces and describes how they are used.

Table 23-2. 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. See Chapter 24 for details.

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. See Chapter 24 for details.

Override

IOverrideFilter

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

Image Note  Filters should not be used to perform tasks that belong in an action method, which essentially means not creating filters that process a request in order to interact with the repository and generate results. Using filters to replace or supplement action methods makes it harder to isolate specific functions for unit testing.

There are corresponding abstract attribute classes that implement each of the interfaces listed in the table, and they are the simplest way to get started with filters, although I will show you how to work directly from the filter interfaces as well. I describe action and authentication filters in this chapter and the other types in Chapter 24. Table 23-3 puts filters in context.

Table 23-3. Putting Filters in Context

Question

Answer

What are they?

Filters allow extra logic to be inserted into the dispatch process before and after the execution of the action method. Filters can also short-circuit the dispatch process to prevent action methods—and other filters—from being executed. See the “Creating a Short-Circuiting Action Filter” section for details.

When should you use them?

Filters should be used only to contain logic that doesn’t belong in the controller or data model as described by the MVC pattern in Chapter 4.

What do you need to know?

Filters can be applied as attributes to action methods and controllers or applied globally through the WebApiConfig.cs file.

Working with Action Filters

Action filters allow extra logic to be executed before and after an action method has been executed. This means you have the opportunity to change the HttpRequestMessage and HttpResponseMessage objects or perform tasks that span the action method execution, such as timing the dispatch process, which is the standard example for action filters (although I’ll show you some other uses later in the chapter). Action filters are defined by the IActionFilter interface, which is defined as follows:

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

namespace System.Web.Http.Filters {

    public interface IActionFilter : IFilter {

        Task<HttpResponseMessage> ExecuteActionFilterAsync(
            HttpActionContext actionContext,
            CancellationToken cancellationToken,
            Func<Task<HttpResponseMessage>> continuation);
    }
}

In the sections that follow, I’ll show you how action filters work and the different ways to create action filters. In Figure 23-1, I have updated the dispatch process diagram to show the relationship between an action filter and the action method. (I have shown only part of the diagram in the figure, but I’ll show the complete view of the process later in the chapter.)

9781484200865_Fig23-01.jpg

Figure 23-1. Action filters and action methods in the ApiController dispatch process

The important point to note about the diagram is that the action filter is invoked before the action method is invoked and afterward, when HttpResponseMessage has been created and is making its way back along the chain of components. Table 23-4 puts action filters into context.

Table 23-4. Putting Action Filters in Context

Question

Answer

What are they?

Action filters provide a mechanism to modify the HttpRequestMessage and HttpResponseMessage objects before and after the action method is executed.

When should you use them?

Action filters should be used with caution and only to perform tasks that do not contain business logic, operate on the data model, or perform authentication or authorization (which are handled by other filter types).

What do you need to know?

In Web API, action filters combine the functionality of action and result filters in the MVC framework.

Creating an Action Filter by Implementing IActionFilter

As I explained in the previous section, the IActionFilter interface defines one method: ExecuteActionFilterAsync. This method looks more complex than it really is because the goal of the interface is to let you define work to be performed before and after the action method is invoked. The best way to explain how this works is with an example, and Listing 23-1 shows the contents of the TimeAttribute.cs file, which I added to the Infrastructure folder.

Listing 23-1. The Contents of the TimeAttribute.cs File

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

namespace Dispatch.Infrastructure {
    public class TimeAttribute : Attribute, IActionFilter {

        public async Task<HttpResponseMessage> ExecuteActionFilterAsync(
                HttpActionContext actionContext,
                CancellationToken cancellationToken,
                Func<Task<HttpResponseMessage>> continuation) {

            Stopwatch sw = Stopwatch.StartNew();

            HttpResponseMessage result = await continuation();

            long elapsedTicks = sw.ElapsedTicks;
            result.Headers.Add("Elapsed-Time", elapsedTicks.ToString());
            System.Diagnostics.Debug.WriteLine("Elapsed time: {0} ticks, {1} {2}",
                elapsedTicks, actionContext.Request.Method,
                actionContext.Request.RequestUri);
            return result;
        }

        public bool AllowMultiple {
            get { return false; }
        }
    }
}

The TimeAttribute class is an action filter: it is derived from the Attribute class, and it implements the IActionFilter interface. This filter uses the StopWatch class to measure the amount of time taken to execute the action method. (This is a simplification of what is really being measured, as I explain in the “Understanding Filter Scope” section.)

Image Tip  The StopWatch class is a high-resolution timer that is useful for measuring small amounts of time, such as the invocation of a single method. The elapsedTicks property I read in Listing 23-1 returns the number of ticks since the timer was started, where a tick is the smallest duration that the StopWatch class can measure on the current system. The length of a tick will differ between systems, and the Frequency field tells you how many ticks there are per second on the current hardware. I am happy working with ticks in this chapter because my focus is on how filters work, but for more details of high-resolution timings, see http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx.

To perform my measurement, I need to take advantage of the opportunity to perform work before and after the action method is executed. Before the execution, I need to start a timer. After the execution, I need to read the value of the timer and report on the elapsed time, which I do by adding a header to the HttpResponseMessage object and by writing a message to the Visual Studio Ouput window.

The first thing you do when the ExecuteActionFilterAsync method is executed is to perform the work you want to do before the action. The filter in Listing 23-1 uses the StopWatch class to measure time, so the only work that I have to do is create and start a new instance of the timer, which I do in a single step like this:

...
Stopwatch sw = Stopwatch.StartNew();
...

Following the statements to be performed before the action method is invoked, you await the Task that the Func<Task<System.Net.Http.HttpResponseMessage>> parameter produces. This is a gnarly type: it is a function that, when invoked, returns a Task that yields an HttpResponse message. Or, to put another way, invoking the continuation parameter executes the action method, which the action filter invokes to get an HttpResponse message, like this:

...
HttpResponseMessage result = await continuation();
...

When the continuation Task has completed, the action filter can manipulate the response. For this action filter, that means adding a header to the HttpResponse message, as well as writing out a message to the Visual Studio Output window.

...
long elapsedMs = sw.ElapsedMilliseconds;
result.Headers.Add("Elapsed-Time", elapsedMs.ToString());
System.Diagnostics.Debug.WriteLine("Elapsed time: {0} ms, {1} {2}",
    elapsedMs, actionContext.Request.Method, actionContext.Request.RequestUri);
return result;
...

Image Tip  The result returned by the ExecuteFilterAsync method is a Task that will yield an HttpResponseMessage object when it completes. I used the async and await keywords in my method implementation, which means I am able to return an HttpResponseMessage object and rely on the .NET runtime to convert it into a Task<HttpResponseMessage>.

If you need more information about the request, such as details of the controller, the action method, or its parameters, then you can obtain it through the HtpActionContext object that is passed as a parameter to the IActonFilter.ExecuteFilterAsync method. The HttpActionContext class defines the properties shown in Table 23-5. In the action filter, I use the Request property to get the HttpRequestMessage object so that I can get the HTTP verb and URL from the request.

Table 23-5. The Properties Defined by the HttpActionContext ClassHttpActionContext Class

Name

Description

ActionArguments

Returns a Dictionary<string, object> that maps the names of the action method arguments to their types.

ActionDescriptor

Returns an HttpActionDescriptor object that describes the action method that is going to be invoked.

ControllerContext

Returns an HttpControllerContext object that describes the controller in which the action method is defined.

ModelState

Returns a ModelStateDictionary object used in the model validation process, which I describe in Chapter 18.

Request

Returns the HttpRequestMessage object that describes the current request.

RequestContext

Returns the HttpRequestContext object that provides supplementary information about the request.

Response

Returns the HttpResponseMessage object that will be used to product the response to the client. This is only set during a short-circuit of the dispatch process, as described in the “Creating a Short-Circuiting Action Filter” section.

Applying an Action Filter

Action filters are applied as attributes, either to individual action methods or to a controller class. Applying a filter to a controller is equivalent to applying it to each and every action method in the controller, and you can see how I have applied the Time attribute from the previous section to the Products controller in Listing 23-2. (You can also apply filters globally, in which case they are applied to all action methods in all controllers—see the “Understanding Filter Scope” section for details.)

Image Tip  I have applied the filter to a RESTful controller, but they work on any Web API controller that is derived from the ApiController class. Filters are not available when you create your own controllers directly from the IHttpController interface.

Listing 23-2. Applying a Filter to the ProductsController.cs File

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

namespace Dispatch.Controllers {

    [Time]
    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;
        }
    }
}

You can test the action filter by starting the application, navigating to the /Home/Index URL, and clicking each of the Get All, Get One, and Post buttons in turn. In addition to a header in the response, the action filter will write messages to the Visual Studio Output window, like this:

Elapsed time: 147 ticks, GET http://localhost:49412/api/products/
Elapsed time: 132 ticks, GET http://localhost:49412/api/products/2
Elapsed time: 89 ticks, POST http://localhost:49412/api/products/

You will see different durations displayed, based on how many ticks your hardware can measure each second and how fast requests can be processed.

Using the Convenience Action Filter Base Class

Not every developer is comfortable having the before and after statements defined in the same method. An alternative approach—and the most common way to create action filters—is to derive from the ActionFilterAttribute class, which implements the ExecuteActionFilterAsync to call separate before and after methods that allow you to separate your code statements. The ActionFilterAttribute class defines the methods described in Table 23-6.

Table 23-6. The Methods Defined by the ActionFilterAttribute Class

Name

Description

OnActionExecutingAsync

Invoked before the action method is executed

OnActionExecutedAsync

Invoked after the action method is executed

Image Note  There are two additional methods, OnActionExecuting and OnActionExecuted, that are invoked by the base implementation of the methods shown in the table. These methods allow you to write filter code without having to worry about using Task objects and the async and await keywords. There is no advantage in using these pseudo-synchronous methods, and I strongly recommend you avoid their use.

In Listing 23-3, you can see how I have updated the TimeAttribute class so that it is derived from the ActionFilterAttribute class. (This is similar to the filter I created in Chapter 22.)

Listing 23-3. Deriving from the ActionFilterAttribute Class in the TimeAttrribute.cs File

using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Dispatch.Infrastructure {
    public class TimeAttribute : ActionFilterAttribute {
        private static readonly string propKey =
            "Dispatch.Infrastructure.TimeAttribute.StopWatch";

        public override Task OnActionExecutingAsync(HttpActionContext actionContext,
                CancellationToken cancellationToken) {
            return Task.Factory.StartNew(() => {
                actionContext.Request.Properties.Add(propKey, Stopwatch.StartNew());
            });
        }

        public override Task OnActionExecutedAsync(HttpActionExecutedContext
                actionExecutedContext, CancellationToken cancellationToken) {

            return Task.Factory.StartNew(() => {
                if (actionExecutedContext.Request.Properties.ContainsKey(propKey)) {
                    Stopwatch sw =
                        ((Stopwatch)actionExecutedContext.Request.Properties[propKey]);
                    long elapsedTicks = sw.ElapsedTicks;
                    actionExecutedContext.Response.Headers.Add("Elapsed-Time",
                        elapsedTicks.ToString());
                    System.Diagnostics.Debug.WriteLine(
                        "Elapsed time: {0} ticks, {1} {2}", elapsedTicks,
                            actionExecutedContext.Request.Method,
                            actionExecutedContext.Request.RequestUri);
                }
            });
        }
    }
}

Deriving a filter from the ActionFilterAttribute class allows you to separate out the before and after code into separate methods, but it adds its own complexities.

Image Tip  Working directly with the IActionFilter interface produces code that I think is simpler and more elegant, but in projects that other developers will maintain, I use the ActionFilterAttribute class because the use of before and after methods is a lot more approachable than the use of the async and await keywords, which are still not widely embraced or understood in corporate development teams.

To improve performance, instances of filters classes are used to handle multiple requests and may be used to handle requests concurrently. That means you must avoid using instance variables as state data if you need to pass context information from the OnActionExecutingAsync method to the OnActionExecutedAsync method. In my example action filter, the context that I need is the StopWatch object, which is started in OnActionExecutingAsync and read in OnActionExecutedAsync.

You can use the HttpRequestMessage.Properties property to access a collection that is used to store ­request-specific state data. The HttpRequestMessage object persists throughout the dispatch process, which means you can use it store objects before the action method is executed, like this:

...
actionContext.Request.Properties.Add(propKey, Stopwatch.StartNew());
...

and then retrieve them after the action method has been executed, like this:

...
Stopwatch sw = ((Stopwatch)actionExecutedContext.Request.Properties[propKey]);
...

The state data is available to any other Web API component that handles the request. This means you need to ensure that the key that you use to store the state data is unlikely to be duplicated elsewhere. I base my state data key names on the class that stores them, like this:

...
private static readonly string propKey =
    "Dispatch.Infrastructure.TimeAttribute.StopWatch";
...

STATE DATA PROBLEMS

The HttpRequestMessage.Properties collection is useful because the Web API programming model doesn’t provide the state data features that are available in the legacy ASP.NET platform.

The HttpRequestMessage object isn’t the only class that defines a Properties collection. Similar collections are available throughout the Web API class hierarchy, including the HttpActionDescriptor, HttpControllerDescriptor, and HttpConfiguration classes.

Using these property collections is fine when the state data is stored and retrieved by the same object, as is the case with the action filter in Listing 23-3. The other use for these collections is to pass data from one component to another—something I recommend you avoid.

The problem is that state data contained in properties collections creates a tight coupling between the component that stores the data and the component that retrieves it. In this situation, you have to use both components in your application—and that makes it difficult to create custom implementations of individual interfaces without figuring out the private meaning of the state data and re-creating or replacing it.

I recommend you avoid using this kind of state data unless it is contained within a single class. In fact, I think that using the property collections to coordinate components is such a dangerous technique that I have omitted the Properties property from all of the classes I describe in this book.

The ActionFilterAttribute class provides the OnActionExecutedAsync method with context information through the HttpActionExecutedContext class. In addition to the context available through the HttpActionContext class, the HttpActionExecutedContext class provides details of any exception that was thrown by the continuation class. I explain how to handle exceptions using filters in Chapter 24 and more broadly in Chapter 25. Table 23-7 shows the properties defined by the HttpActionExecutedContext class.

Table 23-7. The Properties Defined by the HttpActionExecutedContext Class

Name

Description

ActionContext

This returns the HttpActionContext that ApiController passes to the ExecuteActionFilterAsync method.

Exception

This property is set to any exception that occurs in executing the continuation task.

Request

This returns the HttpRequestMessage object associated with the response.

Response

This returns the HttpResponseMessage object that has been generated by the continuation task.

In the TimeAttribute class, I use the HttpActionExecutedContext.Request property to get the HttpRequestMessage and HttpResponseMessage objects. The HttpRequestMessage object gives me access to the StopWatch object I stored as state data and the request URL and verb that I include in the message written to the Visual Studio Output window. The HttpResponseMessage object allows me to add a header to the response.

...
actionExecutedContext.Response.Headers.Add("Elapsed-Time", elapsedTicks.ToString());
System.Diagnostics.Debug.WriteLine("Elapsed time: {0} ticks, {1} {2}",
    elapsedTicks, actionExecutedContext.Request.Method,
        actionExecutedContext.Request.RequestUri);
...

Creating a Short-Circuiting Action Filter

Action filters don’t have to be passive observers of the dispatch process; they can also be active participants—although you must be careful not to use an action filter to perform work that should be defined in the action method. As a demonstration, I added the CounterAttribute.cs file to the Infrastructure folder and used it to define the action filer shown in Listing 23-4.

Listing 23-4. The Contents of the CounterAttribute.cs File

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

namespace Dispatch.Infrastructure {

    public class CounterAttribute : Attribute, IActionFilter {
        private static int counter = 0;
        private static int limit;

        public CounterAttribute(int requestLimit) {
            limit = requestLimit;
        }

        public Task<HttpResponseMessage> ExecuteActionFilterAsync(
                    HttpActionContext actionContext,
                    CancellationToken cancellationToken,
                    Func<Task<HttpResponseMessage>> continuation) {

                if (counter < limit) {
                    Debug.WriteLine("Request {0} of {1}", counter, limit);
                    counter++;
                    return continuation();
                } else {
                    HttpResponseMessage response = actionContext.Request.
                        CreateErrorResponse(HttpStatusCode.ServiceUnavailable,
                            "Limit Reached");
                    return Task.FromResult<HttpResponseMessage>(response);
                }
        }

        public bool AllowMultiple {
            get { return false; }
        }
    }
}

I have implemented this filter directly from the IActionFilter interface and defined a counter that specifies the maximum number of requests that are allowed, after which error messages are returned to the client. This isn’t something you would do in a real project, but it lets me demonstrate some important action filter techniques. (I demonstrate how to create the same effect when deriving from the ActionFilterAttribute class in the “Deriving the Filter from the ActionFilterAttribute Class” section.)

Image Caution  It can be tempting to patch up troublesome action methods by applying action filters. This is fine as a quick fix, but I recommend doing so sparingly and ensuring that you make the time to go back into the code and integrate the logic where it belongs: the action method or the model. Relying on action filters to perform work that belongs in the action method makes it harder to perform unit testing because you can no longer test just the action method—you must test the combined functionality of the filter and the action method together. It also makes the code harder for other developers to manage because understanding how a request is handled requires figuring out how the action method and the action filter interact.

In the ExecuteActionFilterAsync method, I check to see whether the current request exceeds the specified limit. If the limit has not been reached, then I invoke the continuation function to create the Task<HttpResponseMessage>, which I return as the method result. This is the normal request dispatch process, and it leads to the action method being invoked.

If the request has exceeded the limit, then I create an HttpResponseMessage object with the 503 (Service Unavailable) status code and return it as the result of the ExecuteActionFilterAsync method, without having invoked the continuation function. This is the short-circuit—the action generates the response for the request and, in doing so, prevents the action method from being invoked, as illustrated by Figure 23-2.

9781484200865_Fig23-02.jpg

Figure 23-2. Short-circuiting the dispatch process with an action filter

When the request limit has been reached, the action filter starts to short-circuit the dispatch process and generates the HttpResponseMessage directly.

Testing the Short-Circuiting Filter

To test the filter, I need to apply it to the controller class, as shown in Listing 23-5.

Listing 23-5. Applying a Filter to the ProductsController.cs File

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

namespace Dispatch.Controllers {

    [Time]
    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 },
            };

        [Counter(3)]
        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 applied the filter directly to the parameterless Get action method, rather than to the entire controller. To perform the test, start the application and use the browser to navigate to the /Home/Index URL. Click the Get All button four times, and you will see messages similar to these in the Visual Studio Output window:

Request 0 of 3
Elapsed time: 86870 ticks, GET http://localhost:49412/api/products/
Request 1 of 3
Elapsed time: 3196 ticks, GET http://localhost:49412/api/products/
Request 2 of 3
Elapsed time: 2220 ticks, GET http://localhost:49412/api/products/
Elapsed time: 7077 ticks, GET http://localhost:49412/api/products/

For the fourth and subsequent requests, the client will display the 503 (Service Unavailable) message, as illustrated by Figure 23-3.

9781484200865_Fig23-03.jpg

Figure 23-3. The effect of a short-circuiting action filter

Deriving the Filter from the ActionFilterAttribute Class

The technique for creating a short-circuiting filter that is derived from the ActionFilterAttribute class is slightly different because you are not responsible for executing the continuation function. Listing 23-6 shows how I revised the CounterAttribute class so that it is derived from ActionFilterAttribute.

Listing 23-6. Deriving from the ActionFilterAttribute Class in the CounterAttribute.cs File

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

namespace Dispatch.Infrastructure {

    public class CounterAttribute : ActionFilterAttribute {
        private static int counter = 0;
        private static int limit;

        public CounterAttribute(int requestLimit) {
            limit = requestLimit;
        }

        public override Task OnActionExecutingAsync(HttpActionContext actionContext,
                CancellationToken cancellationToken) {

            return Task.Factory.StartNew(() => {
                if (counter < limit) {
                    Debug.WriteLine("Request {0} of {1}", counter, limit);
                    counter++;
                } else {
                    actionContext.Response = actionContext.Request.CreateErrorResponse(
                        HttpStatusCode.ServiceUnavailable, "Limit Reached");
                }
            });
        }
    }
}

I only need to override the OnActionExecutingAsync method because I need to intercept requests before they get to the action method. To stop the action method from being executed, I create an HttpResponseMessage object and use it to set the HttpActionContext.Response method, like this:

...
actionContext.Response = actionContext.Request.CreateErrorResponse(
    HttpStatusCode.ServiceUnavailable, "Limit Reached");
...

The ActionFilterAttribute class implementation checks to see whether the HttpActionContext.Response property has been set after the OnActionExecutingAsync method has been called. If the Response property is null, then the OnActionExecutedAsync method is called as normal. If the Response property isn’t null, then the HttpResponseMessage object to which it has been set is used as the result of its ExecuteActionFilterAsync implementation, which creates the short-circuit effect.

Understanding the Filter Pipeline

If you are especially sharp-eyed, you will have noticed something in the output from the two action filters in the previous section that gives a hint about the way in which filters are executed. Before I move on to describe the other kinds of filer, I am going to dig into the details of how filters are ordered into a filter pipeline when they are executed. Here is the output from the previous example:

Request 0 of 3
Elapsed time: 86870 ticks, GET http://localhost:49412/api/products/
Request 1 of 3
Elapsed time: 3196 ticks, GET http://localhost:49412/api/products/
Request 2 of 3
Elapsed time: 2220 ticks, GET http://localhost:49412/api/products/
Elapsed time: 7077 ticks, GET http://localhost:49412/api/products/

The clue is the highlighted statement, which shows that the Time filter is still processing requests even though the Counter filter is returning 503 (Service Unavailable) responses. This is because the Time filter appears before the Counter filter in the dispatch pipeline, as shown in Figure 23-4.

9781484200865_Fig23-04.jpg

Figure 23-4. The sequence of action filters in the request pipeline

Filters are organized into a specific sequence, and the Time filter appears before the Counter filter in the filter pipeline. Even though the Counter filter is short-circuiting the dispatch process, the HttpResponseMessage object that it creates is passed back along the pipeline to the filters that appear before it. Table 23-8 puts the filter pipeline into context.

Table 23-8. Putting the Filter Pipeline in Context

Question

Answer

What is it?

The filter pipeline provides information about the filters that apply to an action method, sorted by scope.

When should you use it?

The filter pipeline is of interest only for diagnostic purposes or when adding support for filters to controllers that are derived directly from the IHttpController interface, rather than the ApiController class.

What do you need to know?

The filter pipeline takes into account the effect of scope on the order in which filters will be executed but not the way that the ApiController class organizes filters by type. I show you how to re-order the pipeline at the end of this chapter.

Displaying the Filter Pipeline

You can get insight into the filter pipeline through the HttpActionContext.GetFilterPipeline method, which returns an enumeration of FilterInfo objects. Each FilterInfo object provides the details of one filter using the properties shown in Table 23-9.

Table 23-9. The Properties Defined by the FilterInfo Class

Name

Description

Instance

Returns the filter object, which implements the IFilter interface.

Scope

Returns the scope of the filter, expressed using a value from the FilterScope enumeration. See the “Understanding Filter Scope” section for details.

The simplest way to examine the filter pipeline is to create a custom implementation of the IHttpActionSelector interface by deriving from the ApiControllerActionSelector class and overriding the SelectAction method to display information about the filters. Listing 23-7 shows the contents of the PipelineActionSelector.cs file, which I added to the Infrastructure folder.

Listing 23-7. The Contents of the PipelineActionSelector.cs File

using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Dispatch.Infrastructure {
    public class PipelineActionSelector : ApiControllerActionSelector {

        public override HttpActionDescriptor SelectAction(HttpControllerContext
                controllerContext) {

            HttpActionDescriptor action = base.SelectAction(controllerContext);

            IEnumerable<FilterInfo> filters = action.GetFilterPipeline();

            foreach (FilterInfo filter in filters) {
                Debug.WriteLine("Scope {0} Type: {1}",
                    filter.Scope, filter.Instance.GetType().Name);
            }

            return action;
        }
    }
}

I still rely on the base implementation of the SelectionAction method, but I also write out a message that describes each filter. Listing 23-8 shows how I registered the PipelineActionSelector class as the implementation of the IHttpActionSelector interface that Web API will use.

Listing 23-8. Registering a Custom Action Selector in the WebApiConfig.cs File

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

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 }
            );

            config.Services.Replace(typeof(IHttpActionSelector),
                new PipelineActionSelector());
        }
    }
}

To test the custom action selector and get details of the filter pipeline, start the application and use the browser to navigate to the /Home/Index URL. Click the Get All button, and you will see messages similar to these in the Visual Studio Output window:

Scope Controller Type: TimeAttribute
Scope Action Type: CounterAttribute
Request 0 of 3
Elapsed time: 87523 ticks, GET http://localhost:49412/api/products/

In the sections that follow, I’ll explain why the filters are organized this way. As I continue to describe different kinds of filter, I’ll revisit the pipeline and demonstrate how they are organized.

Image Note  The filter pipeline doesn’t completely reflect the order in which other types of filter are executed, but I will revise the code at the end of the chapter to sort the filters by type.

Understanding Filter Scope

There are three ways in which a filter can be applied, which determines the requests to which it is applied to, known as the filter’s scope. The FilterScope enumeration defines a value for each scope, as described in Table 23-10.

Table 23-10. The Values Defined by the FilterScope Enumeration

Scope

Description

Global

The filter is executed for all requests.

Controller

The filter is executed for requests that target any action method defined by the controller to which it has been applied.

Action

The filter is executed for requests that target the action method to which it has been applied.

Filters are first ordered by type and then by scope. I listed the different filter types in Table 23-2, but here is the order in which the three most commonly used types are executed:

  1. Authentication filters
  2. Authorization filters
  3. Action filters

This order is enforced by the ApiController class and cannot be changed by implementing he interfaces that I described in Chapter 22. Unlike the MVC framework, there is no way to control the execution order of filters of the same type with the same scope.

Image Note  There are two other kinds of filter—error filter and override filters—that operate differently and that I describe in Chapter 24.

In the dispatch process, the ApiController sorts the filters in the pipeline by type and then sorts them by scope. This is why the Time filter appears in the filter pipeline before the Counter filter—it is been applied to the controller, and controller-scoped filters take precedence over those that have been applied directly to an action method.

Image Tip  The effect of the scope precedence rules means that my Time filter isn’t measuring just the execution of the action method; it is also measuring the amount of time it takes to execute any filters that appear subsequently in the filter pipeline.

Creating a Global Filter

I have demonstrated controller- and action method–scoped filters; to complete the set, I need to show you how to create a global feature. Listing 23-9 shows the contents of the SayHelloAttribute.cs file, which I added to the Infrastructure folder.

Listing 23-9. The Contents of the SayHelloAttribute.cs File

using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Dispatch.Infrastructure {
    public class SayHelloAttribute : ActionFilterAttribute {

        public string Message { get; set; }

        public override Task OnActionExecutingAsync(HttpActionContext actionContext,
            CancellationToken cancellationToken) {

            Debug.WriteLine("SayHello: {0}", (object)Message ?? "Hello");
            return Task.FromResult<object>(null);
        }
    }
}

This file contains a filter that simply writes a message to the Visual Studio Output window so that I can demonstrate filter precedence.

Image Note  The fact is that there are not many general-purpose uses for action filters beyond logging and measuring performance. This is a feature that it is important to understand but becomes important only when you need to integrate functionality that doesn’t fit neatly into the Web API/MVC model.

Global filters are registered in the WebApiConfig.cs file, as shown in Listing 23-10.

Listing 23-10. Registering a Global Filter in the WebApiConfig.cs File

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

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 }
            );

            config.Services.Replace(typeof(IHttpActionSelector),
                new PipelineActionSelector());

            config.Filters.Add(new SayHelloAttribute { Message = "Global Filter" });
        }
    }
}

To test the global filter, start the application and use the browser to navigate to the /Home/Index URL. Click the Get All button, and you will see messages similar to the following in the Visual Studio Output window:

Scope Global Type: SayHelloAttribute
Scope Controller Type: TimeAttribute
Scope Action Type: CounterAttribute
SayHello: Global Filter
Request 0 of 3
Elapsed time: 83963 ticks, GET http://localhost:49412/api/products/

I have highlighted the important messages. The first shows that the SayHelloAttribute is the first filter in the pipeline, and the second confirms this by showing that output from the filter appears before the other filter messages.

Image Tip  Although I created my global filter as an attribute, this is not required because the filter isn’t being applied directly to a controller or action method. I prefer to create filter attributes, however, because it means that I can easily change the scope of a filter without having to modify its code.

Working with Authentication Filters

Authentication filters, as their name suggests, allow you to ensure that action methods are invoked only by clients that have been authenticated. Most Web API applications will use the ASP.NET Identity platform to perform authentication, and you saw how filters are used to apply ASP.NET Identity in Chapter 6. Table 23-11 puts authentication and filters in context.

Table 23-11. Putting Authentication Filters in Context

Question

Answer

What are they?

Authentication filters are responsible for inspecting an HTTP request and identifying the user identity associated with it. Requests with which no user can be associated are short-circuited.

When should you use them?

Apply an authentication filter when you want to restrict access to one or more action methods to requests made by users who are known to the application.

What do you need to know?

Authentication filters will allow access to all users as long as the request contains valid credentials. Use an authorization filter if you want to restrict access to specific users, as described in Chapter 24.

Authentication filters are invoked first irrespective of scope and before any other kind of filter and the action method. When authentication fails, the dispatch process is short-circuited, and an HttpResponseMessage is produced from the authentication filter without any other filter—or the target action method—being invoked.

Image Caution  In this section, I create some insecure filters solely to demonstrate how these types of filter work. Do not use these filters in a real project. More generally, think long and hard before creating your own authentication code—stick to the built-in support provided by ASP.NET unless you are truly an expert in writing secure code. It is all too easy to make a simple mistake that exposes your web service to attackers. I have been writing software for decades (certainly longer than I care to recount), and I still hesitate before writing custom security code. You should, too.

Preparing for Authentication

If your application needs to tailor the functionality and content delivered to different users, then you need some way of managing the users’ identities and security credentials. This is usually handled through the ASP.NET Identity system (or its predecessor, ASP.NET Membership), but I don’t want to get into the details of Identity beyond the description I gave for the SportsStore application, so for this chapter, I will define a user manager class that I will use to authenticate and authorize users using static data. Listing 23-11 shows the contents of the StaticUserManager.cs class file that I added to the Infrastructure folder.

Listing 23-11. The Contents of the StaticUserManager.cs File

using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;

namespace Dispatch.Infrastructure {
    public class StaticUserManager {
        private static Dictionary<string, string[]> roles;

        static StaticUserManager() {
            roles = new Dictionary<string, string[]>();
            roles.Add("admin", new string[] { "admins", "users" });
            roles.Add("bob", new string[] { "users" });
        }

        public static IPrincipal AuthenticateUser(string user, string pass) {
            if (roles.ContainsKey(user) && pass == "secret") {
                return new StaticUser(user, roles[user]);
            }
            return null;
        }
    }

    public class StaticUser: IIdentity, IPrincipal {
        private string[] roles;

        public StaticUser(string name, string[] rolesList) {
            Name = name;
            AuthenticationType = "Basic";
            IsAuthenticated = true;
            roles = rolesList;
        }

        public string AuthenticationType { get; private set; }
        public bool IsAuthenticated { get; private set; }
        public string Name { get; private set; }

        public IIdentity Identity {
            get { return this; }
        }

        public bool IsInRole(string role) {
            return roles.Any(x => x == role);
        }
    }
}

ASP.NET uses two interfaces to identity users, IPrincipal and IIdentity, both of which are defined in the System.Security.Principal namespace. The IIdentity interface is used to represent a specific user and defines the properties shown in Table 23-12.

Table 23-12. The Properties Defined by the IIdentity Interface

Name

Description

AuthenticationType

Returns a string that specifies the type of authentication used to create the identity

IsAuthenticated

Returns true if the user has been authenticated

Name

Returns the name of the user

The IPrincipal interface is a wrapper around an IIdentity object and provides information about the roles to which a user belongs (roles are used for authorization, as I explain in Chapter 24). The IPrincipal interface defines the property and method shown in Table 23-13.

Table 23-13. The Properties Defined by the IPrincipal Interface

Name

Description

Identity

Returns an implementation of the IIdentity interface that describes the user

IsInRole(role)

Returns true if the user has been assigned to the specified role and false otherwise

User management systems usually provide their own implementations of these interfaces, and in Listing 23-11, I have defined the StaticUser class, which implements both interfaces. The StaticUserManager class provides a static AuthenticateUser method that accepts a username and password as its parameters and returns an instance of the StaticUser class for known users. I have defined two users and some roles, creating the user set shown in Table 23-14. I use the roles in Chapter 24, when I describe authorization filters.

Table 23-14. The Users, Passwords, and Roles Defined by the StaticUser Class

User

Password

Roles

admin

secret

admins, users

bob

secret

users

Image Caution  I have hard-coded the usernames, passwords, and roles into the StaticUserManager class, which is fine for a simple example but causes problems in a real project because it means you have to deploy a new version of the application each time there is a new user or a change to an existing one.

Understanding Authentication Filters

Authentication filters are responsible for establishing the identity of the user who has made the request. Web API doesn’t specify how the user is identified, which means that authentication filters can be used to integrate any user management system into your web service. For most applications, the ASP.NET Identity system is the best way to handle authentication (and authorization, which I describe in Chapter 24), but even so, it is useful to be able to understand how the Web API authentication process works, especially when you don’t get the results you expect in your own projects.

Authentication filters are defined by the IAuthenticationFilter interface, which is defined as follows:

using System.Threading;
using System.Threading.Tasks;

namespace System.Web.Http.Filters {

    public interface IAuthenticationFilter : IFilter {

        Task AuthenticateAsync(HttpAuthenticationContext context,
            CancellationToken cancellationToken);

        Task ChallengeAsync(HttpAuthenticationChallengeContext context,
            CancellationToken cancellationToken);
    }
}

Authentication filters are a little odd. The AuthenticateAsync method is called before the request is processed by other filters, and its job is to identify the user associated with the request or to create an IHttpActionResult object that reports an error to the client.

Image Note  In an MVC Framework application, authentication failures are usually handled by redirecting the client to the login URL for the application. This technique doesn’t work for HTTP web services, where the client cannot be assumed capable of parsing HTML or prompting the user for their credentials. Web services can be used to authenticate users—as I demonstrate in Chapter 24—but failed authentication should be handled by returning a 401 (Unauthorized) response to the client.

The oddity is that the IHttpActionResult produced by the AuthenticateAsync method isn’t used to create the response sent to the client. Instead, that is the responsibility of the ChallengeAsync method, which is invoked after the request has been processed, even if the user has been properly authenticated. This will start to make sense with an example—but not entirely since, as I say, authentication filters are odd.

UNDERSTANDING BASIC HTTP AUTHENTICATION

The example authentication filter that I describe in this section uses the basic authentication mechanism that is defined as part of the HTTP specification. It is incredibly simple to implement but isn’t widely used because it has some profound limitations, especially when used without SSL.

To authenticate itself, the client adds an Authorization header to the request, like this:

Authorization: Basic YWRtaW46YWRtaW5TZWNyZXRY

The value of the header is the word Basic, followed by a Base64-encoded string that contains the username and password separated by a colon (the : character). Decoding the string YWRtaW46YWRtaW5TZWNyZXRY reveals admin:adminsecret, meaning that the client is authenticating itself as the user admin with a password of adminSecret.

If the client sends a request without the Authorization header or with credentials that are incorrect (specifying either a nonexistent user or an invalid password), then the web service will return a 401 (Unauthorized) response that includes a WWW-Authenticate that specifies the Basic authentication mechanism, like this:

WWW-Authenticate: Basic

Basic authentication requires the client to send the user’s credentials to the web service with every request in a format that is easily decoded. If you do find yourself using Basic authentication (and I sincerely hope that you do not), then ensure that all of your requests are handled through SSL.

Creating an Authentication Filter

To demonstrate how authentication filters operate, I created a class file called CustomAuthenticationFilter.cs in the Infrastructure folder and used it to create the filter shown in Listing 23-12.

Image Caution  This filter relies on the basic HTTP authentication scheme, which is wholly inadequate for use in real projects unless SSL is used. Use this example only to understand how the authentication filter mechanism works, and see Part 1 for details of how to use ASP.NET Identity to handle user authentication in real projects.

Listing 23-12. The Contents of the CustomAuthenticationFilter.cs File

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Results;

namespace Dispatch.Infrastructure {
    public class CustomAuthenticationAttribute : Attribute, IAuthenticationFilter {

        public Task AuthenticateAsync(HttpAuthenticationContext context,
                    CancellationToken cancellationToken) {

                context.Principal = null;
                AuthenticationHeaderValue authentication =
                    context.Request.Headers.Authorization;
                if (authentication != null && authentication.Scheme == "Basic") {
                    string[] authData
                        = Encoding.ASCII.GetString(Convert.FromBase64String(
                            authentication.Parameter)).Split(':'),
                    context.Principal
                        = StaticUserManager.AuthenticateUser(authData[0], authData[1]);
                }

                if (context.Principal == null) {
                    context.ErrorResult
                        = new UnauthorizedResult(new AuthenticationHeaderValue[] {
                        new AuthenticationHeaderValue("Basic") }, context.Request);
                }

                return Task.FromResult<object>(null);
        }

        public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
                CancellationToken cancellationToken) {
            return Task.FromResult<object>(null);
        }

        public bool AllowMultiple {
            get { return false; }
        }
    }
}

There is a lot going on in this class, so I’ll break down the details in the sections that follow. As I get into the details, the most important thing to remember is that the authentication filter is responsible for identifying the user from the request and not obtaining the credentials from the user.

Image Tip  Do not implement an authentication filter in your projects until you have read Chapter 24, in which I explain how to create a message handler to authenticate requests and refactor the filter to consume the data that it generates.

Checking the Request

The job of the AuthenticateAsync method is to examine the request to see whether it contains the information that is required to identify a user. Information about the request is provided through an instance of the HttpAuthenticationContext class, which defines the properties shown in Table 23-15.

Table 23-15. The Properties Defined by the HttpAuthenticationContext Class

Name

Description

ActionContext

This property returns the HttpActionContext object that describes the action method that has been selected to execute the request.

ErrorResult

This is property is set to an implementation of the IHttpActionResult interface if the request cannot be authenticated.

Principal

This property is set to an implementation of the IPrincipal interface if the request has been authenticated. Setting the Principal property causes the HttpContext.Principal property to be set as well, which makes details of the authentication process available to other components—including the authorization filters I described in Chapter 24.

Request

This property returns the HttpRequestMessage object that describes the current request.

To create an effective authentication filter, the AuthenticateAsync must do one of the following:

  • If the request is authenticated, set the HttpAuthenticationContext.Principal property to an implementation of the IPrincipal interface.
  • If the request cannot be authenticated, set the HttpAuthenticationContext.ErrorResult property to an implementation of the IHttpActionResult interface that will produce a 401 (Unauthorized) result.

Web API doesn’t specify the mechanism that the AuthenticateAsync method uses to authenticate users, but for web services, the most common techniques involve inspecting request cookies or headers. For HTTP basic authentication, I need to look for the Authorization header, which I do like this:

...
context.Principal = null;
AuthenticationHeaderValue authentication = context.Request.Headers.Authorization;
if (authentication != null && authentication.Scheme == "Basic") {
    string[] authData = Encoding.ASCII.GetString(Convert.FromBase64String(
                            authentication.Parameter)).Split(':'),
    context.Principal = StaticUserManager.AuthenticateUser(authData[0], authData[1]);
}
...

The Authorization header is represented by the AuthenticationHeaderValue class, which parses the header value and presents Scheme and Parameter properties. The Scheme property returns Basic when HTTP basic authentication is used, and the Parameter property will return the Base64-encoded username and password, which I decode and pass to the StaticUserManager class to authenticate the user and obtain the IPrincipal object.

Image Tip  Notice that I explicitly set the HttpAuthenticationContext.Principal property to null before attempting to authenticate the request. If a Web API application is running on the traditional ASP.NET platform, then the Principal property may already be set by the time that the AuthenticateAsync method is called to represent credentials from Windows or elsewhere. It is possible to locate and disable other sources of IPrincipal objects, but I find explicitly setting the Principal property to null to be more reliable.

Getting the right value for the Principal value is important because I use it to check to see whether I need to report an error through the ErrorResult property, like this:

...
if (context.Principal == null) {
    context.ErrorResult = new UnauthorizedResult(
        new AuthenticationHeaderValue[] { new AuthenticationHeaderValue("Basic") },
            context.Request);
}
...

If the call to the StaticUserManager.AuthenticateUser method hasn’t produced an IPrincipal or if the method wasn’t called because the request didn’t contain the authentication information I require, then I set the HttpAuthenticationContext.ErrorResult property to an instance of the UnauthorizedResult class. The UnauthorizedResult class allows me to specify the WWW-Authenticate header, which tells the client which authentication scheme should be used to authenticate the user.

Adding the Response Challenge

The ChallengeAsync method is call for every request, but there are two scenarios that have to be dealt with. The first is that the AuthenticateAsync method has set a value for the HttpAuthenticationContext.ErrorResult property, which has the effect of short-circuiting the dispatch process and invoking the ChallengeAsync method without invoking any other filter or the action method.

The second scenario is that authentication succeeded, and the ChallengeAsync method is called so that the filter has the change to modify the HttpResponse object, usually to make it easier to authenticate subsequent requests.

For my HTTP basic authentication scheme, I don’t have to take any action at all because I created the response needed for failed authentications in the AuthenticateAsync method, which is how I prefer to create authentication filters. You will sometimes encounter code where the IHttpActionResult is executed, checked for a status code, and then replaced by a new result, but that isn’t really required. If you do need to modify the result, you can use the HttpAuthenticationChallengeContext parameter, which defines the properties shown in Table 23-16.

Table 23-16. The Properties Defined by the HttpAuthenticationChallengeContext Class

Name

Description

ActionContext

This property returns the HttpActionContext object that describes the action method that has been selected to execute the request.

Request

This property returns the HttpRequestMessage object that describes the current request.

Result

This property is used to specify the IHttpActionResult that will be executed to generate the HttpResponseMessage object for the request.

Applying and Testing the Authentication Filter

Authentication filters are applied just like any other filter and can be given global, controller, and action scope. Listing 23-13 shows how I applied my example filter to the Products controller.

Listing 23-13. Applying an Authentication Filter in the ProductsController.cs File

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

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 },
            };

        [Time]
        [Counter(3)]
        public IEnumerable<Product> Get() {
            return products;
        }

        [CustomAuthentication]
        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 applied the filter to the version of the Get method that takes a parameter. This has the effect of allowing every other action method that is defined by the controller to be targeted by any request but restricts the Get action to requests that have been authenticated.

Image Tip  An authentication filter doesn’t restrict access to specific users—that’s the job of authorization filters, which I describe in Chapter 24. An authentication filter will grant access to any authenticated request, irrespective of the user identity associated with it.

To test the filter, start the application and use the browser to navigate to the /Home/Index URL. First, click the Get All or Post button to ensure that unauthenticated requests are handled correctly. To check the authentication process, click the Get One button.

The client-side code will respond to the button press by sending an Ajax request to the /api/products/2 URL, which the dispatch process will match to the Get method that accepts a parameter in the Products controller. The AuthenticateAsync method defined by the CustomAuthentication filter will be invoked and, failing to find the Authorization header, will return a 401 (Unauthorized) response to the client.

Almost all browsers have built-in support for basic authentication and will respond by prompting the user for credentials on receipt of a 401 (Unauthorized) response that contains a WWW-Authenticate header set to Basic, as illustrated by Figure 23-5.

9781484200865_Fig23-05.jpg

Figure 23-5. Chrome prompting the user for credentials

Enter one of the usernames and passwords from Table 23-14 (either admin or bob with a password of secret), and Chrome will resend its Ajax request to the /api/products/2 URL, but this time with the required header. The result is that the authentication filter will set the IPrincipal object on the HttpRequestMessage object, and the dispatch pipeline will not be short-circuited. Alternatively, you can click the Cancel button, in which case the 401 status code is reported to the client-side JavaScript code, which will display the error.

Viewing the Filter Pipeline

If you look at the Visual Studio Output window, you will see that the PipelineActionSelector class that I created earlier in the chapter has been writing out the filter pipeline for requests. For a request that targets the Get method to which I applied the authentication filter, the output looks like this:

Scope Global Type: SayHelloAttribute
Scope Controller Type: TimeAttribute
Scope Action Type: CustomAuthenticationAttribute

There are three filters in the pipeline for this action method: the global SayHelloAttribute filter that I set up in the “Creating a Global Filter” section, the TimeAttribute filter I created in the “Working with Action Filters” section, and the CustomAuthenticationAttribute filter from the previous section.

The pipeline as returned by the HttpActionContext.GetFilterPipeline method takes into account the effect that scope has on the order of the filters but not the effect of the filter type. As I explained earlier, the filters are executed in this order:

  1. Authentication filters
  2. Authorization filters
  3. Action filters

There are two special kinds of filter—error and override filters—that I describe in Chapter 24, but for now it is enough to focus on the three filter types in the list. Listing 23-14 shows how I have updated the PipelineActionSelector class to process the filter pipeline and sort it by filter type.

Image Tip  Remember that the filter pipeline is a list of the filters that will be executed only if none of the filters elects to short-circuit the dispatch process.

Listing 23-14. Sorting the Pipeline by Filter Type in the PipelineActionSelector.cs File

using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Linq;

namespace Dispatch.Infrastructure {
    public class PipelineActionSelector : ApiControllerActionSelector {

        public override HttpActionDescriptor SelectAction(HttpControllerContext
                controllerContext) {

            HttpActionDescriptor action = base.SelectAction(controllerContext);

            IEnumerable<FilterInfo> filters = action.GetFilterPipeline();

            IEnumerable<FilterInfo> orderedFilters =
                GetFilters<IAuthenticationFilter>(filters)
                .Concat(GetFilters<IAuthorizationFilter>(filters))
                .Concat(GetFilters<IActionFilter>(filters));

            foreach (FilterInfo filter in orderedFilters) {
                Debug.WriteLine("Scope {0} Type: {1}", filter.Scope,
                    filter.Instance.GetType().Name);
            }

            return action;
        }

        private IEnumerable<FilterInfo> GetFilters<T>(IEnumerable<FilterInfo> filters) {
            return filters.Where(f => f.Instance is T);
        }
    }
}

I used LINQ to arrange filters by type, which produces the following messages in the Visual Studio Output window when you start the application, navigate to the /Home/Index URL, and click the Get One button:

Scope Action Type: CustomAuthenticationAttribute
Scope Global Type: SayHelloAttribute
Scope Controller Type: TimeAttribute

Summary

In this chapter, I explained the role that filters play in the dispatch process and showed you two types of filters: action filters and authentication filters. I demonstrated how to read the filter pipeline and explained how scope and filter types are used to order filters for execution. In the next chapter, I continue describing the Web API support for filters and show you how authorization, exception, and override filters work.

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

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