Posts tagged with 'ActionFilter'
This is the last post of a series of posts about using ASP.NET's ActionFilter.
The last method that you can override in an ActionFilter is OnResultExecute, which runs after the result of a controller action has been executed.
An argument of type ResultExecutedContext gets passed in to OnResultExecute. It's not wildly different from ResultExecutingContext, except that instead of a "Cancel" member, you get a "Canceled" member, which just tells you after the fact if the action was cancelled or not. You can still access HttpContext, the Controller object, the Result, the RouteData, etc.
I can't really create another kitchen sink view like I could with the previous blog posts, because the ActionResult (usually a ViewResult) has already been executed. (I could still write to the Http Response, but that would be a little tedious).
Since OnResultExecuted can't output to the view, it's somewhat limited in its general usefulness. Logging, of course, can still be done here. Last minute changes to the HttpResponse might be a good use of this filter. You can still handle some exceptions, though using a HandleErrorAttribute filter might be a better idea for that.
Continuing my series on ASP.NET MVC ActionFilters, this time it's about the OnResultExecuting method (which runs before the result of the controller (e.g. ActionResult, ViewResult, etc) is run.
Since this filter runs after the action itself has completed, you won't be able to access any information about the action (except Route Data). The ResultExecutingContext has the basic context information that you've seen in the other contexts (HttpContext, Controller, RouteData), but it also has a "Cancel" bool property. If you set this property to true, the Result will not be executed (e.g. if it's a ViewResult, the View will not be returned to the browser; if it's a Redirect result, redirect headers will not be returned to the browser, etc). What you'll get by default is just a blank response with status code 200.
So why 'cancel' a result? You could substitute a cached view or replace it with some other response by directly manipulating the HttpContext. If the action returned a ViewResult, JsonResult, etc, you can still access the model passed to that result, to perform some logic or manipulate its properties.
As before, I created a kitchen sink view of ResultExecutingContext just for reference. I decided to put it in the ViewBag this time:
And here's a screenshot of the results in my browser:
One more to go in this series. As always, if there's something that you want more information or details on, feel free to leave a comment or send a contact form to me, and I'll be glad to help out in any way I can.
Someone asked in a comment on my post about OnActionExecuting if one could get access to the model in these filters in order to record some audit data. I think this is probably a good use-case for using PostSharp or Castle DynamicProxy to write an audit aspect and apply it on your service classes or repositories.
But suppose you wanted to record certain fields and other audit information about information being modified in your controller. I wasn't sure if this was going to work, so I coded something up in 30 minutes, and much to my surprise it worked pretty good.
It's too long to embed here, but here's the Gist link to an Audit ActionFilter proof of concept. To use this attribute, simply decorate the actions you want to audit like: [Audit(typeof(SomeEntity), "EntityPropertyName1", "EntityPropertyName2"]. The filter will first check to make sure the incoming entity results in a valid ModelState. If it's not, then no reason to record an audit record because you aren't persisting changes yet (maybe the user missed a required field).
If it is valid, then it will interrogate the parameters and arguments to see if it can find one of type SomeEntity. If it does, it will then interrogate the properties of that entity to get all the values of the properties that are named EntityPropertyName1, EntityPropertyName2, etc. It will then record all this information, along with other audit information like action name, controller name, the current user, timestamp. All this "interrogation" is being done via reflection.
In my example, it just writes it to TempData so that it can be displayed back out. In a real example, it would write it to whatever data store you are using for your audit records (a database, for instance).
Problems with this method of auditing:
- Reflection - it's slow and brittle. This version is very simple and it's still quite messy. A real production version could be downright unwieldy
- Line 61 of CustomerController.cs - I'm making the assumption that a ToString on each entity's property is meaningful. That may not always be the case, which could lead to even more complexity.
- If you spell one of the field names wrong, you could have problems. Refactoring/renaming of your entity properties could break this quite easily.
That being said, if you have a very large system and audit requirements like this, then this messy approach could be better than the even messier alternative of boilerplate audit code in every controller action. I still think using PostSharp or DynamicProxy on the entity repository/service layer would be a cleaner approach.
I contacted Nestor with my proof-of-concept, and he had some comments about possible further refinements:
- Store the name of the class and properties to be audited in a class/model of its own, so an administrator could decide which properties to audit via some administration tool.
- Store the property/value pairs audited in a XML format in the model, facilitating the search of some values (what were the activities of a given user, who or when a given class was deleted o modified, etc.) using the abilities of SQL Server in making indexes over XML columns.
OnActionExecuted method in an action filter will be run after the controller action it is filtering is executed. The context object that gets passed in of type ActionExecutedContext, which is very similar to ActionExecutingContext in the types of information you can get with these exceptions:
- There is no ActionParameters dictionary property.
- You will get some information related to an unhandled exception:
- Exception - the exception that was thrown by the action
- ExceptionHandled - set this to true if you've handled the exception in OnActionExecuted (otherwise it will be bubbled up)
- Canceled - this can get a bit confusing, but if the process was canceled by setting a different result in OnActionExecuting this will be set to 'true', or you can set this to 'true' yourself to cancel further processing. For more info, see this post on how MVC handles filters and this discussion on the Canceled property.
I did another "kitchen sink" view of an ActionExecutedContext object:
Paired with a slightly different View, which results in:
Note that one of the most interesting things you can do is to change the result of the action (like I did to make the Kitchen Sink example possible). For instance, if you wanted to redirect to another action, show a different view, etc,
Building on the overview of ActionFilters in MVC, I'm going to go into a deeper dive on the various overrides that you can use inside of an ActionFilter, starting with OnActionExecuting.
OnActionExecuting is run before the Action is executed. You can put whatever code you want in here, including something as simple as just writing some raw HTML out before the Action is executed. But the real power comes from the filterContext parameter (of type ActionExecutingContext) that gets passed in to your OnActionExecuting method. This object gives you a whole bunch of information about the current context of the action that you are filtering. You can use this information to control logic or, conversely, you can manipulate the context according to your logic.
I wrote a "kitchen sink" filter combined with a Razor view to give you an idea of what kind of context you can view and/or manipulate.
All this does is expose the filterContext object to the view, where I can display some of the information available.
Here's a screenshot of the output. Note the URL (/Home/Index/parameter_value_here?anotherParam=99) as you look through the screenshot.
I've broken this output into 4 sections of different context information you can get. The first 4 are ASP.NET MVC specific: Controller, Action, Action Result, and Route. The last one, Http Context, is more general to ASP.NET.
Controller
You can get the controller name, the controller type, and even the controller instance, depending on your need. You can try to cast the controller to a specific controller if you want and access properties of that controller (I wouldn't recommend that generally, but you'll note that's how I did my kitchen sink action filter, by casting to the HomeController type).
Once you have a controller instance, you can access ViewBag, ViewData, TempData, all of which allows you to communicate to the action (and ultimately the view) if you choose to.
You can also access custom attributes here, which opens up some possibilities in declarative programming and use of metadata.
Action
You can again, get the name (of the action this time) if you want just that. You can also get some reflection information about the parameters. If you look at ActionParameters, you can get the parameter names along with the arguments. And once again, you can access custom attributes here, just as you can with controllers.
Action Result
You have the ability to get the ActionResult that the Action will return. At this point in the life cycle, that ActionResult is probably null, so I'm not exactly sure what you would want to do with it. But it's there. You could create a RedirectResult and immediately execute it; I suppose that might come in handy.
Route
I generally would not recommend messing around with routes in your action filters, as things could get really confusing in ASP.NET MVC, but you have access to RouteData, Values and DataTokens here too.
Http Context
Finally, Http Context, which contains just about everything you need to work with your standard Http objects like Response, Request, Session, etc. Do note that the objects you get are generally base classes (e.g. HttpResponseBase and HttpRequestBase), so you may not have access to some of the methods and properties that you are used to.
With the Http Context available, you can do some interesting things like checking to see if the session expired, and then either reloading the session from a db or cookie or something, or redirecting to a 'timeout' or other message page.
There's more?
This isn't even an exhaustive list of the stuff you can get to via the ActionExecutingContext being passed in. I've just tried to list some of the more useful properties and examples of data that could be in those properties and left out the esoteric stuff. But there's a lot of power here to do almost anything you want before an action starts executing.
You're going to see very similar properties in the other action filter override context objects, but there are some differences that I'll explore in later posts.