In-Depth
Creating Custom ASP.NET MVC Filters
ASP.NET MVC filters can be defined once and used in multiple places. Because ASP.NET MVC also provides the ability to create custom filters, it's good practice to learn how to make them and use them in your own projects.
ASP.NET MVC filters are declarative attributes that capture, or filter, a specific behavior on an action method or controller class. When applied, filters can trigger methods before, during or after an action occurs. This allows for added capabilities when handling actions, via default or custom filters.
The ASP.NET MVC framework offers several filters available by default. Examples of these are shown in Table 1.
Table 1: Model-View-Controller filters that are available by default.
Filter Name |
Description |
[AuthorizeAttribute] |
This Authorization filter limits user access depending on authentication or authorization. |
[HandleErrorAttribute] |
This Exception filter allows for custom exception handling of action methods. |
[OutputCacheAttribute] |
This Result filter caches output for a specified duration. |
[RequireHttpsAttribute] |
This Authorization filter requires HTTPS to be used instead of unsecured HTTP. |
|
All filters, either default or custom, are defined by a class that inherits the base class System.Web.Mvc.FilterAttribute and implements a corresponding interface. Listed in Table 2 are the different types of ASP.NET MVC 5 filters, their corresponding interfaces and the methods available.
Table 2: The filter types available in ASP.NET MVC 5.
Filter Type |
Interface |
Methods |
Authentication |
IAuthenticationFilter |
OnAuthentication On
AuthenticationChallenge |
Authorization |
IAuthorizationFilter |
OnAuthorization |
Action |
IActionFilter |
OnActionExecuting On
ActionExecuted |
Result |
IResultFilter |
OnResultExecuting
OnResultExecuted |
Exception |
IExceptionFilter |
OnException |
|
A few notes about the default filters in ASP.NET MVC 5. First, the Authentication filter is newly introduced. Second, by design, Authorization filters run before any other filter. Action and Result filters are the most common types of filters. They've become so common that the ASP.NET MVC team has created an ASP.NET MVC base class System.Web.Mvc.ActionFilterAttribute. Last, the Exception filter is executed when an unhandled exception is thrown in a method decorated with the Exception filter attribute.
In addition to the various filters offered by default, ASP.NET MVC 5 also allows the ability for creating custom filters. This will be the basis for this article.
Sample Application
To demonstrate creating a custom filter, I'll use Visual Studio 2013 and the ASP.NET MVC 5 template. I'll create a new custom Exception filter type. This new filter will catch unhandled exceptions and process them accordingly. The intention is to have the filter call a specific routine depending on the type of exception thrown.
Upon starting Visual Studio 2013, you'll notice the Web templates have been reorganized and all categorized as ASP.NET Web Application, as shown in Figure 1.
After specifying the name and location of the new solution to be created (CustomMVCFilterDemo), clicking OK will display the selection window shown in Figure 2. It's in this window where the specific project template will be selected.
For this demo, I chose the MVC template without adding a unit test project. Clicking the Change Authentication button reveals another window with a menu of authentication options, as shown in Figure 3.
For the purposes of this sample application, I selected No Authentication.
With the default project template created, the next step is to add a Filters folder. This isn't necessary but it'll help keep the solution organized, following best practices. Within the newly created folder, I'll create a new class called CustomExceptionFilter.vb.
As stated previously, all filters inherit from the System.Web.MVC.FilterAttribute base class. Therefore, the new CustomExceptionFilter class will inherit it. In addition, it will also implement the IExceptionFilter interface. Part of this implementation includes defining the OnException method, which I did. I also created a WriteLog method that will be used for displaying messages in the text-based log file (C:\CustomMVCFiltersDemo\ExceptionLog.txt). The complete code listing for CustomExceptionFilter.vb can be seen in Listing 1.
Listing 1: CustomExceptionFilter.vb
Imports System.IO
Imports System.Web.Mvc
Imports System.Reflection.MethodBase
Public Class CustomExceptionFilter
Inherits System.Web.Mvc.FilterAttribute
Implements IExceptionFilter
Public Sub OnException(
filterContext As ExceptionContext) Implements IExceptionFilter.OnException
Dim LogLine As String = String.Empty
'Gather filterContext info for logging
Dim ExMessage As String = filterContext.Exception.Message
Dim ExType As System.Type = filterContext.Exception.GetType
Dim ExName As String = ExType.ToString()
Select Case (ExType)
Case GetType(EntitySqlException)
NotifyHelpDesk(ExMessage)
Case GetType(OverflowException)
NotifyDevTeam(ExMessage)
End Select
'Gather RouteData info for logging
Dim RtData As RouteData = filterContext.RouteData
Dim ControllerClassName As String = RtData.Values("controller")
Dim ActionMethodName As String = RtData.Values("action")
Dim MethodName As String = GetCurrentMethod().Name
LogLine = String.Format(
"Logging MethodName={0}; Exception Message={1}; ControllerClass={2}; ActionMethod={3}",
MethodName, ExMessage, ControllerClassName, ActionMethodName)
WriteLog(LogLine)
End Sub
Private Sub NotifyHelpDesk(Msg As String)
Dim MethodName As String = GetCurrentMethod().Name
WriteLog(String.Format("Logging MethodName={0}; Exception Message={1}", MethodName, Msg))
'**********************************************************************************
'Include additional logic here to notify help desk via SMS or other means
'**********************************************************************************
End Sub
Private Sub NotifyDevTeam(Msg As String)
Dim MethodName As String = GetCurrentMethod().Name
WriteLog(String.Format("Logging MethodName={0}; Exception Message={1}", MethodName, Msg))
'**********************************************************************************
'Include additional logic here to notify Dev team via e-mail or other means
'**********************************************************************************
End Sub
Public Sub WriteLog(Msg As String)
Dim sw As StreamWriter = New StreamWriter("C:\CustomMVCFiltersDemo\ExceptionLog.txt", True)
sw.WriteLine(DateTime.Now.ToString() + ": " + Msg)
sw.Close()
End Sub
End Class
Looking at OnException in more detail, it accepts an ExceptionContext type parameter. This object, as the name suggests, provides a variety of information about the exception and its location (type, Controller Class, Action Method and so on). To learn more about ExceptionContext, please see the MSDN documentation here.
I'm using the ExceptionContext object in OnException to extract more information for logging. I also added a code segment to simulate notifying different teams depending on the type of exception. For example, if the exception is type EntitySqlException, then it will call NotifyHelpDesk. If the exception is of type OverflowException, then it will call NotifyDevTeam. Both NotifyHelpDesk and NotifyDevTeam simply log the error, but they also act as place holders for code to show how different routines can be called from OnException.
To use the newly created Exception filter, I'll apply the attribute <CustomExceptionFilter>
to the HomeController Class, in HomeController.vb, as seen in Listing 2.
Listing 2: HomeController.vb
Imports CustomMVCFiltersDemo.CustomExceptionFilter
Imports System.Reflection.MethodBase
<CustomExceptionFilter>
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Return View()
End Function
Function About() As ActionResult
Try
Throw New EntitySqlException("Throwing SQL Exception from About()")
ViewData("Message") = "Your application description page."
'Handling the exception locally, then re-throwing it to have
'OnException process it as well.
Catch Ex As Exception
Dim csf As New CustomExceptionFilter() 'This is needed only to access WriteLog()
Dim ec As New ExceptionContext()
Dim MethodName As String = GetCurrentMethod().Name
csf.WriteLog(String.Format(
"Logging MethodName={0}; Exception Message={1}", MethodName, Ex.Message))
Throw Ex 'This is required to ensure OnException gets called.
End Try
Return View()
End Function
Function Contact() As ActionResult
Dim a, b, c As Int32
a = 3
b = 0
c = (a / b) 'this intended to throw an OVERFLOW exception
ViewData("Message") = "Your contact page."
Return View()
End Function
End Class
If you look closely at the About and Contact methods, you'll see I inserted some code to deliberately cause an exception in each one. Only About has a Try-Catch block for handling the error. This is used only for demonstration purposes and by no means do I consider this production code.
When I execute the app from Visual Studio, the default homepage is launched, as shown in Figure 4.
Clicking on About will call the About action method where an EntitySqlException is thrown. The Try-Catch block will catch the error, calling WriteLog to log the error in the log file. Within the Try-Catch block, the error is thrown again. This will cause a Visual Studio pop-up error message to appear, as shown in Figure 5. If the application was running in IIS, not Visual Studio, you wouldn't see the pop-up error messages.
Clicking Continue in Visual Studio will allow OnException to be called. Remember, in order for the exception filter to catch an exception, it has to be unhandled. By throwing the error from within the Try-Catch block, I'm essentially creating an unhandled exception. After continuing, an error message will appear on the Web page. Figure 6 shows the error message with the exception message highlighted.
Looking at the ExceptionLog.txt, you'll see the entries generated from this exception:
12/11/2013 4:37:43 PM: Logging MethodName=About; Exception Message=Throwing SQL Exception from About()
12/11/2013 4:37:46 PM: Logging MethodName=NotifyHelpDesk; Exception Message=Throwing SQL Exception from About()
12/11/2013 4:37:46 PM: Logging MethodName=OnException; Exception Message=Throwing SQL Exception from About(); ControllerClass=Home; ActionMethod=About
The first entry is logged by the method About, as specified in the Logging MethodName field. This is because About has a Try-Catch block that captures the exception and calls WriteLog to log the error. The second line is an entry logged by NotifyHelpDesk. The third entry is logged by OnException. This is due to OnException calling NotifyHelpDesk if the Exception is an EntitySqlException type. NotifyHelpDesk logs the error using WriteLog and displays only the Exception Message. Afterward, OnException logs the error, showing additional information (ControllerClass, ActionMethod)
Next, I'll focus on testing the Contact action method. To do so, I'll restart the application, and from the default homepage, click on Contact. This immediately renders a Visual Studio pop-up error message, shown in Figure 7, where a divide-by-zero operation caused an OverflowException to be thrown.
Once again, clicking Continue in Visual Studio causes the error to surface on the Web page (see Figure 8).
The log file shows two additional entries generated:
12/11/2013 4:41:05 PM: Logging MethodName=NotifyDevTeam; Exception Message=Arithmetic operation resulted in an overflow.
12/11/2013 4:41:05 PM: Logging MethodName=OnException; Exception Message=Arithmetic operation resulted in an overflow.; ControllerClass=Home; ActionMethod=Contact
This is in comparison to the three entries generated by clicking About. The action method Contact doesn't have a Try-Catch block to log the error. Therefore, NotifyException calls NotifyDevTeam, which logs the error. The next entry is logged by OnException, and then the process terminates as expected.
The Filter Advantage The advantage of ASP.NET MVC filters is they can be defined once, and used in multiple places. Defining a custom filter is simplified through the use of base class inheritance. By using the name of the custom filter object as an attribute, you can decorate either a class or an individual method to filter certain behavior. In addition, the ExceptionContext object provides all the details of the exception for logging or invoking custom logic. This makes filters a valuable feature in ASP.NET MVC.