Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

Replacing Spring with Ninject

Sunday, 19 December, 2010 @ 4:29 PM < Adam Boddington
Tags: Building Neno, Inversion of Control, Ninject, Spring

This is post #28 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.

When I rolled out my earlier page implementation to my hosting server, a new bug popped up. Spring was complaining in Application_Start again, and this time it specifically mentioned URL rewriting. Oh no... This happened to me before with DotNetOpenAuth and URL rewriting. Would I have to code up another hack?

I try to configure my development, testing and production environments the same way as much as possible to avoid these kinds of rollout surprises. I haven't bothered doing it for Neno yet, but two strikes so far, I think I will need to invest the time soon.

Sproing!

Googling found various forum posts about the same (or a similar) issue. The thread mentions a fix coming soon in 1.3.1, but people were still asking about the fix and the 1.3.1 release date six months later. It was late, so rather than fluff about trying to research this anymore, I decided to add yet another fudge to Global.asax to get my site working again. If you remember I already had a fudge in place.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes, GetPages());
}

private IEnumerable<Page> GetPages()
{
    // Fudge this to get it working. I can't access request scoped objects here.
    using (ISession session = Container.Get<SessionFactoryWrapper>().OpenSession())
        return session.Query<Page>()
            .ToArray();
}

This got further hacked into...

private IEnumerable<Page> GetPages()
{
    // Unfortunately the Spring container is unavailable in Application_Start in a
    // path rewritten environment. Instantiate SessionFactoryWrapper directly.
    // Unfortunately this means NHibernate is configured twice. Once here, and once
    // later through Spring.

    var sessionFactoryWrapper = new SessionFactoryWrapper();

    using (ISession session = sessionFactoryWrapper.OpenSession())
        return session.Query<Page>()
            .ToArray();
}

It's expensive to have NHibernate start up twice, but it worked. It nagged and nagged at me though, so today I decided to get rid of the hackery altogether by looking at another IOC/DI framework.

Enter the Dragon

There are a bunch of great IOC/DI frameworks out there. I chose Ninject for no other reason than it was the first one that came to mind. (The power of cute ninjas and a memorable name!) A quick test found Ninject worked in Application_Start without needing any special consideration -- so I donned the black pyjamas and ran with it.

My Container

Switching IOC/DI frameworks would normally be a massive pain in the ninjass. But if you recall, I wrapped Spring up in my own container, mostly to improve the interface, but also in case I wanted to get rid of it one day. A weird thing to be prepared for, considering the years of bug free service Spring has provided me. But IOC/DI is all about decoupling dependencies, and decoupling the decoupler is not a bad approach to take. One that is paying off today.

My interface looks like this.

namespace StackingCode.Moja.InversionOfControl
{
    public interface IContainerProvider
    {
        void Configure<T>(T target);
        void Configure<T>(T target, string name);
        T Get<T>();
        T Get<T>(string name);
        T Get<T>(object[] args);
        T Get<T>(string name, object[] args);
    }
}

The only method I use in Neno is the parameterless Get<T> method, but I have at one time or another used variations of the other methods in the past. At first glance, Ninject doesn't support all of them, but it does support the important one.

namespace StackingCode.Moja.InversionOfControl.Ninject
{
    public class ContainerProvider : IContainerProvider
    {
        // ...

        private IKernel Kernel { get; set; }

        #region Implementation of IContainerProvider

        public void Configure<T>(T target)
        {
            Kernel.Inject(target);
        }

        public void Configure<T>(T target, string name)
        {
            throw new NotImplementedException();
        }

        public T Get<T>()
        {
            return Kernel.Get<T>();
        }

        public T Get<T>(string name)
        {
            return Kernel.Get<T>(name);
        }

        public T Get<T>(object[] args)
        {
            throw new NotImplementedException();
        }

        public T Get<T>(string name, object[] args)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

Note, the above implementation is going into its own assembly, StackingCode.Moja.InversionOfControl.Ninject, to remove the Ninject dependency from Moja the same way Spring was.

Ninject doesn't have a universal container, or a context from which to pull a container. It looks like I need to make my own container (called a kernel), configure it, and then presumably hang on to it somewhere. That's a small problem for my container -- there's no method or property for passing in a kernel.

My container is static however, it's accessible from everywhere. What if it creates the kernel itself and hangs on to it. But how do I get access to the kernel to configure it? By using the Get<T> method on the container of course.

namespace StackingCode.Moja.InversionOfControl.Ninject
{
    public class ContainerProvider : IContainerProvider
    {
        public ContainerProvider()
        {
            // Create a root container.
            Kernel = new StandardKernel();
            Kernel.Bind<IKernel>().ToConstant(Kernel);
        }

        private IKernel Kernel { get; set; }

        // ...

        public T Get<T>()
        {
            return Kernel.Get<T>();
        }

        // ...
    }
}

The kernel is created and gains a reference to itself, making the kernel accessible to everything that can see the container. This is only really needed for configuration operations though -- normal retrievals will be routed through the container methods instead. (See the Global.asax changes below for an example.)

Lost in Translation

My Spring configuration looks like this.

<spring>
    <context>
        <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
        <!-- Services -->
        <object name="PageService" type="StackingCode.Neno.Services.PageService, StackingCode.Neno" scope="request">
            <property name="Context" ref="Context" />
            <property name="PageRepository" ref="PageRepository" />
        </object>
        <object name="PostService" type="StackingCode.Neno.Services.PostService, StackingCode.Neno" scope="request">
            <property name="Context" ref="Context" />
            <property name="PostRepository" ref="PostRepository" />
            <property name="CommentRepository" ref="CommentRepository" />
        </object>
        <object name="TagService" type="StackingCode.Neno.Services.TagService, StackingCode.Neno" scope="request">
            <property name="Context" ref="Context" />
            <property name="TagRepository" ref="TagRepository" />
        </object>
        <object name="UserService" type="StackingCode.Neno.Services.Web.UserService, StackingCode.Neno.Services.Web" scope="request">
            <property name="Context" ref="Context" />
            <property name="UserRepository" ref="UserRepository" />
        </object>
        <!-- Repositories -->
        <object name="CommentRepository" type="StackingCode.Neno.Repositories.NHibernate.CommentRepository, StackingCode.Neno.Repositories.NHibernate" scope="request">
            <property name="Context" ref="Context" />
            <property name="Session" ref="Session" />
        </object>
        <object name="PageRepository" type="StackingCode.Neno.Repositories.NHibernate.PageRepository, StackingCode.Neno.Repositories.NHibernate" scope="request">
            <property name="Context" ref="Context" />
            <property name="Session" ref="Session" />
        </object>
        <object name="PostRepository" type="StackingCode.Neno.Repositories.NHibernate.PostRepository, StackingCode.Neno.Repositories.NHibernate" scope="request">
            <property name="Context" ref="Context" />
            <property name="Session" ref="Session" />
        </object>
        <object name="TagRepository" type="StackingCode.Neno.Repositories.NHibernate.TagRepository, StackingCode.Neno.Repositories.NHibernate" scope="request">
            <property name="Context" ref="Context" />
            <property name="Session" ref="Session" />
        </object>
        <object name="UserRepository" type="StackingCode.Neno.Repositories.NHibernate.UserRepository, StackingCode.Neno.Repositories.NHibernate" scope="request">
            <property name="Context" ref="Context" />
            <property name="Session" ref="Session" />
        </object>
        <!-- Moja -->
        <object name="Context" type="StackingCode.Moja.Repositories.NHibernate.Context, StackingCode.Moja.Repositories.NHibernate" scope="request">
            <property name="Session" ref="Session" />
        </object>
        <object name="Session" factory-object="SessionFactory" factory-method="OpenSession" scope="request">
            <constructor-arg>
                <list>
                    <ref object="StaleInterceptor" />
                    <ref object="InvalidInterceptor" />
                </list>
            </constructor-arg>
        </object>
        <object name="StaleInterceptor" type="StackingCode.Moja.Repositories.NHibernate.Interceptors.StaleInterceptor, StackingCode.Moja.Repositories.NHibernate" scope="request" />
        <object name="InvalidInterceptor" type="StackingCode.Moja.Repositories.NHibernate.Interceptors.InvalidInterceptor, StackingCode.Moja.Repositories.NHibernate" scope="request" />
        <object name="SessionFactory" type="StackingCode.Moja.Repositories.NHibernate.SessionFactoryWrapper, StackingCode.Moja.Repositories.NHibernate" />
    </objects>
</spring>

Ninject has a variety of configuration options, the most common of which is to put it all in one or more derivatives of the NinjectModule class. Ninject can do property injection, but the elegant usage is in constructor injection. I've favoured property injection in the past because it requires less code, but I don't mind switching to constructor injection -- being explicit about dependencies has its advantages.

Here's the Ninject translation.

public class NinjectModule : Ninject.Modules.NinjectModule
{
    #region Overrides of NinjectModule

    public override void Load()
    {
        // Services

        Bind<IPageService>().To<PageService>().InRequestScope();
        Bind<IPostService>().To<PostService>().InRequestScope();
        Bind<ITagService>().To<TagService>().InRequestScope();
        Bind<IUserService>().To<UserService>().InRequestScope();

        // Repositories

        Bind<ICommentRepository>().To<CommentRepository>().InRequestScope();
        Bind<IPageRepository>().To<PageRepository>().InRequestScope();
        Bind<IPostRepository>().To<PostRepository>().InRequestScope();
        Bind<ITagRepository>().To<TagRepository>().InRequestScope();
        Bind<IUserRepository>().To<UserRepository>().InRequestScope();

        // Moja

        Bind<IContext>().To<Context>().InRequestScope();

        Bind<ISession>()
            .ToMethod(context => context.Kernel.Get<SessionFactoryWrapper>().OpenSession(
                new IInterceptor[]
                {
                    context.Kernel.Get<StaleInterceptor>(),
                    context.Kernel.Get<InvalidInterceptor>()
                }))
            .InRequestScope();

        Bind<StaleInterceptor>().ToSelf().InRequestScope();
        Bind<InvalidInterceptor>().ToSelf().InRequestScope();
        Bind<SessionFactoryWrapper>().ToSelf().InSingletonScope(); // Singleton
    }

    #endregion
}

The only other thing to worry about from a configuration standpoint is to add the Ninject.OnePerRequestModule to my module collection. While not required, it helps with cleaning up after requests are done.

Application Start

The last thing to do is replace my hackery in Global.asax.

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    Container.Get<IKernel>().Load(new NinjectModule());
    RegisterRoutes(RouteTable.Routes, Container.Get<IPageService>().GetPages());
}

As simple as that! I'm not sure how Ninject lets me get into request scoped objects in Application_Start, but it does, so I'm not complaining. All I really care about is that a new instance of ISession is being served up to each request, and the SessionFactoryWrapper is only created once. Strategic breakpoints confirm that they are.

Ninject makes it all so easy.

EDIT: Since swapping Spring for Ninject, I found out Spring released version 1.3.1 a week ago. Doh! While it may have fixed the URL rewrite problem I was having, I already had a Spring hack in place which Ninject removed the need for. I'm happy with the switch.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
BLOBs, NHibernate and SQL Server

Newer
Prettify and Internet Explorer

Older
BLOBs, NHibernate and SQL Server

Newer
Prettify and Internet Explorer

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM