The Exception property returns any exception that the action method has thrown, and the

Một phần của tài liệu Giáo trình lập trình ASP.NET Apress pro ASP NET MVC3 framework pre release (Trang 421 - 451)

WARNING: WRITING SECURITY CODE IS DANGEROUS

8. The Exception property returns any exception that the action method has thrown, and the

Table 8. The ActionExecutedContext properties

Name Type Description

ActionDescriptor ActionDescriptor Provides details of the action method

Canceled Bool Returns true if the action has been cancelled by another filter Exception Exception Returns an exception thrown by another filter or by the action

method

ExceptionHandled Bool Returns true if the exception has been handled

Result ActionResult The result for the action method – a filter can cancel the request by setting this property to a non-null value

The Canceled property will return true if another filter has cancelled the request by setting a value for the Result property since the OnActionExecuting of our filter was invoked.

Our OnActionExecuted method is still called – but only so we can tidy up and release any resources we were using.

Implementing a Result Filter

Action filters and result filters have a lot in common. Result filters are to action results what action filters are to action methods. Results filters implement the IResultFilter interface, which is shown in Listing 18.

Listing 18. The IResultFilter interface namespace System.Web.Mvc { public interface IResultFilter {

void OnResultExecuting(ResultExecutingContext filterContext);

void OnResultExecuted(ResultExecutedContext filterContext);

} }

In Chapter 12, we explained how action methods return action results, which allows us to separate out the intent of an action method from its execution. When we apply a result filter to an action method, the OnResultExecuting method is invoked once the action method has returned an action result, but before the action result is executed. The OnResultExecuted method is invoked after the action result is executed.

The parameters to these methods are ResultExecutingContext and

ResultExecutedContext objects respectively, and they are very similar to their action filter counterparts. They define the same properties, which have the same effect. Listing 19 shows a simple result filter.

Listing 19. A simple result filter using System.Diagnostics;

using System.Web.Mvc;

namespace MvcFilters.Infrastructure.Filters {

public class ProfileResultAttribute : FilterAttribute, IResultFilter { private Stopwatch timer;

public void OnResultExecuting(ResultExecutingContext filterContext) { timer = Stopwatch.StartNew();

}

public void OnResultExecuted(ResultExecutedContext filterContext) { timer.Stop();

filterContext.HttpContext.Response.Write(

string.Format("Result execution - elapsed time: {0}", timer.Elapsed.TotalSeconds));

} }

}

This filter measures the amount of time taken to execute the result. If we attach the filter to an action method, like this:

...

[ProfileResult]

public ActionResult Index() { return View();

} ...

and then navigate to the action method, we see the output shown in Figure 3. Notice that the performance information from this filter appears at the bottom of the page – this is because we have written our message after the action result has been executed – i.e. after the view has been rendered. Our last filter write to the response before the action result was executed, which is why the message appeared at the top of the page.

Figure 3. Output from a result filter

Using the Built-In Action and Result Filter Class

The MVC framework includes a built-in class that can be used to create both action and result filters – but unlike the built-in authorization and exception filters, it doesn’t provide any useful features. The class is called ActionFilterAttribute and is shown in Listing 20.

Listing 20. The ActionFilterAttribute class

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter{

public virtual void OnActionExecuting(ActionExecutingContext filterContext) { }

public virtual void OnActionExecuted(ActionExecutedContext filterContext) { }

public virtual void OnResultExecuting(ResultExecutingContext filterContext) {

}

public virtual void OnResultExecuted(ResultExecutedContext filterContext) { }

} }

The only benefit to using this class is that we don’t have to implement the methods that we don’t intend to use. For completeness, Listing 21 shows a filter derived from

ActionFilterAttribute which combines our performance measurements for both the action method and the action result being executed.

Listing 21. Using the ActionFilterAttribute class using System.Diagnostics;

using System.Web.Mvc;

namespace MvcFilters.Infrastructure.Filters {

public class ProfileAllAttribute : ActionFilterAttribute { private Stopwatch timer;

public override void OnActionExecuting(ActionExecutingContext filterContext) { timer = Stopwatch.StartNew();

}

public override void OnActionExecuted(ActionExecutedContext filterContext) { timer.Stop();

filterContext.HttpContext.Response.Write(

string.Format("Action method elapsed time: {0}", timer.Elapsed.TotalSeconds));

}

public override void OnResultExecuting(ResultExecutingContext filterContext) { timer = Stopwatch.StartNew();

}

public override void OnResultExecuted(ResultExecutedContext filterContext) { timer.Stop();

filterContext.HttpContext.Response.Write(

string.Format("Action result elapsed time: {0}", timer.Elapsed.TotalSeconds));

} } }

The ActionFilterAttribute class implements the IActionFilter and IResultFilter interfaces, which means that the MVC framework will treat derived classes as both types of

filter, even if not all of the methods are overridden. If we apply the filter in Listing 21 to our action method, we see the output shown in Figure 4.

Figure 4. The output from a combined action/result filter

Other Filter Features

The previous examples have given you all the information you need to work effectively with filters – but there are some other features available that are interesting, but not as widely used.

In the following sections, we’ll show you some of the advanced MVC framework filtering capabilities.

Filtering without Attributes

The normal way of using filters is to create and use attributes, as we have demonstrated in the previous sections – however, there is an alternative. The Controller class implements the IAuthorizationFilter, IActionFilter, IResultFilter and IExceptionFilter interfaces and provides empty virtual implementations of each of the OnXXX methods we have seen in the previous sections – such as OnAuthorization, OnException, etc. Listing 22 shows a controller that measures its own performance.

Listing 22. Using the Controller filter methods using System.Diagnostics;

using System.Web.Mvc;

namespace MvcFilters.Controllers {

public class SampleController : Controller { private Stopwatch timer;

public ActionResult Index() { return View();

}

protected override void OnActionExecuting(ActionExecutingContext filterContext) { timer = Stopwatch.StartNew();

}

protected override void OnActionExecuted(ActionExecutedContext filterContext) { timer.Stop();

filterContext.HttpContext.Response.Write(

string.Format("Action method elapsed time: {0}", timer.Elapsed.TotalSeconds));

}

protected override void OnResultExecuting(ResultExecutingContext filterContext) { timer = Stopwatch.StartNew();

}

protected override void OnResultExecuted(ResultExecutedContext filterContext) { timer.Stop();

filterContext.HttpContext.Response.Write(

string.Format("Action result elapsed time: {0}", timer.Elapsed.TotalSeconds));

} } }

This technique is most useful when you are creating a base class from which multiple controllers in your project are derived. The whole point of filtering is to put code that is required across the application in one reusable location, so using these methods in a controller that will not be derived from doesn’t make much sense.

Tip We prefer to use attributes – we like the separation between the controller logic and the filter logic. If you are looking for a way to apply a filter to all of your controllers, then see the section on global filters below.

Using Global Filters

Global filters are applied to all of the action methods in your application. We make a regular filter into a global filter through the RegisterGlobalFilters method in Global.asax. Listing 23 shows how to make the ProfileAll filter we created in Listing 21 into a global filter.

Listing 23. Creating a global filter

public class MvcApplication : System.Web.HttpApplication {

public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute());

filters.Add(new ProfileAllAttribute());

} ...

The RegisterGlobalFilters method is called from the Application_Start method, which ensures that our filters are registered when our MVC application starts – the

RegisterGlobalFilters method and the call from Application_Start are set up for us when Visual Studio creates the MVC project. The parameter to the RegisterGlobalFilters method is a GlobalFilterCollection – we register global filters using the Add method, like this:

filters.Add(new ProfileAllAttribute());

Notice that we must refer to the filter using the full class name (ProfileAllAttribute) rather than the short name used when we apply the filter as an attribute (ProfileAll). Once we have registered a filter like this, it will apply to every action method.

Tip The first statement in the default RegisterGlobalFilters method created by Visual Studio sets up the default MVC exception handling policy – this will render the /Views/Shared/Error.cshtml view when an unhandled exception arises. This exception handling policy is disabled by default for development – see the note in the section on exception filters for details of enabling it.

The format for specifying property values for a filter is the same as for regular classes, as shown in Listing 24 which creates a global exception filter.

Listing 24. Creating a global filter that requires properties

public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute());

filters.Add(new ProfileAllAttribute());

filters.Add(new HandleErrorAttribute() {

ExceptionType = typeof(NullReferenceException), View = "SpecialError"

});

}

Ordering Filter Execution

We have already explained that filters are executed by type – the sequence is: authorization filters, action filters, and then result filters. and thenThe framework executes your exception filters at any stage if there is an unhandled exception. However, within each type category, we can take control of the order in which individual filters are used. Listing 25 contains a simple action filter that we will use to demonstrate ordering filter execution.

Listing 25. A simple action filter using System;

using System.Web.Mvc;

namespace MvcFilters.Infrastructure.Filters {

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true)]

public class SimpleMessageAttribute : FilterAttribute, IActionFilter { public string Message { get; set; }

public void OnActionExecuting(ActionExecutingContext filterContext) { filterContext.HttpContext.Response.Write(

string.Format("[Before Action: {0}]", Message));

}

public void OnActionExecuted(ActionExecutedContext filterContext) { filterContext.HttpContext.Response.Write(

string.Format("[After Action: {0}]", Message));

} } }

This filter writes a message to the response when the OnXXX methods are invoked. We can specify part of the message using the Message parameter. We can apply multiple instances of this filter to an action method, as shown in Listing 26 (notice that we have set the

AllowMultiple property in the AttributeUsage attribute to be true).

Listing 26. Applying multiple filters to an action ...

[SimpleMessage(Message="A")]

[SimpleMessage(Message="B")]

public ActionResult Index() {

Response.Write("Action method is running");

return View();

}

...

We have created two filters with different messages – the first has a message of A and the other a message of B. We could have used two different filters, but this approach allows us to create a simpler example. When we run the application and navigate to a URL that invoked the action method, we see the output in Figure 5.

Figure 5. Multiple filters on the same action method

When we ran this example, the MVC framework executed the A filter before the B filter. It could as easily been the other way around – the MVC framework doesn’t guarantee any particular order or execution. Most of the time the order doesn’t matter – but when it does, we can use the Order property, as shown in Listing 27.

Listing 27. Using the Order property in a filter ...

[SimpleMessage(Message="A", Order=2)]

[SimpleMessage(Message="B", Order=1)]

public ActionResult Index() {

Response.Write("Action method is running");

return View();

} ...

The Order parameter takes an int value, and the MVC framework executes the filters in ascending order. In the listing, we have given the B filter the lowest value, so the framework executes it first, as shown by Figure 6.

Figure 6. Specifying the order of filter execution

Tip Notice that the OnActionExecuting methods are executed in the order we specified but the OnActionExecuted methods are executed in the reverse order. The MVC framework builds up a stack of filters as it executes them before the action method, and then unwinds the stack afterwards. This

unwinding behavior cannot be changed.

If we don’t specify a value for the Order property, then it is assigned a default value of -1. This means that if we mix filters so that some have Order values and others don’t the ones without will be executed first, since they have the lowest Order value.

If multiple filters of the same type (say, action filters) have the same Order value (say 1), then the MVC framework determines the execution order based on where the filter has been applied. Global filters are executed first, then filters applied to the controller class, and then filters applied to the action method.

Tip There are two other categories, which we will explain later in the chapter, when we demonstrate how to create filters using a filter provider. The filters in the First category are executed before all others, and the filters in the Last category are executed after all others.

As a demonstration, we have created a global filter in Global.asax, as shown in Listing 28.

Listing 28. Defining a global filter with an Order value

public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute());

filters.Add(new SimpleMessageAttribute() { Message = "Global", Order = 1 });

}

You can see that we are able to specify a value for Order using the standard property initialization syntax. We have also defined filters on the controller class and the action method, as shown in Listing 29. All three of these filters have the same Order value.

Listing 29. Defining ordered filters at the controller and action levels ...

[SimpleMessage(Message="Controller", Order=1)]

public class ExampleController : Controller { [SimpleMessage(Message="Action", Order=1)]

public ActionResult Index() {

Response.Write("Action method is running");

return View();

} } ...

When we run the application and navigate to the action method, we see the output in Figure 7.

Figure 7. The sequence of filter execution

Note The order of execution is reversed for exception filters. If exception filters are applied with the same Order value to the controller and to the action method, the filter on the action method will be executed first. Global exception filters with the same Order value are executed last.

Using Filter Providers

So far you’ve seen how to apply a filter to an individual action, to all actions on a controller, or to your entire application as a global filter. This covers most common scenarios, but in truly advanced cases you might want to control filter application programmatically at runtime.

To handle this, the MVC framework has a notion of We like the global filters feature, but we tend not to use it very often - there just aren’t that many things we need to do universally. We prefer the more granular approach of using filter providers, which gives us you control over how filters are applied without using attributes. (Although, to be clear – mostly we use attributes – they are clean and simple). Filter providers implement an interface called IFilterProvider, which is shown in Listing 30.

Listing 30. The IFilterProvider class

31 namespace System.Web.Mvc {

using System.Collections.Generic;

public interface IFilterProvider {

IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);

} }

If we implement this interface and register our implementing with the MVC framework, then our GetFilters method will be called as the MVC framework prepares to invoke an action method. This is our opportunity to provide the MVC framework with filters that will be executed as though we had used attributes.

We don’t just return a list of filters – things are a little more indirect than that – we have to return an enumeration of Filter objects. The Filter class is shown in Listing 26.

Listing 26. The Filter class namespace System.Web.Mvc {

using System.Diagnostics.CodeAnalysis;

public class Filter {

public const int DefaultOrder = -1;

public Filter(object instance, FilterScope scope, int? order) { // ...statements removed for clarity...

}

public object Instance { get; protected set; } public FilterScope Scope { get; protected set;}

public int Order { get; protected set; } }

}

The Filter class is a wrapper around a filter object, which is accessed by the Instance property – the MVC framework uses this property to get hold of the actual filter to execute its OnXXX methods.

The MVC framework is very particular about the order in which it executed filters and the Scope and Order properties give the MVC framework information about the priority of the filter that can be compared against other filters to order their execution. We explained the Order property earlier in the chapter.

The Scope property tells the MVC framework where it should rank the filter when it comes to execution order. The FilterScope enumeration contains values for First, Global, Controller, Action and Last. The Global, Controller and Action values allow our wrapped filter

to be compared against global filters and those applied as attributes to the controller class and action method. The First and Last values allow you to specify that a filter should be executed before or after others with the same Order value.

To demonstrate the filter provider feature, we are going to create a provider that allows policy based filter selection. To start with, we have sub-classed Filter to create a more complex filter wrapper class – this is called CustomFilterWrapper and is shown in Listing 27.

Listing 27. The custom filter wrapper public class CustomFilterWrapper : Filter {

public CustomFilterWrapper(object instance, FilterScope scope, int? order , Func<ControllerContext, ActionDescriptor, bool> selector)

: base(instance, scope, order) { Selector = selector;

}

public Func<ControllerContext, ActionDescriptor, bool> Selector { get;

set;

} }

The addition is a delegate which takes the same parameter types that are passed to the IFilterProvider.GetFilters method. We will use the delegate to express our policy –

CustomFilterWrapper objects can use the ControllerContext and ActionDescriptor to inspect details of the action method that is about to be invoked and decide if they should be used to filter the request - a result of true from the delegate will indicate that the filter should be applied. As an example, we can create a CustomFilterWrapper object like this:

new CustomFilterWrapper(new ProfileAllAttribute(), FilterScope.Action, 1, (c, a) => a.ActionName == "Index");

The filter object is a new instance of the ProfileAllAttribute filter. We have set the scope to be equivalent to the filter being applied to an action method and the Order value is defined as 1. The delegate is expressed as a lambda method, which returns true if the name of the action method that is about to be invoked is Index.

We have also created the CustomFilterProvider class, which implements the IFilterProvider interface and which operated on CustomFilterWrapper objects – this class is shown in Listing 28.

Listing 28. The CustomFilterProvider class public class CustomFilterProvider : IFilterProvider { private IList<CustomFilterWrapper> wrappers;

public CustomFilterProvider() {

wrappers = new List<CustomFilterWrapper>();

}

public IList<CustomFilterWrapper> Wrappers { get { return wrappers; }

}

public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {

return wrappers.Where(e => e.Selector(controllerContext, actionDescriptor));

} }

We maintain a collection of wrapper objects and when the GetFilters method is called, we use LINQ to select only those wrappers whose delegate returns true – this allows individual wrappers to have arbitrarily complex policies determining when they are applied. The last step is to register our filter provider with the MVC application and populate it with wrappers that contain filters – this is done in the Application_Start method in Global.asax, as shown in Listing 29.

Listing 29. Registering and populating the filter provider protected void Application_Start() {

AreaRegistration.RegisterAllAreas();

CustomFilterProvider customFilterProvider = new CustomFilterProvider();

customFilterProvider.Wrappers.Add(new CustomFilterWrapper(new ProfileAllAttribute(), FilterScope.Action, 1, (c, a) => a.ActionName == "Index"));

FilterProviders.Providers.Add(customFilterProvider);

RegisterGlobalFilters(GlobalFilters.Filters);

RegisterRoutes(RouteTable.Routes);

}

We register filter providers using the static FilterProviders.Providers property, which returns the collection of available providers. The Add method lets us register our provider so that it will be used each time an action method is about to be invoked.

Using Property Dependency Injection in Filters

Sometimes we might want to use our DI infrastructure to supply dependencies or configuration parameters to our filter attribute instances. Unfortunately we can’t use constructor We can’t use dependency injection in for filters which that are expressed as .NET attributes – this is because

Một phần của tài liệu Giáo trình lập trình ASP.NET Apress pro ASP NET MVC3 framework pre release (Trang 421 - 451)

Tải bản đầy đủ (PDF)

(603 trang)