Action Filter -By Narendra Shrestha
Action filters are executed before and after the execution of the actions
on which these filters are applied. Due to the nature of their execution, these
filters are useful for any purpose. This filter is implemented when we want to
introduce some logic prior and posterior to the execution of our action
methods. Like other filters, it is customizable as well as has its default
implementation (refer table 1 Filter Part 1).
To implement customizable feature, we need to
implement interface “IActionFilter” in our custom class. The skeletal code for this implementation is as
listed below:-
using System;
namespace
System.Web.Mvc
{
// Summary:
// Defines the methods that are used in an
action filter.
public interface IActionFilter
{
// Summary:
// Called after the action method executes.
//
//
Parameters:
// filterContext:
// The filter context.
void
OnActionExecuted(ActionExecutedContext
filterContext);
//
// Summary:
// Called before an action method executes.
//
//
Parameters:
// filterContext:
// The filter context.
void
OnActionExecuting(ActionExecutingContext
filterContext);
}
}
Listing 1:- “IActionFilter” interface skeletal code
As expected this interface has two methods. “OnActionExecuting” method is called before the action method is executed. Inside this method we can investigate the
request, modify the request, cancel the request or even start another request.
“OnActionExecuted” method is called after
the action method is executed. This method can be used to set the result type
or cancel the result.
The parameter for “OnActionExecuting” method is of
type “ActionExecutingContext”. The parameter
for “OnActionExecuted” method is of type “ActionExecutedContext”. Both of these types
are derived from “ControllerContext” giving us access over important properties of controllers.
Afar from the
derived properties, both of these parameters have their own properties.
“ActionExecutingContext” has following
properties:-
·
“ActionDescriptor” of type “ActionDescriptor” which provides the detail of the current action method
·
“Result” of type “ActionResult” states the
result of the action method. The result can be set to null to cancel the entire
request
“ActionExecutedContext” has following
properties:-
·
“ActionDescriptor” of type “ActionDescriptor” which provides the detail of the current action method
·
“Result” of type “ActionResult” states the
result of the action method. The result can be set to null to cancel the entire
request
·
“Canceled” of type “bool” which indicates
whether “ActionExecutedContext” has been
cancelled by other action filters
·
“Exception” of type “Exception” provides the
exception thrown by other action filter of action methods
·
“ExceptionHandled” of type “bool” which indicates
if the exception has been handled
For implementation of the custom action filter, let us consider a situation
when user is authenticated. The “username” and “password” are posted along with
the request. Before executing action method, we may need to encrypt the
password before matching it. This is the ideal case in the real projects. For
our purpose we are using “MD5” algorithm. The password encrypted by this
algorithm cannot be decrypted. This one way nature of “MD5” algorithm makes it
one of the safest encryption ciphers.
“MyActionAttribute” class is created inside “infrastructure” folder. This class is derived
from “FilterAttribute” and it
implements interface “IActionFilter”. The code of
this class is as listed below:-
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using System.Text;
using
System.Security.Cryptography;
using
System.Web.Security;
namespace
Filters.Infrastructure
{
public class MyActionAttribute:FilterAttribute, IActionFilter
{
#region IActionFilter Members
private
string username;
private
string password;
private
bool isAuthorized=false;
public void OnActionExecuted(ActionExecutedContext
filterContext)
{
if
(isAuthorized)
{
filterContext.Result = new ViewResult {
ViewName = "Welcome" };
}
else
{
ViewResult
result = new ViewResult();
result.ViewName = "Login";
result.ViewBag.message = "Login attempt failed!!!";
filterContext.Result = result;
}
}
public void OnActionExecuting(ActionExecutingContext
filterContext)
{
username =
filterContext.HttpContext.Request.Form["username"];
password =
filterContext.HttpContext.Request.Form["password"];
MD5
md5Hash = MD5.Create();
string
md5Password = GetMD5Hash(md5Hash, password);
bool
result = FormsAuthentication.Authenticate(username,
md5Password);
if
(result)
{
FormsAuthentication.SetAuthCookie(username,
false);
isAuthorized = true;
}
else
{
isAuthorized = false;
}
}
private
static string
GetMD5Hash(MD5 md5Hash, string input)
{
byte[]
data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
StringBuilder
strBuilder = new StringBuilder();
foreach
(byte b in
data)
{
strBuilder.Append(b.ToString("x2"));
}
return
strBuilder.ToString();
}
#endregion
}
}
Listing 2:-“ MyActionAttribute” class
In this class we added some of our own methods and variables. “username” and “password” are for the reference of the user login
name and password. “isAuthorized” is to keep
track of whether user has been authenticated or not. We will shortly get back
to you why we chose this variable as a reference for authentication status. “GetMD5Hash” method’s role is to generate MD5 encryption of our password.
Starting from method “OnActionExecuting”, the username
and the password values that are posted from the login view is retrieved
through “filterContext.HttpContext.Request.Form”. The key
specified in the form value is the “name” attributes value of the HTML controls
in the view. The value of this key-value pair is the “value” attributes value
of the HTML controls. MD5 hash is
created by “Create” method of “MD5” static class in namespace “System.Security.Cryptography”. This hash is used to encrypt the password by supplying it to “GetMD5Hash” method. We authenticate username and encoded password by “Authenticate” method. We saved the result of authentication because we are now not sure
of the credentials supplied by user. This is not hard coded any more as it was
in our previous cases (Refer filter Part 1 and 2). You might have inferred the
changes in “Web.config” “authentication” tag mentioned below:-
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880">
<credentials passwordFormat="Clear">
<user name="name" password="5f4dcc3b5aa765d61d8327deb882cf99"/>
</credentials>
</forms>
</authentication>
Listing 3:- “Web.config” configuration
Value of “password” attribute
within the “user” tag is
pre-calculated by encoding it from “MD5” alogrithm. We kept note of MD5 hash
value of password whose value is “password”. In real application, this value
will be generated during sign up procedure and will be stored in database.
Based on the result of authentication, the value of “isAuthorized” is set accordingly. This turn was taken because, after arriving at “OnActionExecuted” method. We had no way of knowing the result of authentication. I even
used “filterContext.HttpContext.User.Identity.IsAuthenticated”. But to my surprise even when authentication was successful this value
remained false. This value became true
only when the whole thing was again repeated. It was not long enough to see
that “IsAuthenticated” property is read-only and can’t be changed.
I even realized that “filterContext” was updated
only at the beginning of the action method. So my procedure is one of the
effective ways to keep track of the authentication in scenarios like this one.
In “OnActionExecuted” method, the result of
the action method is set to type “ViewResult”. If the authentication is successful, the view name to be rendered is
“Welcome”. Else the view name “Login” is rendered. The failure method is stored
in “Viewbag.message”.
The changes added in the “Home” controller are listed as below:-
[HttpGet]
public ViewResult Login()
{
return
View();
}
[HttpPost]
[MyAction]
public ActionResult Login(string
username, string password)
{
//do the database
update such as last accessed etch here
return null;
}
public ActionResult SignOut()
{
FormsAuthentication.SignOut();
return
RedirectToAction("Login");
}
Listing 4:-“Home” controller
The view rendered
by “Login” action method is listed as below:-
@{
ViewBag.Title = "Login";
}
<h2>
Login</h2>
<p style=" color:Red">@ViewBag.message</p>
@using (Html.BeginForm())
{
<div style="width: 25%; border: 1px solid black;">
<table width="100%">
<tr>
<td>
Username:
</td>
<td>@Html.TextBox("username", null,
new { style = "width:90%"
})
</td>
</tr>
<tr>
<td>
Password:
</td>
<td>@Html.Password("password", null,
new { style = "width:90%"
})
</td>
</tr>
</table>
<table width="100%">
<tr>
<td align="center">
<input type="submit" name="login" value="Log in" />
</td>
</tr>
</table>
</div>
}
Listing 5:-“Login.cshtml”
Note the “ViewBag.message” to retrieve the
failure message for failed login. The post request from this view is redirected
to “Login” method with “[HttpPost]” attribute. It is in this action, where we apply our custom attribute “[MyAction]”. We simply
returned null from this action. Everything else will be handled by our custom
action filter. The comment in this action is suggestive in its own right.
The view “Welcome.cshtml” for successful login is as listed below.
@{
ViewBag.Title = "Welcome";
}
<h2>Login Sucessfull. Welcome!!!</h2>
@Html.ActionLink("Sign out", "SignOut")
Listing 6:-“Welcome.cshtml”
In this view, there is an action link that redirects to “SignOut” action in “Home” controller. This particular action cancels the User’s
current session. It then redirects to “Login” action with “[HttpGet]” attribute to
prompt another session.
When we run our
solution, we can see the following outputs:-
1)
When user enters URL
“/Home/Login” which is our default URL
Figure 1- “Login.cshtml”
2)
When user successfully
sings in by entering username as “name” and password as “password”
Figure 2:-“Welcome.cshtml”
3)
When login fails
Figure 3:- Failed login
implied with message
Action Result Filters
Action Result Filters are more or less general purpose filter that operate
on the result produced by the action filters. Like action filters, these
filters also function executes in two stages i.e. when the result from action
filter becomes available and when the result has been executed. This filter is
also customizable. To implement the custom behavior of this filter, we need to
implement interface “IResultFilter”. The skeletal
code for this interface is as listed as below:-
using System;
namespace
System.Web.Mvc
{
// Summary:
// Defines the methods that are required for
a result filter.
public interface IResultFilter
{
// Summary:
// Called after an action result executes.
//
//
Parameters:
// filterContext:
// The filter context.
void
OnResultExecuted(ResultExecutedContext
filterContext);
//
// Summary:
// Called before an action result executes.
//
//
Parameters:
// filterContext:
// The filter context.
void
OnResultExecuting(ResultExecutingContext
filterContext);
}
}
Listing 7:- Skeletal code of interface “IResultFilter”
As expected, the interface has two method implementations. “OnResultExecuting” is called when the action filter returns the result i.e. when the result
is ready. Everything you put into this section executes before execution of the
action result. The parameter to this action is of type “ResultExecutingContext” derived from “ControllerContext”.
“OnResultExecuted” is called after the
action result is executed. The tasks that needed to be conducted after the
execution of the action result fit here. The parameter to this action is of type
“ResultExecutedContext” which is also
derived from “ControllerContext”.
Afar from having acquired derived properties, these parameter types also
have their own general purpose properties:-
“ResultExecutingContext” has following
properties:-
·
“Cancel” of type “bool” which indicates
if the result has been canceled by other action filters
·
“Result” of type “ActionResult” states the
result of the action method. The result can be set to null to cancel the entire
request
“ResultExecutedContext” has following
properties:-
·
“Result” of type “ActionResult” states the result of the action method. The
result can be set to null to cancel the entire request
·
“Canceled” of type “bool” which indicates
whether “ResultExecutedContext” has been
cancelled by other action result filters
·
“Exception” of type “Exception” provides the
exception thrown by other action filter and action result filter of action
methods
·
“ExceptionHandled” of type “bool” which indicates
if the exception has been handled
We are going to implement result filters to make slight modification in our
previous example of action filters. Let us say that after the execution of the
action filter, we need to convey small yet critical information to the users.
In case of failed login attempt, we need to inform the user about his previous
failed attempt’s date and time. After successful login, we need him to see
complete long format date as Today is Saturday, 27 June, 1986 - 1:23 PM on the
bottom of the page. We could easily use action filter without any second thoughts.
Since we are trying to implement result filter here, we will select later case
as a way to resolve our problem. However, there may be the situations like when
action filters has been completely tested and approved. It may be compromising
act to make changes to action filters which calls out for repetition of the
entire testing procedure. Shifting this logic to the result filters decouples
the dependency from the action filter eliminating overhead of retesting and
speeding up the deliverance time.
The code listed
below is for the class “MyActionResultAttribute” in “infrastructure” folder which is the implementation of “IResultFilter” and derives from “FilterAttribute” as required.
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
namespace
Filters.Infrastructure
{
public class MyActionResultAttribute:FilterAttribute,IResultFilter
{
#region IResultFilter Members
private
string longdate;
private
string lastTry;
public void OnResultExecuted(ResultExecutedContext
filterContext)
{
if
(filterContext.Exception == null &&
!filterContext.Canceled)
{
ViewResult
result = (ViewResult)filterContext.Result;
if (result
!= null)
{
if
(result.ViewName == "Welcome")
{
filterContext.HttpContext.Response.Write("<p
style='color:Green;'><br/>Today is "
+ longdate+"<br/></p>");
}
else
if (result.ViewName == "Login")
{
filterContext.HttpContext.Response.Write("<p
style='color:Red;'><br/>Last login
attempt at " + lastTry+"<br/></p>");
}
filterContext.Result =
result;
}
}
}
public void OnResultExecuting(ResultExecutingContext
filterContext)
{
longdate = DateTime.Now.ToLongDateString();
lastTry = DateTime.Now.ToShortDateString()+"-"+ DateTime.Now.ToShortTimeString();
}
#endregion
}
}
Listing 8:-“ MyActionResultAttribute” class
In this class there are two string variables “longdate” and “lastTry”. “longdate” variable is implemented to hold the long date format. “lastTry” variable is used to store a brief information about last unsuccessful
authentication.
In method “OnResultExecuting” when the result
from action filter becomes available, we just set these variables values
through “DateTime” static class. In method
“OnResultExecuted”, if there is no
exception, we extract the result property from the filtercontext of type “ResultExecutedContext”. The result of the
“filtercontext” is casted to “ViewResult” type. If the result is not null (i.e. result is of type “ViewResult”), the view name is checked. For view
“Welcome.cshtml”, we simply write our message along with “longdate”. While for view “Login.cshtml”, we will write “lastTry” indicating last unsuccessful login attempt. We could have used “viewbag”,
but I am lazy enough to show you the changes in these views. After all, we have
dealt with these views while working with custom action filters. “HttpContext.Response.Write” works fine for this context. Am I right? Try to modify the View result in
this method and see what happens. Only then it will make sense to you why I chose
this process. You will realize that I am half lying. You know I also tend think
the way you think.
We need to place “[MyActionResult]” attribute for action result filter in the action method “Login” that accepts POST request. The highlighted segment in code below shows
this change:-
[HttpPost]
[MyAction]
[MyActionResult]
public ActionResult Login(string
username, string password)
{
//access the
database and update the last accessed
return null;
}
Listing 9:- “Login” action method incorporated with “[MyActionResult]” action result filter
When we run the solution and go to URL “/Home/Login”, enter any invalid
credential, we can see the following output
Figure 4:- Failed login attempt showing last login attempt in view
“Login.cshtml”
After successful login, we can see the following result
Figure 5:- Successful login showing today’s date in long date format in
view “Welcome.cshtml”
Default Action filter and
action result filter
The default filter for both action filter and
result filter uses the same class “ActionFilterAttribute” (Refer table 1 in Filters Part 1). The benefit we get from using the
default class over custom class is that we can skip some of the unnecessary
method which we do not want to implement. In our previous example for action
result, it may have not made sense to many of you about the places/methods
where I kept the logic. It was only for the purpose of the demonstration of the
result filters. Like in “AuthorizeAttribute” while implementing default, we need to have our class derived from “ActionFilterAttribute”. This is
mandatory not because we want to preserve the native formation, but to write
our own. The “ActionFilterAttribute” is the abstract class with different virtual methods which needs to be
overridden in order to be useful. Hence, you must have understood why we must
derive our class from “ActionFilterAttribute”. The following code shows the abstract class “ActionFilterAttribute”.
public abstract class ActionFilterAttribute : FilterAttribute,
IActionFilter, IResultFilter
{
protected
ActionFilterAttribute();
public virtual void
OnActionExecuted(ActionExecutedContext
filterContext);
public virtual void
OnActionExecuting(ActionExecutingContext
filterContext);
public virtual void
OnResultExecuted(ResultExecutedContext
filterContext);
public virtual void
OnResultExecuting(ResultExecutingContext
filterContext);
}
Listing 10:- abstract class “ActionFilterAttribute”
This abstract class contains all the methods we used for action filters and
result filter. We used them all in our previous encounters. It wasn’t necessary
for us to use all these methods. We will modify our previous example by using
this default implementation.
We add class “MyDefaultActionFiltersAttribute” to” infrastructure” folder and derive it from class “ActionFilterAttribute”. The below code lists
this class:-
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
System.Security.Cryptography;
using
System.Web.Security;
using System.Text;
namespace
Filters.Infrastructure
{
public class MyDefaultActionFiltersAttribute:ActionFilterAttribute
{
private
string username;
private
string password;
private
bool isAuthorized = false;
private
string longdate;
private
string lastTry;
public override void
OnActionExecuting(ActionExecutingContext filterContext)
{
username =
filterContext.HttpContext.Request.Form["username"];
password =
filterContext.HttpContext.Request.Form["password"];
MD5
md5Hash = MD5.Create();
string
md5Password = GetMD5Hash(md5Hash, password);
bool
result = FormsAuthentication.Authenticate(username,
md5Password);
if
(result)
{
FormsAuthentication.SetAuthCookie(username,
false);
isAuthorized = true;
longdate = DateTime.Now.ToLongDateString();
}
else
{
isAuthorized = false;
lastTry = DateTime.Now.ToShortDateString() + "-" + DateTime.Now.ToShortTimeString();
}
}
public override void
OnActionExecuted(ActionExecutedContext
filterContext)
{
if
(isAuthorized)
{
filterContext.Result = new ViewResult {
ViewName = "Welcome" };
}
else
{
ViewResult
result = new ViewResult();
result.ViewName = "Login";
result.ViewBag.message = "Login attempt failed!!!";
filterContext.Result = result;
}
}
public override void OnResultExecuted(ResultExecutedContext
filterContext)
{
if
(filterContext.Exception == null &&
!filterContext.Canceled)
{
ViewResult
result = (ViewResult)filterContext.Result;
if
(result != null)
{
if
(result.ViewName == "Welcome")
{
filterContext.HttpContext.Response.Write("<p
style='color:Green;'><br/>Today is "
+ longdate + "<br/></p>");
}
else
if (result.ViewName == "Login")
{
filterContext.HttpContext.Response.Write("<p
style='color:Red;'><br/>Last login
attempt at " + lastTry + "<br/></p>");
}
filterContext.Result =
result;
}
}
}
private
static string
GetMD5Hash(MD5 md5Hash, string input)
{
byte[]
data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
StringBuilder
strBuilder = new StringBuilder();
foreach
(byte b in
data)
{
strBuilder.Append(b.ToString("x2"));
}
return
strBuilder.ToString();
}
}
}
Listing 11:- “MyDefaultActionFiltersAttribute” class
You can notice that the method in this class resembles the methods in our
custom action filters and custom result filters. Only difference is we saved
our self from using “OnResultExecuting”. We didn’t do
much useful in this action in our previous implementation rather than assigning
date formats to our variables. We have transferred this logic to “OnActionExecuting” method. In “OnResultExecuting” method of
custom result filter, both values of “longdate” and “lastTry” were assigned value irrespective of
authentication result. Now after transferring this logic to “OnActionExecuting”, we don’t need to assign values for unused variables. A good programming
practice!
The change we make in “Login” action for POST request in the home controller
is as below:-
[HttpPost]
[MyDefaultActionFilters]
public ActionResult Login(string
username, string password)
{
//access the
database and update the last accessed
return null;
}
Listing 12:- “Login” action method incorporated with “[MyDefaultActionFilters]” filter attribute
When we run the project and enter URL “/Home/Login”, we see the same result
as in above case of result filter
Figure 6:- Failed login attempt showing last login attempt in view
“Login.cshtml”
Figure 7:- Successful login showing today’s date in long date format in
view “Welcome.cshtml”
Global Filter
Global filters are applied to all the controllers and their actions in your
application. We change a regular filter into global filter by registering it as
a global filter. This is done in the “RegisterGlobalFilters” method of the “Global.asax.cs” class. We register our filters by adding
it to collection “GlobalFilterCollection” as follows
public static void
RegisterGlobalFilters(GlobalFilterCollection
filters)
{
filters.Add(new
HandleErrorAttribute());
filters.Add(new
MyActionAttribute());
}
Listing 13:-Registering Global filters
Ordering the filter
execution
The filters are executed on order based of
their type. Authorization filter are executed at the very beginning, followed
by action filters and then by result filters. However, the exception filters
are invoked only when there is an exception in the application. For same filter
types applied multiple times to an action, there is no strict distinction for
the order in the execution, unless explicitly specified. The MVC framework can
run any filter instance prior to another. In order to be sure about the
ordering, we add order parameter to the filter attribute of type “int” indicating the order of the execution.
This is shown as below
[HttpPost]
[MyDefaultActionFilters1(Order=2)]
[MyDefaultActionFilters2(Order=1)]
public ActionResult Login(string
username, string password)
{
//access the
database and update the last accessed
return null;
}
Listing 14:- Ordering of filter execution
In above action, “MyDefaultActionFilters2” executes first and “MyDefaultActionFilters1” executes later. If we do not specify the order of a filter it takes the
value “-1” causing it to have lowest value and become the first one to be
executed.
If all filters have same order
specified, MVC framework determines the order based on where filter has been
applied. Global filter are executed first, then follows the filter applied to
the controller and then comes the turn of the filters applied to the actions.
Whereas, in case of exception filters with same orders the execution order is
reversed. The filters applied to actions are executed first, and then the
filters applied to the controllers and finally the global filters.
For sake of curiosity, try an example to find out the order of execution of
“OnActionExecuting” and “OnActionExecuted” when you have two action filters applied to same action. Don’t forget to
order them. All I can say is that the MVC framework maintains the stack of
filters along while executing them and pops them up accordingly. The behavior
may be exactly what you were searching for. So go for it!!!
great
ReplyDeleteASP.NET MVC Training | MVC Online Training | MVC Training | ASP.NET MVC Training in Chennai | ASP.NET MVC 6 Training | Dot Net Training in Chennai
ReplyDelete.Net MVC Training | ASP.NET MVC Online Training | C# MVC Training | ASP.NET MVC Training in Chennai | Online ASP.NET MVC 6 Training | Dot Net Training in Chennai