Twitter Feed Popout byInfofru

OverrideThis.com

Adventures in .NET Software Craftsmanship!

NHibernate Audit w/Database Server Time

Disclaimer: This articles highlights a workaround not a best practice. In fact, if you are so inclined, please provide me with your feedback on how you would have solved this problem differently.

 

At a recent consulting gig a client’s IT department made the very common request that we store classic audit trail information in all database records.  What that means, at least to me, is that every single record in the database is going to have a [CreatedBy], [CreatedDate], [UpdatedBy], and [UpdatedDate] fields.  I found a lot of help on the internet on how to implement this by using NHibernate and its Event Model introduced (ported from Hibernate) in the 2.0 version.

 

These are some of the articles I based my solution on:

http://ayende.com/Blog/archive/2009/04/29/nhibernate-ipreupdateeventlistener-amp-ipreinserteventlistener.aspx

http://nhforge.org/wikis/howtonh/creating-an-audit-log-using-nhibernate-events/revision/1.aspx

 

Both articles are pretty much self explanatory and solve all the business requirements except one, and I quote “All dates in the audit database fields must be the Database Server Time not the Application Server Time”.  Now this might sound like a simple requirement that can be solved this by defining custom SQL queries for insert, update and delete operations in the NHibernate mapping files but doing so will introduce the following problems:


1.- Writing custom xml mapping files defeats the purpose of using FluentNHibernate and its AutoMapping capability.

2.- Our integration testing strategy that relies on the use of SQLite would be become complex as we would have to define custom .hbm mapping files for testing and different ones for production ones that use T-SQL for SQL Server 2008.

 

My solution, albeit probably not a good one, was to define a named query for retrieving the Database Server Time from the application.  The following is the code for registering that named query using the NHibernate’s fluent configuration API.

 

public const string SQLSERVER_HBM_MAPPING_XML =
    @"
        <hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'>
        <sql-query name='GetServerDateTime'>
            SELECT GETDATE();
        </sql-query>
        </hibernate-mapping>
    ";

public const string SQLITE_HBM_MAPPING_XML =
    @"
        <hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'>
        <sql-query name='GetServerDateTime'>
            SELECT datetime('now');
        </sql-query>
        </hibernate-mapping>
    ";

public static void RegisterServerDateTimeMethod(Configuration cfg) {
   if (cfg.Properties["connection.driver_class"].Contains("SQLite"))
        cfg.AddXmlString(SQLITE_HBM_MAPPING_XML);                    
   else
        cfg.AddXmlString(SQLSERVER_HBM_MAPPING_XML);
}

 

The key is to call the RegisterServerDateTimeMethod which registers the named query depending on the connection driver class settings name.   Now to make the process of calling this named query easier I wrote an extension method for NHibernate’s ISession that looks like the following.

 

public static class ISessionExtensions {
    public static DateTime GetServerDateTime(this ISession session) {
        var resultFromServerDateCall = session
     		.GetNamedQuery(NhSessionFactory.SERVER_DATETIME_NAMED_QUERY)
     		.UniqueResult();
        DateTime result;
        if (resultFromServerDateCall is string)
            result = DateTime.Parse((string)resultFromServerDateCall);
        else
            result = (DateTime) resultFromServerDateCall;
        return result;
    }
}

 

The following test illustrates how to use the Extension method.
* Needless to say this test is very brittle and doesn’t really test much, but it gives me some peace of mind as it verifies that the .hbm file mapping syntax is valid.

 

[Test]
public void NhRepositoryCanRetrieveServerDateTime() {
    var serverDate = this.Session.GetServerDateTime();
    Assert.AreEqual(serverDate.Year, DateTime.Now.Year);
    Assert.AreEqual(serverDate.Month, DateTime.Now.Month);
    Assert.AreEqual(serverDate.Day, DateTime.Now.Day);
}

FluentNHibernate1.1 RTW and ReadOnly Properties

As detailed in the FluentNHibernate website the 1.1 RTW was released last month!  The main purpose of the release was to get everybody off the trunk and have the latest version of FluentNHibernate target the latest NHibernate build 2.1.2GA.   Additionally, the release included a lot of new functionality and bug fixes that have been added by community members.  At first glance there didn’t seem to be any changes that would break any existing applications, but as always with these type of things I managed to find a scenario where an existing project simply collapsed because of decisions made on the original design.

 

Trouble Scenario

While using 1.0RTM and the default auto mapping functionality, a class like the Budget class in the following snippet would result in the Id being mapped as the identifier, the Amount being mapped as a property and the AmountFormatted property would be ignored as it would be identified as a read only property. This behavior changes significantly in the 1.1RTW build where the default behavior is to map the AmountFormatted property producing a nasty error that stresses that no setter was found for this property, if indeed your intent is to map and persist the information in this read only property then you would have to define an access strategy for a backing field.

 

public class Budget {
    public virtual int Id { get; protected set; }
    public virtual decimal Amount { get; set; }
    public virtual string AmountFormatted {
        get { return string.Format("{0:c}", this.Amount); }
    }
}

 

Solution
If you do not intent to persist the information in that read only property, one possible solution is to specify IAutoMappingOverride<T> classes for all entities that are being mapped in your system and manually specify each read only properties to be ignored.  Needless to say that this is not the preferred migration path for a large application where we relied on the original auto mapping behavior and now have 30 or 40 entities that we would need to build specific overrides for.  The more elegant solution is to use a new feature in 1.1RTW, the new AutoMappingConfiguration that allows you to define rules for types that are to be mapped and members of those types.  So adding the following CustomAutoMappingConfiguration solved all my problems.

 

public class CustomMappingConfiguration : DefaultAutomappingConfiguration {
    
    public override bool ShouldMap(Member member) {
        return member.CanWrite && member.IsProperty && base.ShouldMap(member);
    }

    public override bool ShouldMap(System.Type type) {
        return typeof(Budget).Namespace == type.Namespace;
    }
}

 

Hopefully, if you find yourself doing a similar migration this will help get you on your way.

Double Duty at Richmond Code Camp 2010.01

I will be presenting at the upcoming Richmond Code Camp 2010 spring edition, held at the J. Sargeant Reynolds Community College campus on Parham Road.  For additional details visit the Richmond Code Camp website at http://www.richmondcodecamp.org.

 

I am looking forward to seeing all the familiar faces that make up the Code Camp experience what it is. 

 

The following are the topics I will be presenting on,

 

Articulate your Data with FluentNHibernate

Learn how to use NHibernate, arguably the most used and powerful OSS ORM, using strongly typed fluent configurations. Avoid the hassle of building hundreds of XML files for mapping and configuration, and explore the benefits of Convention over Configuration while increasing your productivity.

 

Designing an ASP.NET MVC2 Application for Testability

The ASP.NET MVC Framework greatly improved the testability of an ASP.NET Web application by allowing us to easily apply unit and integration testing techniques to our Controllers, Filters, Binders, and more. But testing these components in isolation doesn't prove that your site functions as expected and outlined in the business requirements. Learn how to design and implement testability from the view perspective using BDD techniques applied to frameworks such as SpecFlow, Watin, and NUnit.

MVC2 Model Validation and Testing Scenarios

The solution described in this blog doesn't work for all scenarios.
Navigate to the following post for a better solution:
http://blog.overridethis.com/blog/post/2010/07/08/MVC2-Validation-and-Testing-e28093-Refactored.aspx

* TIP: If you don’t know what MVC2 Model Validation is you can check out this video here.

 

Lately, I have begun using ASP.NET MVC2 Model validation as much as possible and I have noticed a significant drop in the amount of plumbing code for validating a user’s request, which is making my controller action’s lighter, more readable and easier to manage. 

 

The one big drawback of using MVC2 Model Validation is that you are no longer able to test the functionality of your Controller’s action methods without having to mock or stub the validation process.  This is due to the fact that validation occurs during the Model binding process and as such is done by the ModelBinder, which doesn’t get called if you test your controller action’s by calling them directly.  We could write code to ensure that the Model binding process happens but it would be painful as we would have to mock around with the ControllerContext which is not an easy endeavor.

 

The following is my outline for a possible solution to this problem inspired by this post.  

 

First, lets establish a context to solve the problem, the following code snippet shows a Controller, Model, and Service Interface that all are direct dependencies of the CommentController we want to test.

 

Exhibit A – Very Simple Controller, Service, and Model:

namespace WebSite.Services {

   public interface ICommentService {
        void AddComment(string message);
    }

}
namespace WebSite.Models {
    
    using System.ComponentModel.DataAnnotations;

    public class AddCommentRequest {
        [Required]
        public string Message { get; set; }
    }

    public class AddCommentResult {
        public bool Success { get; set; }
        public string Message { get; set; }
    }
}
namespace WebSite.Controllers {

    using System.Web.Mvc;
    using Models;
    using Services;

    public class CommentController : Controller {

        private readonly ICommentService service;

        public CommentController() {

        }

        public CommentController(ICommentService service) {
            this.service = service;
        }

        [HttpPost]
        public ActionResult AddComment(AddCommentRequest request) {
            if (ModelState.IsValid) {
                this.service.AddComment(request.Message);
                return Json(new AddCommentResult {Success = true, Message = "Success"});
            }
            return Json(
                new AddCommentResult { 
                        Success = false, 
                        Message = "The 'message' is a required field." 
                });
        }
    }
}

 

Now, if we run the following tests the invalid request scenario would fail due to the validation not being triggered and as such the ModelState.IsValid property would always be true.

 

Exhibit B – The Tests

[TestFixture]
public class CommentControllerTests {

    private string VALID_MESSAGE = "SOME MESSAGE!";
    private string NOT_VALID_MESSAGE = string.Empty;

    [Test]
    public void CommentControllerCanAddValidMessage() {
        Assert.IsTrue(this.HttpPostToAddComments(VALID_MESSAGE).Success);
    }

    [Test]
    public void CommentControllerCannotAddValidMessage() {
        Assert.IsFalse(this.HttpPostToAddComments(NOT_VALID_MESSAGE).Success);
    }

    private AddCommentResult HttpPostToAddComments(string message) {
        
        var mockOfICommentService = new Mock<ICommentService>();
        mockOfICommentService.Setup(m => m.AddComment(VALID_MESSAGE)).AtMostOnce();
        var model = new AddCommentRequest {Message = message};
        var controller = new CommentController(mockOfICommentService.Object);
        var result = controller.AddComment(model);
        mockOfICommentService.Verify();
        return (AddCommentResult)((JsonResult) result).Data;
    }
}

 

The best option to solve this issue and keep our tests light would be to write an extension method that would allow us to call the Controller Action elegantly while adding the additional validation that needs to be stubbed out.  The following, is a possible implementation of that extension method.  One thing to note is that this implementation would only work in .NET 4.0 scenarios that are using System.Component.DataAnnotations attributes.  You could implement a similar solution in .NET 3.5 but would not have the ValidationContext or ValidationResult objects to leverage.

 

Exhibit C – The Controller Extension for validating System.Component.DataAnnotations

public static class ControllerExtensions {

    public static ActionResult CallWithModelValidation<C,R,T>(this C controller
            , Func<C,R> action
            , T model) 
        where C : Controller
        where R : ActionResult
        where T : class {

        var validationContext = new ValidationContext(model, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(model, validationContext, validationResults);
        foreach (var validationResult in validationResults) {
            controller
                .ModelState
                .AddModelError(validationResult.MemberNames.First(), 
                    validationResult.ErrorMessage);
        }

        return action(controller);
    }
}

 

Having this extension method in place would allow us to rewrite just a single line of code in the tests to ensure everything is working as expected.

 

Exhibit D – The corrected test scenarios

[TestFixture]
public class CommentControllerTests {

    private string VALID_MESSAGE = "SOME MESSAGE!";
    private string NOT_VALID_MESSAGE = string.Empty;

    [Test]
    public void CommentControllerCanAddValidMessage() {
        Assert.IsTrue(this.HttpPostToAddComments(VALID_MESSAGE).Success);
    }

    [Test]
    public void CommentControllerCannotAddValidMessage() {
        Assert.IsFalse(this.HttpPostToAddComments(NOT_VALID_MESSAGE).Success);
    }

    private AddCommentResult HttpPostToAddComments(string message) {
        
        var mockOfICommentService = new Mock<ICommentService>();
        mockOfICommentService.Setup(m => m.AddComment(VALID_MESSAGE)).AtMostOnce();
        var model = new AddCommentRequest {Message = message};
        var controller = new CommentController(mockOfICommentService.Object);
        var result = controller.CallWithModelValidation(c => c.AddComment(model), model);
        mockOfICommentService.Verify();
        return (AddCommentResult)((JsonResult) result).Data;
    }
}

Nova Code Camp - Articulate your Data with FluentNHibernate

As promised the code for my presentation at the Norhern Virgnia (Nova) Code Camp is attached.  For those of you who missed the presentation I will be presenting again later on this month at the CapArea.NET user's group.  I want to congratulate Jeff Schoolcraft and the rest of the organizers for a very successful code camp and hopefully we will get to do it again during the spring.

 

Download Code & PPT

 

If you attended my presentation please rate my presentation at SpeakerRate

 

Thanks for reading,

 

Roberto Hernandez