Skip to main content

An Audit ActionFilter for ASP.NET MVC

February 19, 2012 mgroves 0 Comments
Tags: asp.net mvc ActionFilter audit

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.

Comments

Matthew D. Groves

About the Author

Matthew D. Groves lives in Central Ohio. He works remotely, loves to code, and is a Microsoft MVP.

Latest Comments

Twitter