[MVC]Asp.net MVC :Custom Action Filters

来自:http://quickstarts.ASP.NET/previews/mvc/mvc_ActionFiltering.htm

Custom Action Filters

Introduction

The possible uses for action filters are as varied as the actions to which they can be applied. Some possible uses for action filters include the following:

  • Logging to track user interactions.
  • "Anti-image-leeching" to prevent images from being loaded in pages that are not on your site.
  • Web crawler filtering to change application behavior based on the browser user agent.
  • Localization to set the locale.
  • Dynamic actions to inject an action into a controller.

Implementing a Custom Action Filter

An action filter is implemented as an attribute class that inherits from ActionFilterAttribute. ActionFilterAttribute is an abstract class that has four virtual methods that you can override: OnActionExecuting, OnActionExecuted, OnResultExecuting, and OnResultExecuted. You must override at least one of these methods.
The ASP.NET MVC framework will call the OnActionExecuting method of your action filter before it calls any action method that is marked with your action filter attribute. Similarly, the framework will call the OnActionExecuted method after the action method has finished.
The OnResultExecuting method is called just before the ActionResult instance that is returned by your action is invoked. The OnResultExecuted method is called just after the result is executed. These methods are useful for performing actions such as logging, output caching, and so forth.
The following example shows a simple action filter that logs trace messages before and after an action method is called.

Visual Basic

Public Class LoggingFilterAttribute
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuting(ByVal filterContext _
As ActionExecutingContext)
filterContext.HttpContext.Trace.Write("Starting: " & _
filterContext.ActionMethod.Name)
MyBase.OnActionExecuting(filterContext)
End Sub
Public Overrides Sub OnActionExecuted(ByVal filterContext _
As ActionExecutedContext)
If Not filterContext.Exception Is Nothing Then
filterContext.HttpContext.Trace.Write("Exception thrown")
End If
MyBase.OnActionExecuted(filterContext)
End Sub
End Class
C#

public class LoggingFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext
filterContext)
{
filterContext.HttpContext.Trace.Write("Starting: " +
filterContext.ActionMethod.Name);
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext
filterContext)
{
if (filterContext.Exception != null)
filterContext.HttpContext.Trace.Write("Exception thrown");
base.OnActionExecuted(filterContext);
}
}

Action Filter Context

Each of the controller event handlers for action filtering takes a context object as a parameter. The following list shows the event handlers and the context type that each takes.

  • OnActionExecuting takes an ActionExecutingContext object.
  • OnActionExecuted takes an ActionExecutedContext object.
  • OnResultExecuting takes a ResultExecutingContext object.
  • OnResultExecuted takes a ResultExecutedContext object.

All context classes inherit from the ControllerContext class and include an ActionMethod property. You can use the ActionMethod property to identify which action the filter is currently applied to.
The ActionExecutingContext and ResultExecutingContext classes contain a Cancel property that enables you to cancel the action.
The ActionExecutedContent and ResultExecutedContext classes contain an Exception property and an ExceptionHandled property. If the Exception property is null, it indicates that no error occurred when the action method ran. If the Exception property is not null and the filter knows how to handle the exception, the filter can handle the exception and then signal that it has done so by setting the ExceptionHandled property to true. Even if the ExceptionHandled property is true, the OnActionExecuted or OnResultExecuted method of any additional action filters in the stack will be called and exception information will be passed to them. This enables scenarios such as letting a logging filter log an exception even though the exception has been handled. In general, an action filter should not handle an exception unless the error is specific to that filter.

Marking an Action Method with a Filter Attribute

You can apply an action filter to as many action methods as you need. The following example shows a controller that contains action methods that are marked with an action-filter attribute. In this case, all the action methods in the controller will invoke the same action filter.

Visual Basic

Public Class HomeController
Inherits Controller
Shared clicked As Boolean = False
<LoggingFilter()> _
Public Function Index()
ViewData("Title") = "Home Page"
ViewData("Message") = "Welcome to ASP.NET MVC!"
If clicked = True Then
ViewData("Click") = "You clicked this button."
clicked = False
End If
Return View()
End Function
<LoggingFilter()> _
Public Function About()
ViewData("Title") = "About Page"
Return View()
End Function
<LoggingFilter()> _
Public Function ClickMe()
clicked = True
Return RedirectToAction("Index")
End Function
End Class
C#

public class HomeController : Controller
{
static Boolean clicked = false;
[LoggingFilter]
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
if (clicked == true)
{
ViewData["Click"] = "You clicked this button.";
clicked = false;
}
return View();
}
[LoggingFilter]
public ActionResult About()
{
ViewData["Title"] = "About Page";
return View();
}
[LoggingFilter]
public ActionResult ClickMe()
{
clicked = true;
return RedirectToAction("Index");
}
}
Note:
If you want to apply an action filter to all action methods of a controller, you can mark the controller with the filter attribute. This is the same as marking every action method with the attribute.

Executing Code Before and After an Action from Within a Controller

The ASP.NET MVC Controller class defines OnActionExecuting and OnActionExecuted methods that you can override. When you override one or both of these methods, your logic will execute before or after all action methods of that controller. This functionality is like action filters, but the methods are controller-scoped.
The following example shows controller-level OnActionExecuting and OnActionExecuted methods that apply to all action methods in the controller.

Visual Basic

Public Class HomeController
Inherits Controller
Shared clicked As Boolean = False
Public Function Index()
ViewData("Title") = "Home Page"
ViewData("Message") = "Welcome to ASP.NET MVC!"
If clicked = True Then
ViewData("Click") = "You clicked this button."
clicked = False
End If
Return View()
End Function
Public Function About()
ViewData("Title") = "About Page"
Return View()
End Function
Public Function ClickMe()
clicked = True
Return RedirectToAction("Index")
End Function
Protected Overrides Sub OnActionExecuting(ByVal filterContext _
As ActionExecutingContext)
filterContext.HttpContext.Trace.Write("Action Executing: " & _
filterContext.ActionMethod.Name)
If clicked = True Then
filterContext.HttpContext.Trace.Write("Button clickced")
End If
MyBase.OnActionExecuting(filterContext)
End Sub
Protected Overrides Sub OnActionExecuted(ByVal filterContext _
As ActionExecutedContext)
If Not filterContext.Exception Is Nothing Then
filterContext.HttpContext.Trace.Write("Exception thrown")
End If
MyBase.OnActionExecuted(filterContext)
End Sub
End Class
C#

public class HomeController : Controller
{
static Boolean clicked = false;
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
if (clicked == true)
{
ViewData["Click"] = "You clicked this button.";
clicked = false;
}
return View();
}
public ActionResult About()
{
ViewData["Title"] = "About Page";
return View();
}
public ActionResult ClickMe()
{
clicked = true;
return RedirectToAction("Index");
}
protected override void OnActionExecuting(ActionExecutingContext
filterContext)
{
filterContext.HttpContext.Trace.Write("Action Executing: " +
filterContext.ActionMethod.Name);
if (clicked == true)
filterContext.HttpContext.Trace.Write("Button clickced");
base.OnActionExecuting(filterContext);
}
protected override void OnActionExecuted(ActionExecutedContext
filterContext)
{
if (filterContext.Exception != null)
filterContext.HttpContext.Trace.Write("Exception thrown");
base.OnActionExecuted(filterContext);
}
}

Scope of Action Filters

In addition to marking individual action methods with an action filter, you can mark a controller class as a whole with an action filter. In that case, the filter applies to all action methods of that controller.
Additionally, if your controller derives from another controller, the base controller might have its own action-filter attributes. Likewise, if your controller overrides an action method from a base controller, the method might have its own action-filter attributes and those it inherits from the overridden action method.
To make it easier to understand how action filters work together, action methods are grouped into scopes. A scope defines where the attribute applies, such as whether it marks a class or a method, and whether it marks a base class or a derived class.

Order of Execution for Action Filters

Each action filter has an Order property, which is used to determine the order that filters are executed in the scope of the filter. The Order property takes an integer value that must be 0 (default) or greater (with one exception). Omitting the Order property gives the filter an order value of -1, which indicates an unspecified order. Any action filters in a scope whose Order property is set to -1 will be executed in a nondeterministic order, but before the filters that have a specified order.
When an Order property of a filter is specified, it must be set to a unique value in a scope. If two or more action filters in a scope have the same Order property value, an exception is thrown.
Action filters are executed in an order that meets the following constraints. If more than one ordering fulfills all these constraints, the selected ordering is undetermined.
1. The implementation of the OnActionExecuting and OnActionExecuted methods on the controller always go first. For more information, see Executing Code Before and After an Action from Within a Controller.
2. Unless the Order property explicitly set, an action filter has an implied order of -1.
3. If the order property of multiple action filters are explicitely set, the one with the lowest value executes before those with greater values, as shown in the following example.

Visual Basic

<Filter1(Order = 2)> _
<Filter2(Order = 3)> _
<Filter3(Order = 1)> _
Public Sub Index()
View("Index")
End Sub
C#

[Filter1(Order = 2)]
[Filter2(Order = 3)]
[Filter3(Order = 1)]
public void Index()
{
View("Index");
}
In this example, action filters would execute in the following order: Filter3, Filter1, and then Filter2.

4. If two action filters have the same Order property value, and if one action filter is defined on a type and the other action filter is defined on a method, the action filter defined on the type executes first.

Visual Basic

<FilterType(Order = 1)> _
Public Class MyController
<FilterMethod(Order = 1)> _
Public  Sub Index()
View("Index")
End Sub
End Class
C#

[FilterType(Order = 1)]
public class MyController
{
[FilterMethod(Order = 1)]
public void Index()
{
View("Index");
}
}

In this example, FilterType executes before FilterMethod.

Example of Action Filter order of Execution

The following example shows an MVC application that contains two action filters. The DebugFilter filter writes trace messages, and the ThrowExceptionFilter filter causes an exception. The application also contains a base controller, a derived controller, and a view.
The following example shows the class that defines the DebugFilter filter. The filter uses some static methods and properties to trace filter depth and to control indentation of the output.

C#

public class DebugFilterAttribute : ActionFilterAttribute
{
public static void Reset()
{
counter = 1;
indent = 0;
incrementIndex = true;
}
public static int Counter
{
get
{
return counter++;
}
}
static int counter
{
get
{
return (int)(HttpContext.Current.Items["counter"] ?? 1);
}
set
{
HttpContext.Current.Items["counter"] = value;
}
}
static int indent
{
get
{
return (int)(HttpContext.Current.Items["indent"] ?? 1);
}
set
{
HttpContext.Current.Items["indent"] = value;
}
}
static bool incrementIndex
{
get
{
return (bool)(HttpContext.Current.Items["incrementIndex"] ?? true);
}
set
{
HttpContext.Current.Items["incrementIndex"] = value;
}
}
public string Message { get; set; }
public int Id { get; set; }
public override void OnActionExecuting(ActionExecutingContext
filterContext)
{
HttpContext.Current.Trace.Write(
"Action Filter",
string.Format("{0}: {3}(PRE) DebugFilter.OnActionExecuting - order={1} Message='{2}'",
Counter,
this.Order,
this.Message,
Indent)
);
}
public override void OnActionExecuted(ActionExecutedContext
filterContext)
{
HttpContext.Current.Trace.Write(
"Action Filter",
string.Format("{0}: {3}(POST) DebugFilter.OnActionExecuted - order={1} Message='{2}'",
Counter,
this.Order,
this.Message,
Indent)
);
}
public static string Indent
{
get
{
indent += (incrementIndex ? 1 : -1);
string indentText = string.Empty;
for (int i = 0; i < indent; i++)
indentText += " ";
return indentText;
}
}
public static void StartDecrement()
{
incrementIndex = false;
}
}

The following example shows a class that defines the base controller with action filters applied.

C#

[DebugFilter(Message = "(CONTROLLER) MyBaseController", order = 1)]
[DebugFilter(Message = "(CONTROLLER) MyBaseController", order=2)]
public class MyBaseController : Controller
{
[DebugFilter(Message = "(ACTION) MyBaseController.Index()", order=2)]
[DebugFilter(Message = "(ACTION) MyBaseController.Index()", order=1)]
public virtual ActionResult Index()
{
return View();
}
}

The following example shows a class that defines the derived controller with action filters applied. Notice that this controller implements its OnActionExecuting and OnActionExecuted methods.

C#

[DebugFilter(Message = "(CONTROLLER) MyDerivedController", order = 2)]
[DebugFilter(Message = "(CONTROLLER) MyDerivedController", order=1)]
public class MyDerivedController : MyBaseController
{
[DebugFilter(Message = "(ACTION) MyDerivedController.Index()", order=1)]
[DebugFilter(Message = "(ACTION) MyDerivedController.Index()", order = 2)]
// Uncomment the following line to cause an exception to be
// thrown and to see what happens in the trace messages.
//[ThrowExceptionFilter(Message = "(ACTION) Exception thrown!", order=3)]
public override ActionResult Index()
{
base.Index();
HttpContext.Trace.Write("Action Filter",
string.Format("{0}: {1}(ACTION) MyDerivedController.Index()",
DebugFilterAttribute.Counter,
DebugFilterAttribute.Indent));
DebugFilterAttribute.StartDecrement();
return View();
}
protected override void OnActionExecuting(ActionExecutingContext
filterContext)
{
// Processing begins here.
DebugFilterAttribute.Reset();
HttpContext.Trace.Write("Action Filter",
string.Format("{0}: {1}(PRE) MyDerviedController.OnActionExecuting VIRTUAL METHOD",
DebugFilterAttribute.Counter,
DebugFilterAttribute.Indent));
}
protected override void OnActionExecuted(ActionExecutedContext
filterContext)
{
HttpContext.Trace.Write("Action Filter",
string.Format("{0}: {1}(POST) MyDerviedController.OnActionExecuted VIRTUAL METHOD",
DebugFilterAttribute.Counter,
DebugFilterAttribute.Indent));
if (filterContext.Exception != null)
filterContext.ExceptionHandled = true;
// Processing ends here.
((MyDerivedController)filterContext.Controller).View("Index");
}
}

The following example shows the Index view for the application whose Trace property is set to true.

HTML

<%@ Page Language="C#" CodeBehind="Index.aspx.cs"
Inherits="ActionFilterTests.Views.MyDerived.Index" Trace="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Attribute Filter Test</title>
</head>
<body>
<form id="form1" runat="server">
<div>
Hello World
</div>
</form>
</body>
</html>

The following example shows the output trace messages that are displayed when this application runs.

(PRE) MyDerviedController.OnActionExecuting VIRTUAL METHOD
(PRE) DebugFilter.OnActionExecuting - order=1 Message='(CONTROLLER) MyBaseController
(PRE) DebugFilter.OnActionExecuting - order=2 Message='(CONTROLLER) MyBaseController
(PRE) DebugFilter.OnActionExecuting - order=1 Message='(CONTROLLER) MyDerivedController
(PRE) DebugFilter.OnActionExecuting - order=2 Message='(CONTROLLER) MyDerivedController
(PRE) DebugFilter.OnActionExecuting - order=1 Message='(ACTION) MyBaseController.Index()
(PRE) DebugFilter.OnActionExecuting - order=2 Message='(ACTION) MyBaseController.Index()
(PRE) DebugFilter.OnActionExecuting - order=1 Message='(ACTION) MyDerivedController.Index()
(PRE) DebugFilter.OnActionExecuting - order=2 Message='(ACTION) MyDerivedController.Index()
(ACTION) MyDerivedController.Index() 0.010142769541939 0.008739
(POST) DebugFilter.OnActionExecuted - order=2 Message='(ACTION) MyDerivedController.Index()
(POST) DebugFilter.OnActionExecuted - order=1 Message='(ACTION) MyDerivedController.Index()
(POST) DebugFilter.OnActionExecuted - order=2 Message='(ACTION) MyBaseController.Index()
(POST) DebugFilter.OnActionExecuted - order=1 Message='(ACTION) MyBaseController.Index()
(POST) DebugFilter.OnActionExecuted - order=2 Message='(CONTROLLER) MyDerivedController
(POST) DebugFilter.OnActionExecuted - order=1 Message='(CONTROLLER) MyDerivedController
(POST) DebugFilter.OnActionExecuted - order=2 Message='(CONTROLLER) MyBaseController
(POST) DebugFilter.OnActionExecuted - order=1 Message='(CONTROLLER) MyBaseController
(POST) MyDerviedController.OnActionExecuted VIRTUAL METHOD

See Also

Controllers and Controller Actions
ASP.NET Model View Controller Applications
How to: Create an Action Filter in Visual Studio

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏