Auditing with Castle Active Record
How to audit every save, update, and delete in Castle ActiveRecord using an NHibernate interceptor, without touching a single line of existing code.
Sometimes you need to audit everything you do in a transactional system. A lot of standards such as SOX and others state that you must audit your transactions. Doing this by hand inside of your system can (and usually is) a huge PITA. However, it is a necessary evil in some instances.
However, if you’re using Castle’s Active Record (AR) for your database access, then hopefully this post will save you some trouble.
Active Record provides us with the ability to provide interceptors which “intercept” the execution of some of the Active Record calls. I’m using the ActiveRecordMediator
The Interceptor
using System;using System.Collections.Generic;using Castle.ActiveRecord.Framework.Internal;using NHibernate;using NHibernate.Type;
namespace Felker.AR.Audit{ public class AuditInterceptor : EmptyInterceptor { private static AuditInterceptor instance = new AuditInterceptor();
protected AuditInterceptor() { }
public static AuditInterceptor Instance { get { return instance; } set { instance = value; } }
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types) { // Object was updated. Save the current state, previous state, etc. return false; }
public override void OnDelete(object entity, object id, object[] state, string[] propertyNames, IType[] types) { // Object was deleted. }
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types) { // Object was created. "state" is the state of the object. return false; } }}The class above implements a default “EmptyInterceptor”. This allows you to override the methods you need in your interceptor. I have overridden the “OnSave”, “OnDelete”, and “OnFlushDirty” methods. When the interceptor is in place, when a Save, Delete or Update call is made with the ActiveRecordMediator
It is inside of the “OnSave”, “OnDelete”, and “OnFlushDirty” that I can perform my auditing as I am provided with the object state during the save, delete or update. I have not provided any sort of mechanism in the code to actually audit the info to a database or a file as this is up to you to decide “where” it should go. This is only the boiler plate that you can use to intercept the save calls. You have all the data you need. The current entity, the current state (and previous state if it is an update) and the type information.
Why Works So Well
The beauty in this implementation is that I _did_not_ have to change _any_ of my pre-exising ActiveRecordMediator
Wiring up the Interceptor with a Facility
using Castle.ActiveRecord.Framework;using Castle.Core.Configuration;using Castle.MicroKernel;using NHibernate;
namespace Felker.Castle.Facilities{ /// /// Allows for the system to pick up the audit facility which will be used to /// audit all transactions in the system. /// public class AuditFacility : IFacility { #region IFacility Members
public void Init(IKernel kernel, IConfiguration facilityConfig) { InterceptorFactory.Create = new InterceptorFactory.CreateInterceptor(CreateAuditInterceptor); }
public void Terminate() { // Nothing to terminate }
#endregion
private IInterceptor CreateAuditInterceptor() { return AuditInterceptor.Instance; } }}The class above creates a Facility for Active Record. We wire this up in the Global.asax.cs file like so:
static private IWindsorContainer _container;
protected void Application_Start(object sender, EventArgs e){ try { _container = new WindsorContainer(Server.MapPath("~/config/windsor.config")); var app = _container.Resolve(); app.RegisterFacilities(_container); app.RegisterComponents(_container); }}In the Application.cs file we add the facility as such:
public void RegisterFacilities(IWindsorContainer container){ container.AddFacility("audit.support", new AuditFacility());}The code above wires up the facility into the container which will handle all of our type registration/etc for us.
Conclusion
The container now contains the facility which will wire up the audit interceptor which will intercept all ActiveRecordMediator
We have not changed a line of code in our existing system, yet we have added the ability to audit all of our CRUD operations in a simple yet effective manner.



