CHAPTER 24

image

Filters Part II

In this chapter, I continue describing the Web API support for filters and demonstrate how authorization filters work. I also show you two kinds of special filters: exception filters and override filters. Table 24-1 summarizes this chapter.

Table 24-1. Chapter Summary

Problem

Solution

Listing

Create an authorization filter.

Implement the IAuthorizationFilter interface, derive from the AuthorizationFilterAttribute class, or use the Authorize attribute.

1, 5–9

Restrict access to an action method.

Apply an authorization attribute to the action method or its containing controller.

2–4

Handle an exception thrown by an action method or another filter.

Create an exception filter by implementing the IExceptionFilter interface or deriving from the ExceptionFilterAttribute class.

10–14

Disable the effect of a controller-wide or global exception filter for an action method.

Apply an override filter.

15–16

Preparing the Example Project

I am going to continue with the Dispatch project I created in Chapter 19 and have been building on throughout this part of the book. No preparatory changes are required for this chapter.

Image Tip  You don’t have to re-create the project from Chapter 19 and apply all of the changes I have described since. You can also download all of the source code organized by chapter from http://apress.com.

Reviewing Filters in the Dispatch Process

In Chapter 23, I explained how filters are wrapped around the action method so that they can execute logic before and after it is executed. This provides the means to alter the request processed by the action method or the response that will be sent to the client, and it allows for short-circuiting, where the dispatch process is terminated. The order in which filters are executed depends on the type of filter and its scope. Table 24-2 shows the different filter types in the order they are executed.

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

Authorization

IAuthorizationFilter

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

Action

IActionFilter

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

Exception

IExceptionFilter

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

Override

IOverrideFilter

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

In this chapter, I describe the last of the convention filters (the authorization filter) and explain how two special types of filters work (the exception and override filters). Authorization filters are executed after authentication filters and before action filters, as illustrated by Figure 24-1.

9781484200865_Fig24-01.jpg

Figure 24-1. Filters in the dispatch process

Image Tip  I have used two common contractions in Figure 24-1 to fit everything on the page. AuthN is a contraction of authentication, and AuthZ is a contraction of authorization.

Working with Authorization Filters

Authorization filters are used to restrict access to action methods to specific users. This may seem like a variation on the authentication filters that I described in Chapter 23, but as you will see, authorization works differently.

In most applications, you will simply apply the Authorize attribute to your controllers and action methods and rely on the ASP.NET Identity system to perform the authentication and authorization work, but in this section I go behind the scenes and explain how this important kind of filter works. Table 24-3 puts authorization filters into context.

Table 24-3. Putting Authorization Filters in Context

Question

Answer

What are they?

Authorization filters restrict action methods to specific users or the roles to which they have been assigned.

When should you use them?

The most common use for authorization filters is to prevent normal users from gaining access to administrative functionality.

What do you need to know?

Authorization filters generally rely on another component to authenticate requests and then inspect the authenticated user for role membership. The simplest way to perform authorization is to use the Authorize attribute.

Understanding Authorization Filters

Authorization filters are defined by the IAuthorizationFilter interface, which is defined in the System.Web.Http.Filters namespace and shares the same basic approach as action filters. Here is the IAuthorization interface:

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 IAuthorizationFilter : IFilter {

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

Web API doesn’t enforce any kind of restrictions on how requests are authorized or how information about the user associated with the request is obtained. The most common approach is to read the value of the HttpRequestContext.Principal property to get an IPrincipal object that can be compared to an authorization policy. This approach allows authorization to be decoupled by authentication, meaning that the authentication mechanism can be changed without affecting authorization.

Creating an Authorization Filter

Authorization filters are required to enforce an authorization policy before the action method is executed. If a request complies with the policy—meaning that it is associated with a user who has been granted access to the action method—then action filter does nothing. If the request doesn’t comply with the policy, the filter short-circuits the dispatch process and returns a 401 (Unauthorized) response to the client. To demonstrate how an authorization filter works, I added a class file called CustomAuthorizationAttribute.cs to the Infrastructure folder and used it to define the filter shown in Listing 24-1.

Listing 24-1. The Contents of the CustomAuthorizationAttribute.cs File

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Dispatch.Infrastructure {

    public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter {
        private string[] roles;

        public CustomAuthorizationAttribute(params string[] rolesList) {
            roles = rolesList;
        }

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

            IPrincipal principal = actionContext.RequestContext.Principal;
            if (principal == null || !roles.Any(role => principal.IsInRole(role))) {
                return Task.FromResult<HttpResponseMessage>(
                    actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized));
            } else {
                return continuation();
            }
        }

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

Image Tip  You don’t have to create a filter directly from the interface. There is a built-in attribute class that applies authorization, as I describe in the “Using the Authorize Attribute” section. The examples that follow explain how authorization works, but you can jump right to the built-in attribute section if you just want to see how to restrict access to your action methods.

The filter employs some of the techniques I described in Chapter 23 to authorize requests. The class constructor takes an array of roles that will be granted access and uses the IPrincipal.IsInRole method to check to see whether the authenticated user belongs to one of the permitted roles.

If the user is in a permitted role, then the filter invokes the continuation function and returns the result. This is often referred to as a pass-through, meaning that a request from an allowed user passes through the filter without any modification.

If the user is not in a permitted role, then the filter short-circuits the dispatch process and returns an HttpResponseMessage that yields a 401 (Unauthorized) status code to the client.

Image Tip  For simple applications, you might be tempted to perform authorization using individual account names rather than roles, which you can do through the IIdentity associated with the IPrincipal object. Be careful, though: simple applications often live longer than initially inspected and grow in unexpected directions. Working with roles, even when there is only one user, often pays off in the long term.

Appling the Authorization Filter

Notice that the CustomAuthorizationAttribute in Listing 24-1 does not identity the user associated with the request but relies on the HttpContext.Principal property having been set. The cost of loosely coupling authentication and authorization is that authorization filters rely on there being another component to identify the user and create the IPrincipal associated with the request. In Listing 24-2, you can see how I have paired the authentication attribute from Chapter 23 with the authorization filter to restrict access to an action method defined by the Products controller.

Listing 24-2. Applying Authorization in the ProductsController.cs File

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

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

        [CustomAuthentication]
        [CustomAuthorization("admins")]
        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;
        }
    }
}

The CustomAuthentication filter sets up the IPrincipal for the request, which is then assessed by the CustomAuthorization filter to make sure the user has been assigned to the admins role, which I specified as the constructor parameter.

Testing the Authorization Filter

To test the authorization filter, start the application and use the browser to navigate to the /Home/Index URL. Click the Get One button and, when prompted, enter the username bob and the password secret.

The browser will encode the credentials you have provided and resend the request to the server. The authentication filter will create an IPrincipal object that represents the user bob and allow the request to continue through the dispatch process. The authorization filter inspects the IPrincipal to see whether the user has been assigned to the admins role. Table 24-4 lists the users and roles I defined in Chapter 23 and shows that bob isn’t part of the admin role and therefore isn’t authorized to invoke the action method.

Table 24-4. The Users, Passwords, and Roles Defined by the StaticUser Class

User

Password

Roles

admin

secret

admins, users

bob

secret

users

The authorization filter short-circuits the request and returns a 401 (Unauthorized) response to the client. Browsers that deal with basic authentication directly intercept the 401 response before it is passed to jQuery, which means that you will be prompted for different credentials without ever seeing the error response.

Image Note  Browsers store HTTP basic authentication credentials even when the history cache is cleared. To switch from one user account to another, you will usually have to restart the browser, navigate to the /Home/Index URL, and click the Get One button again.

Restart the browser, navigate to the /Home/Index URL, and click the Get One button again. This time, enter the username admin and the password secret. These credentials are for a user who has been assigned to the admins role, which means that the authorization filter will allow the request to pass to the next stage of the dispatch process.

Removing the Authentication Filter

The type ordering of filters ensures that the authentication filter runs before the authorization filter, even when they have the same scope. (I explained filter scope in Chapter 23.) If you look at the Visual Studio Output window, you will see that the pipeline information written out by the PipelineActionSelector class I created previously confirms the authentication before authorization sequencing.

Scope Action Type: CustomAuthenticationAttribute
Scope Action Type: CustomAuthorizationAttribute
Scope Global Type: SayHelloAttribute
Scope Controller Type: TimeAttribute
SayHello: Global Filter
Elapsed time: 1497 ticks, GET http://localhost:49412/api/products/2

This ordering allows me to create an authorization filter that builds on the authentication process, without having to worry about where the IPrincipal objects are coming from. The drawback of depending on an authentication filter is that I have to make sure that I apply two filters every time I want to perform authorization.

A more common approach is to create a message handler that will perform authentication earlier in the dispatch process so that the authorization filter can be applied on its own, without needing an accompanying authentication filter. Listing 24-3 shows the contents of the AuthenticationDispatcher.cs file that I added to the Infrastructure folder and used to define an authenticating message handler.

Listing 24-3. The Contents of the AuthenticationDispatcher.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;

namespace Dispatch.Infrastructure {
    public class AuthenticationDispatcher : DelegatingHandler {

        protected override async Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request,
                CancellationToken cancellationToken) {

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

            HttpResponseMessage response = await base.SendAsync(request,
                cancellationToken);
            if (response.StatusCode == HttpStatusCode.Unauthorized) {
                response.Headers.Add("WWW-Authenticate", "Basic");
            }
            return response;
        }

    }
}

This filter uses the same basic code I applied in the authentication filter in Chapter 24 but arranged in a different way. I still check for the Authorization header and use it to authenticate the request if it is present, but I don’t terminate the dispatch process for requests that cannot be validated. This allows requests to flow unhindered through the application to reach action methods open to anyone but still provides the authorization filter with the information it needs.

In addition to authenticating requests, the message handler inspects responses and adds a WWW-Authenticate header to 401 (Unauthorized) responses to give the client the information it needs to try again. Listing 24-4 shows how I registered the authenticating message handler in the WebApiConfig.cs file.

Listing 24-4. Registering a Message Handler 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" });

            config.MessageHandlers.Add(new AuthenticationDispatcher());
        }
    }
}

I can now remove the authentication filter from the action method in the Products controller and rely on just the authorization filter, as illustrated by Listing 24-5.

Listing 24-5. Removing the Authentication Filter from the ProductsController.cs File

...
//[CustomAuthentication]
[CustomAuthorization("admins")]
public Product Get(int id) {
    return products.Where(x => x.ProductID == id).FirstOrDefault();
}
...

Image Tip  The authentication filter I created in Chapter 24 can still be useful because it restricts access to any authenticated user, which is a common requirement, especially for paid-for services. In the “Reworking the Authentication Filter” section, I revisit the authentication filter and rework it to take advantage of the message handler.

Retesting the Authorization Filter

The simplest way to test the effect of the message handler is to give the browser the basic credentials that you want to test as part of the URL. Start the application and use the browser to request the following URL, taking care to change the port number to correspond to the one your application is running on:

http://admin:secret@localhost:49412/Home/Index

The part of the URL that I have highlighted tells the browser to use the specified credentials if basic authentication is required. Click the Get One button, and the client-side jQuery code will send an Ajax request to the web service.

The request does not contain any credentials, so the authorization filter will short-circuit the dispatch process and return a 401 (Unauthorized) response, to which the authentication message handler will add a WWW-Authenticate header telling the client that basic authentication is required.

The browser will automatically resend the request using the credentials you provided in the URL without notifying jQuery. The message handler will process the Authorization header and create the IPrincipal object, which allows the authorization filter to validate the request.

To see the effect of a request from a user who is not allowed to access the action method, request the following URL:

http://bob:secret@localhost:49412/Home/Index

Clicking the Get One button will repeat the sequence of requests but still produce a 401 (Unauthorized) result because the bob has not been assigned to the admins role, which is the policy that the authorization filter enforces.

Using the Built-in Authorization Filter Attributes

Two built-in filter classes allow you to perform authorization without needing to implement a class directly from the IAuthorizationFilter interface. The first is the AuthorizationFilterAttribute class, defined in the System.Web.Http.Filters namespace; this class makes it possible to write a filter without worrying about continuations, much like the ActionFilterAttribute class I described in Chapter 23 does for action filters. The AuthorizationFilterAttribute class defines the method shown in Table 24-5.

Table 24-5. The Method Defined by the AuthorizationFilterAttribute Class

Name

Description

OnAuthorizationAsync

This method is overridden to implement the authorization policy.

There is little reason to use the AuthorizationFilterAttribute class because the other built-in class, which I describe in the next section, is simpler to work with; however, for completeness, Listing 24-6 shows how I have reworked the CustomAuthorizationAttribute class so that it is derived from AuthorizationFilterAttribute.

Listing 24-6. Deriving from AuthorizationFilterAttribute in the CustomAuthorizationAttribute.cs File

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Dispatch.Infrastructure {

    public class CustomAuthorizationAttribute : AuthorizationFilterAttribute {
        private string[] roles;

        public CustomAuthorizationAttribute(params string[] rolesList) {
            roles = rolesList;
        }

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

            IPrincipal principal = actionContext.RequestContext.Principal;
            if (principal == null || !roles.Any(role => principal.IsInRole(role))) {
                actionContext.Response =
                    actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
            return Task.FromResult<object>(null);
        }
    }
}

When deriving from the AuthorizationFilterAttribute class, the OnAuthorizationAsync method short-circuits the dispatch process by setting the Response property of the HttpActionContext parameter. For requests that pass the authorization policy, no response is set, and the request continues through the dispatch pipeline.

Using the Authorize Attribute

The reason that there is little reason to use the AuthorizationFilterAttribute class is because Web API includes an Authorize filter that has built-in support for creating a policy to restrict access to specific users and roles without needing any coding at all. The Authorize attribute defines the configuration properties shown in Table 24-6.

Table 24-6. The Properties Defined by the Authorize Attribute

Name

Description

Roles

A comma-separated list of roles that are allowed to access the action method

Users

A comma-separated list of users who are allowed to access the action method

The Authorize attribute works in the same way as the custom attributes I defined and relies on another—unspecified—component in the dispatch process to authenticate the requests. Listing 24-7 shows how I have replaced my custom authorization attribute with the built-in one in the Products controller.

Listing 24-7. Using the Built-in Authorize Attribute in the ProductsController.cs File

...
//[CustomAuthentication]
//[CustomAuthorization("admins")]
[Authorize(Roles="admins")]
public Product Get(int id) {
    return products.Where(x => x.ProductID == id).FirstOrDefault();
}
...

The effect is the same as for my custom filter but without the need to write any custom code.

Image Note  You might be wondering why I have taken the long way around to reach the point where I demonstrate the simplest way to perform authorization checks. There are two reasons. First, the Authorize attribute relies on another component to perform authentication, and I wanted to explain how to achieve this before introducing the Authorize filter. Second, by understanding how authorization works behind the scenes, you are less likely to be caught out when the Authorize attribute doesn’t work the way you expect, which is not uncommon when working with user management systems such as ASP.NET Identity. These systems are complex and often counterintuitive, and the more you understand about how they integrate into Web API, the less likely you are to encounter problems.

Reworking the Authentication Filter

I want to tidy up one loose end before moving on: when the authentication filter that I created in Chapter 23 is applied, authentication will be performed twice, once in the filter and again in message handler. Listing 24-8 shows how I have revised the CustomAuthenticationFilter class so that it only enforces the policy of restricting access to authenticated users and relies on the message handler to perform the authentication.

Listing 24-8. Revising the Authentication Policy in the CustomAuthenticationFilter.cs File

using System;
using System.Net.Http.Headers;
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) {

            if (context.Principal == null
                    || !context.Principal.Identity.IsAuthenticated) {
                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; }
        }
    }
}

The filter no longer has any knowledge of the mechanism used to authenticate users and simply relies on the IPrincipal objects that the message handler from Listing 24-3 associated with requests. Listing 24-9 shows how I have applied the revised filter to the Products controller.

Listing 24-9. Applying the Revised Authentication Filter to the ProductsController.cs File

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

namespace Dispatch.Controllers {

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

        //[CustomAuthentication]
        //[CustomAuthorization("admins")]
        [Authorize(Roles="admins")]
        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;
        }
    }
}

The combined effect of the authentication and authorization filters is that requests from any authenticated user can invoke Post and parameterless Get action methods, but only authenticated users who are assigned the admins role are able to invoke the Get action method that accepts a parameter.

Working with Exception Filters

Exception filters are executed only if, as their name suggests, an exception is thrown by the action method or another filter; they are used to translate that exception into a response that will be sent to the client. As I demonstrate, the default Web API is to treat exceptions as problems within the server, but that isn’t always helpful. Table 24-7 puts exception filters into context.

Table 24-7. Putting Exception Filters in Context

Question

Answer

What are they?

Exception filters are executed when an exception is thrown by the action method or another filter.

When should you use them?

Exception filters are useful for replacing the default response with one that gives the client more information about the problem and what actions may be taken to remedy it.

What do you need to know?

Throwing an HttpResponseException bypasses the exception filters, as I explain in Chapter 25.

Understanding the Default Behavior

Before I get into the detail of exception filters, I need to create a source of exceptions. Listing 24-10 shows the modifications I made to the Products controller so that I can generate exceptions on demand. (I also removed the filters from earlier examples to simplify the code.)

Listing 24-10. Throwing Exceptions in the ProductsController.cs File

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

namespace Dispatch.Controllers {

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

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

        public Product Get(int id) {
            return products[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 commented out several of the statements that define data objects and changed the version of the Get action method that accepts a parameter so that the value of the parameter is treated as an index into the data collection.

To see the effect of the change, start the application, navigate to the /Home/Index URL with the browser, and click the Get One button. The URL that the client requests when the Get One button is clicked is /api/products/2, which is out of the data collection bounds. The overall effect is that the client receives a 500 (Internal Server Error), as shown in Figure 24-2.

9781484200865_Fig24-02.jpg

Figure 24-2. The default error-handling behavior

If you use the F12 developer tools, you will see that the response from the web service is a JSON object that contains details about the exception that was thrown, as follows:

{"Message" : "An error has occurred.",
"ExceptionMessage" : "Index was outside the bounds of the array.",
"ExceptionType":"System.IndexOutOfRangeException",
"StackTrace":"at Dispatch.Controllers.ProductsController.Get(Int32 id) in ..."}

I’ll come back to the JSON object and explain its use in Chapter 25, but for this chapter, the key point to note is that the default behavior when an exception is thrown is to send a 500 (Internal Server Error) response.

Understanding Exception Filters

Treating all exceptions the same by sending a 500 (Internal Server Error) response is a catchall strategy that doesn’t always make sense, especially if you are using code that uses exceptions to signal outcomes that are not server-side problems. In the case of Products controller in the previous section, the client is requesting a data object that doesn’t exist, and returning a 500 (Internal Server Error) response isn’t entirely helpful because it indicates that the request couldn’t be processed because of some problem within the server, rather than a problem with the request. An exception filter can be used to override the default behavior and return a more meaningful and useful response to the client.

Exception filters are derived from the IExceptionFilter interface, which is defined in the System.Web.Http.Filters namespace. Here is the definition:

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

namespace System.Web.Http.Filters {

    public interface IExceptionFilter : IFilter {

        Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext,
            CancellationToken cancellationToken);
    }
}

The ExecuteExceptionFilterAsync method is invoked when an exception has been thrown by the action method or by another filter. Details of the exception are accessed through the Exception property of the HttpActionExecutedContext parameter, which also defines the Response property that is used to set the HttpResponseMessage that will be sent to the client. (I described the HttpActionExecutedContext class in Chapter 23.)

Creating an Exception Filter

Exception filters can create a new response, modify an existing one, or elect to do nothing at all. Listing 24-11 shows the contents of the CustomExceptionAttribute.cs file that I added to the Infrastructure folder and used to define an exception filter that will handle the exception thrown by the Products controller.

Listing 24-11. The Contents of the CustomExceptionAttribute.cs File

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

namespace Dispatch.Infrastructure {
    public class CustomExceptionAttribute : Attribute, IExceptionFilter {

        public Task ExecuteExceptionFilterAsync(HttpActionExecutedContext
            actionExecutedContext, CancellationToken cancellationToken) {

            if (actionExecutedContext.Exception != null
                && actionExecutedContext.Exception is ArgumentOutOfRangeException) {
                    actionExecutedContext.Response =
                        actionExecutedContext.Request.CreateErrorResponse(
                            HttpStatusCode.BadRequest, "No data item");
            }
            return Task.FromResult<object>(null);
        }

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

Unlike the other filter interfaces, there are no continuation functions or even method results required in the ExecuteExceptionFilterAsync method. When the method is called, I check that the HttpActionExecutedContext.Exception property has been set and that it is an instance of the exception that I am interested in: the ArgumentOutOfRangeException class.

I create an HttpResponseMessage using the HttpRequestMessage.CreateError message extension method and use it to set the HttpActionExecutedContext.Response property, which allows me to change the 500 (Internal Server Error) response to a more helpful 401 (Bad Request) response. Listing 24-12 shows how I applied the exception filter to the Products controller.

Listing 24-12. Applying an Exception Filter in the ProductsController.cs File

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

Exception filters are applied just like other filter types and can be used for specific action methods or an entire controller or registered as global filters so that they affect the entire application.

To see the effect, start the application, use the browser to navigate to the /Home/Index URL, and click the Get One button. The new response is shown in Figure 24-3.

9781484200865_Fig24-03.jpg

Figure 24-3. Using an exception filter to change the response

If you use the F12 developer tools, you will see that the string I passed to the CreateErrorResponse method like this:

...
actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
    "No data item");
...

is used to create the JSON object that is included in the following response:

{"Message":"No data item"}

I return to the JSON object, and the broader Web API error handling, in Chapter 25.

Deriving the Filter from the ExceptionFilterAttribute Class

The ExceptionFilterAttribute class can be used as the base for exception filters, although since the IExceptionFilter interface doesn’t require the use of continuation functions, the main benefit is consistency with other types of custom filter. The ExceptionFilterAttribute class defines the method shown in Table 24-8.

Table 24-8. The Method Defined by the ExceptionFilterAttribute Class

Name

Description

OnExceptionAsync

This method is overridden to handle exceptions thrown by the action method or by other filters.

Listing 24-13 shows how I have updated the custom exception filter class so that it is derived from ExceptionFilterAttribute. I have also made the class configurable so that it can be configured to map from exception types to HTTP results when it is applied.

Listing 24-13. Changing the Base Type in the CustomExceptionAttribute.cs File

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

namespace Dispatch.Infrastructure {
    public class CustomExceptionAttribute : ExceptionFilterAttribute {

        public HttpStatusCode StatusCode { get; set; }
        public Type ExceptionType { get; set; }
        public string Message { get; set; }

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

            if (actionExecutedContext.Exception != null
                && actionExecutedContext.Exception.GetType() == ExceptionType) {
                   actionExecutedContext.Response
                        = actionExecutedContext.Request.CreateErrorResponse(StatusCode,
                              Message);
            }
            return Task.FromResult<object>(null);
        }
    }
}

The basic technique in this filter is the same as when I derived directly from the IExceptionFilter interface, except that I have extracted the exception type, the HTTP status code, and the message to be included in the JSON object into properties. Listing 24-14 shows how I have changed the application of the filter to the Products controller to set the property values.

Listing 24-14. Changing the Application of the Exception Filter in the ProductsController.cs File

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

namespace Dispatch.Controllers {

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

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

        [CustomException(ExceptionType=typeof(ArgumentOutOfRangeException),
            StatusCode=HttpStatusCode.BadRequest, Message="No such index")]
        public Product Get(int id) {
            return products[id];
            //return products.Where(x => x.ProductID == id).FirstOrDefault();
        }

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

Image Tip  If you are dealing with a lot of exceptions, then consider using the global error handling feature, which I describe in Chapter 25.

Working with Override Filters

In Chapter 23, I explained that filters have scope. This is a useful feature that means you don’t have to apply a filter to every single action method on which you want to apply. I also demonstrated how to create global filters that are applied to all the action methods in the application.

A filter override allows you to disable one or more filters for an action method. This allows you to still benefit from controller and global scopes but selectively disable filters to create different behaviors for specific action methods. Table 24-9 puts overriding filters in context.

Table 24-9. Putting Overriding Filters in Context

Question

Answer

What are they?

Override filters disable higher-scoped filters of a given type.

When should you use them?

Use an override when you want to vary the filter pipeline for a single action method so that controller-level and global filters won’t be executed.

What do you need to know?

Override filters do not affect filters applied at the same scope, as demonstrated in the “Redefining Filter Policies” section.

Overriding Built-in Filter Types

Override filters implement the IOverrideFilter interface, which is defined in the System.Web.Http.Filters namespace. Here is the interface:

namespace System.Web.Http.Filters {

    public interface IOverrideFilter : IFilter {

        Type FiltersToOverride { get; }
    }
}

The FiltersToOverride propertyreturns the type of filter that is to be overridden. To apply an override, use one of the built-in filter classes that I have shown in Table 24-10. Each override filter attribute affects one type of filter.

Table 24-10. The Built-in Override Filter Attributes

Name

Description

OverrideAuthenticationFilters

Prevents authentication filters from being executed

OverrideAuthorizationFilters

Prevents authorization filters from being executed

OverrideActionFilters

Prevents action filters from being executed

OverrideExceptionFilters

Prevents exception filters from being executed

Image Tip  Unlike the other filter types, there is no benefit in creating a custom override filter. This is because the class that ApiController uses to handle overrides (the FilterGrouping class) checks only for each filter type and not the types they are derived from. This means it is possible to override filters that implement the IExceptionFilter interface, for example, but not IFilter. The built-in override filter classes shown in the table encapsulate the complete range of functionality that the IOverrideFilter interface can be used for.

Listing 24-15 shows how I applied an authorization filter to the Products controller so that it applies to all of the action methods and then applies the OverrideAuthorizationFilters attribute to disable authorization for one of them. (The authentication for requests is still being handled by the HTTP Basic authentication message handler that I created earlier in the chapter.)

Listing 24-15. Overriding Controller-wide Authorization in the ProductsController.cs File

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

namespace Dispatch.Controllers {

    [Authorize(Roles="admins")]
    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 },
            };

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

        [CustomException(ExceptionType=typeof(ArgumentOutOfRangeException),
            StatusCode=HttpStatusCode.BadRequest, Message="No such index")]
        public Product Get(int id) {
            return products[id];
            //return products.Where(x => x.ProductID == id).FirstOrDefault();
        }

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

The effect of the Authorize attribute is to restrict all of the action methods in the Products controller so they can be accessed only by authenticated users who have been assigned to the admins role.

The effect of applying the OverrideAuthorization attribute to the parameterless version of the Get action method is to prevent execution of all authorization filters for that action method, which means that any request is able to invoke the action.

Redefining Filter Policies

The clever part of the override filters is that they affect filters only at the previous scope, which means you can apply attributes of the overridden type at the same level as the override, and they will be executed. As an example, Listing 24-16 shows how I have applied the Authorize attribute alongside the OverrideAuthorization attribute in the Products controller.

Listing 24-16. Redefining Authorization in the ProductsController.cs File

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

namespace Dispatch.Controllers {

    [Authorize(Roles="admins")]
    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 },
            };

        [OverrideAuthorization]
        [Authorize(Roles="users")]
        public IEnumerable<Product> Get() {
            return products;
        }

        [CustomException(ExceptionType=typeof(ArgumentOutOfRangeException),
            StatusCode=HttpStatusCode.BadRequest, Message="No such index")]
        public Product Get(int id) {
            return products[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 Authorize filter to the Get method, specifying that only authenticated users who have been assigned to the users role are allowed to invoke the action method. Without the OverrideAuthorization attribute, the filter pipeline would contain both Authorize attributes, and they would be executed one after the other, creating a combined effect of restricting access to those users who have been assigned to both the admins and users roles.

But with the OverrideAuthorization, the controller-scoped Authorize attribute is removed from the pipeline, meaning that only the Authorize filter applied directly to the action method will be used: the effect is to restrict access to the users role.

Summary

In this chapter, I finished describing the Web API support for filters by explaining how authentication and exception filters work. I also demonstrated how override filters can be used to prevent the execution of filters that have been applied at the global and controller scopes, allowing filtering to be disabled or redefined for an action method.

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

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