Simple Auditing using an NHibernate IInterceptor (part 1)

This is the beginning of a multi-part post series on writing simple auditing functionality for an ASP.NET application using NHibernate.  The requirement was that every object modification event in the system should be logged by username and date.  Specifically I don’t need to know exactly which properties were changed (just that a user was updated by whom at what time), but if you do need to save the changed properties there are plenty of hooks to do that.

I’ll update this post with links to the next parts when they become available

The Audit Class

The Audit class will inherit from a base DomainObject<> which basically provides an Id property for all objects and helper methods for validation and transient checks (this is pretty common architecture when using NHibernate).  I’m using EntLib validation on the class and to satisfy a particular requirement I am going to set the action type to C/U/D depending on the action.  Here’s the code:

   1: public class Audit : DomainObject<Audit,Guid>
   2: {
   3:     [RequiredValidator]
   4:     [StringLengthValidator(50)]
   5:     public virtual string ObjectName { get; set; }
   6:  
   7:     [IgnoreNulls]
   8:     [StringLengthValidator(50)]
   9:     public virtual string ObjectId { get; set; }
  10:  
  11:     [RequiredValidator]
  12:     [StringLengthValidator(1)]
  13:     public virtual string AuditActionTypeId { get; set; }
  14:  
  15:     [RequiredValidator]
  16:     [StringLengthValidator(256)]
  17:     public virtual string Username { get; set; }
  18:  
  19:     public virtual DateTime AuditDate { get; set; }
  20:  
  21:     public virtual void SetActionCode(AuditActionType auditActionType)
  22:     {
  23:         switch (auditActionType)
  24:         {
  25:             case AuditActionType.Create:
  26:                 AuditActionTypeId = "C";
  27:                 break;
  28:             case AuditActionType.Update:
  29:                 AuditActionTypeId = "U";
  30:                 break;
  31:             case AuditActionType.Delete:
  32:                 AuditActionTypeId = "D";
  33:                 break;
  34:             default:
  35:                 throw new ArgumentOutOfRangeException("auditActionType");
  36:         }
  37:     }
  38: }
  39:  
  40: public enum AuditActionType
  41: {
  42:     Create, Update, Delete
  43: }

Audit Repository Testing

Before I move on to creating the interceptor I am going to run a few tests against a SQLite repository to make sure the Audit class can save and validate properly.

   1: [TestMethod]
   2: public void ValidationAuditWithoutSettingActionCodeShouldFail()
   3: {
   4:     var audit = new Audit
   5:     {
   6:         AuditDate = DateTime.Now,
   7:         ObjectName = "ValidObject",
   8:         Username = "ValidUsername"
   9:     };
  10:  
  11:     var isValid = ValidateBusinessObject<Audit>.IsValid(audit);
  12:  
  13:     Assert.AreEqual(false, isValid, "An audit requires an action code");
  14: }
  15:  
  16: [TestMethod]
  17: public void ValidationBlankAuditShouldFail()
  18: {
  19:     var audit = new Audit();
  20:  
  21:     var isValid = ValidateBusinessObject<Audit>.IsValid(audit);
  22:  
  23:     Assert.AreEqual(false, isValid, "A blank audit should not be valid");
  24: }

And to test that saving works properly:

   1: [TestMethod]
   2: public void CanSaveCompleteAndValidAudit()
   3: {
   4:     var audit = new Audit
   5:                     {
   6:                         AuditDate = DateTime.Now,
   7:                         ObjectName = "ValidObject",
   8:                         ObjectId = "ValidId",
   9:                         Username = "ValidUsername"
  10:                     };
  11:     
  12:     audit.SetActionCode(AuditActionType.Update);
  13:  
  14:     using (var ts = new TransactionScope())
  15:     {
  16:         _auditRepository.EnsurePersistent(audit);
  17:  
  18:         ts.CommitTransaction();
  19:     }
  20:  
  21:     Assert.AreEqual(false, audit.IsTransient(), "Audit should have been saved");
  22: }
  23:  
  24: [TestMethod]
  25: public void CanSaveAuditWithoutObjectId()
  26: {
  27:     var audit = new Audit
  28:     {
  29:         AuditDate = DateTime.Now,
  30:         ObjectName = "ValidObject",
  31:         Username = "ValidUsername"
  32:     };
  33:  
  34:     audit.SetActionCode(AuditActionType.Update);
  35:  
  36:     using (var ts = new TransactionScope())
  37:     {
  38:         _auditRepository.EnsurePersistent(audit);
  39:  
  40:         ts.CommitTransaction();
  41:     }
  42:  
  43:     Assert.AreEqual(false, audit.IsTransient(), "Audit should have been saved");
  44: }

What Did This Accomplish?

Not much yet.  We have an audit class that is behaving how we want, and eventually we are going to use it to save audit records.  Next time we’ll create an IInterceptor which will hook into create/update/delete events in NHibernate.

1 Comment

Comments have been disabled for this content.