Los Techies : Blogs about software and anything tech!

Making frameworks container-aware


I’m currently knee-deep in NHibernate custom listeners, for the purpose of adding auditing to our application.  Besides current documentation being plain wrong on the subject (I’ll update on the solution in the future), I hit a rather frustrating snag around the instantiation of my custom listener.  One of the shouldn’t-be-frustrating-but-yet-it-is side-effects of committing fully to Dependency Injection and Inversion of Control Containers is all the code out there with “extension” points that don’t allow custom factory implementations.  Sure, your framework allows for custom Floogle providers.  But how does it create the IFloogleProvider implementations?

In the NHibernate code, I found this snippet that instantiates custom listeners, which are configured through an XML configuration file:

public void SetListeners(ListenerType type, string[] listenerClasses)
{
    if (listenerClasses == null || listenerClasses.Length == 0)
    {
        ClearListeners(type);
    }
    else
    {
        var listeners = (object[]) Array.CreateInstance(eventListeners.GetListenerClassFor(type), listenerClasses.Length);
        for (int i = 0; i < listeners.Length; i++)
        {
            try
            {
                listeners = Activator.CreateInstance(ReflectHelper.ClassForName(listenerClasses));
            }
            catch (Exception e)
            {
                throw new MappingException(
                    "Unable to instantiate specified event (" + type + ") listener class: " + listenerClasses, e);
            }
        }
        SetListeners(type, listeners);
    }
}

One. glaring. problem.  It uses Activator.CreateInstance to create the instance, which internally calls the default constructor.  As a developer for custom listeners, this means that we need to define a no-argument constructor:

public class AuditPreInsertUpdateEventListener : IPreUpdateEventListener, IPreInsertEventListener
{
    private readonly IUserSession _userSession;

    public AuditPreInsertUpdateEventListener(IUserSession userSession)
    {
        _userSession = userSession;
    }

    public AuditPreInsertUpdateEventListener()
        : this(ObjectFactory.GetInstance<IUserSession>())
    {
    }

When doing constructor injection, you normally don’t create this constructor.  Frameworks like ASP.NET MVC, WCF and others provide hooks for custom factories or instance providers.  Containers receive a request to instantiate a component, and the container will create the dependencies, call the right constructor and pass those dependencies in.

From a developer’s perspective, it’s very helpful not to have to define any unnecessary no-arg constructors, as it muddies code.  There is at least one solution out there, such as the Common Service Locator project.  Now, NHibernate does allow programmatic configuration, so I do have the option to create the instances myself and pump them into NHibernate.  All I’ve really done is eliminated the possibility of using XML configuration, which isn’t too fun if you’re doing things like XML manipulation as part of your deployments.

For framework developers, something like Activator.CreateInstance should be a red flag, that maybe we should provide alternate means of instantiation.  As IoC containers become more mainstream, I think we’ll see more changes in major frameworks to support containers out-of-the-box.  Until then, pointless dual constructors it is.

Kick It on DotNetKicks.com
Posted Dec 14 2008, 09:12 PM by bogardj
Filed under:

Comments

Reflective Perspective - Chris Alcock » The Morning Brew #244 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #244
on 12-15-2008 2:33 AM

Pingback from  Reflective Perspective - Chris Alcock  » The Morning Brew #244

Jonathan wrote re: Making frameworks container-aware
on 12-15-2008 6:50 AM

We've tried to avoid listeners for object creation because things can get complicated quickly - as you have discovered.

NHibernate isn't the most IoC friendly framework.  What we ended up doing was creating a private field in our repositories that looked like this:

private Action<T> initialize;

This private field served as a callback that populated our object instance - typically through setter injection (ugh!)

We used setter injection rather than constructor injection for several reasons, but the most important was to allow NHibernate to create a proxy object for us so that we gained the benefits of deferred query execution.

Here is our "load" method on our repository:

public virtual T FindById(int id)

{

 var loaded = (T)this.session.Load(this.type, id);

 this.initialize(loaded);

 return loaded;

}

In this instance "T" would be something like IContact, IAddress, IUser, etc. whereas "this.type" would be a concrete implementation for the respective interface.

We are actually performing dependency injection on our repositories to inject the session, type, and initialize objects.

In this manner we make NHibernate a bit more IoC friendly.

Dew Drop - December 15, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - December 15, 2008 | Alvin Ashcraft's Morning Dew
on 12-15-2008 8:11 AM

Pingback from  Dew Drop - December 15, 2008 | Alvin Ashcraft's Morning Dew

Dew Drop - December 15, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - December 15, 2008 | Alvin Ashcraft's Morning Dew
on 12-15-2008 8:11 AM

Pingback from  Dew Drop - December 15, 2008 | Alvin Ashcraft's Morning Dew

Arjan`s World » LINKBLOG for December 15, 2008 wrote Arjan`s World &raquo; LINKBLOG for December 15, 2008
on 12-15-2008 9:09 AM

Pingback from  Arjan`s World    » LINKBLOG for December 15, 2008

Fabio Maulo wrote re: Making frameworks container-aware
on 12-21-2008 4:29 AM

@bogardj

use this method

public void SetListeners(ListenerType type, object[] listeners)

if you want initialize NH with instance of your listeners

@Jonathan

fabiomaulo.blogspot.com/.../entities-behavior-injection.html

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright Los Techies 2008, 2009. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems