Castle Windsor IoC in an MVC application
Asked Answered
C

2

14

Prepare for a wall of code... It's a long read, but it's as verbose as I can get.

In response to Still lost on Repositories and Decoupling, ASP.NET MVC

I think I am starting to get closer to understanding this all. I'm trying to get used to using this. Here is what I have so far.

Project

Project.Web (ASP.NET MVC 3.0 RC)

  • Uses Project.Models
  • Uses Project.Persistence

Project

Project.Models (Domain Objects)

  • Membership.Member
  • Membership.IMembershipProvider

Project

Project.Persistence (Fluent nHibernate)

  • Uses Project.Models
  • Uses Castle.Core
  • Uses Castle.Windsor

  • Membership.MembershipProvider : IMembershipProvider

I have the following class in Project.Persistence

using Castle.Windsor;

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;

namespace Project.Persistence
{
    public static class IoC
    {
        private static IWindsorContainer _container;

        public static void Initialize()
        {
            _container = new WindsorContainer()
                .Install(
                    new Persistence.Containers.Installers.RepositoryInstaller()
            );
        }

        public static T Resolve<T>()
        {
            return _container.Resolve<T>();
        }
    }
}
namespace Persistence.Containers.Installers
{
    public class RepositoryInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component
                .For<Membership.IMembershipProvider>()
                .ImplementedBy<Membership.MembershipProvider>()
                .LifeStyle.Singleton
            );
        }
    }
}

Now, in Project.Web Global.asax Application_Start, I have the following code.

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);

        // Register the Windsor Container
        Project.Persistence.IoC.Initialize();
    }

Now then, in Project.Web.Controllers.MembershipController I have the following code.

    [HttpPost]
    public ActionResult Register( Web.Models.Authentication.Registration model)
    {
        if (ModelState.IsValid)
        {
            var provider = IoC.Resolve<Membership.IMembershipProvider>();
            provider.CreateUser(model.Email, model.Password);
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

So I am asking first of all..

Am I on the right track?

How can I use Castle.Windsor for my ISessionFactory

I have my SessionFactory working like this ...

namespace Project.Persistence.Factories
{
    public sealed class SessionFactoryContainer
    {
        private static readonly ISessionFactory _instance = CreateSessionFactory();

        static SessionFactoryContainer()
        { 

        }

        public static ISessionFactory Instance
        {
            get { return _instance; }
        }

        private static ISessionFactory CreateSessionFactory()
        {
            return Persistence.SessionFactory.Map(@"Data Source=.\SQLEXPRESS;Initial Catalog=FluentExample;Integrated Security=true", true);
        }
    }
}
namespace Project.Persistence
{
    public static class SessionFactory
    {
        public static ISessionFactory Map(string connectionString, bool createSchema)
        {
            return FluentNHibernate.Cfg.Fluently.Configure()
                .Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2008
                    .ConnectionString(c => c.Is(connectionString)))
                    .ExposeConfiguration(config =>
                    {
                        new NHibernate.Tool.hbm2ddl.SchemaExport(config)
                            .SetOutputFile("Output.sql")
                            .Create(/* Output to console */ false, /* Execute script against database */ createSchema);
                    })
                    .Mappings(m =>
                    {
                        m.FluentMappings.Conventions.Setup(x =>
                        {
                            x.AddFromAssemblyOf<Program>();
                            x.Add(FluentNHibernate.Conventions.Helpers.AutoImport.Never());
                        });

                        m.FluentMappings.AddFromAssemblyOf<Mapping.MembershipMap>();
                    }).BuildSessionFactory();
        }

So basically, within my Project.Persistence layer, I call the SessionFactory like this..

var session = SessionFactoryContainer.Instance.OpenSession()

Am I even getting close to doing this right? I'm still confused - I feel like the ISessionFactory should be part of Castle.Windsor, but I can't seem to figure out how to do that. I'm confused also about the way I am creating the Repository in the Controller. Does this mean I have to do all of the 'mapping' each time I use the Repository? That seems like it would be very resource intensive.

Colet answered 9/12, 2010 at 17:28 Comment(0)
M
31

Firstly some conceptual details. In an ASP.NET MVC application the typical entry point for a page request is a controller. We want the Inversion of Control container to resolve our controllers for us, because then any dependencies that the controllers have can also be automatically resolved simply by listing the dependencies as arguments in the controllers' constructors.

Confused yet? Here's an example of how you'd use IoC, after it is all set up. I think explaining it this way makes things easier!

public class HomeController : Controller
{
    // lets say your home page controller depends upon two providers
    private readonly IMembershipProvider membershipProvider;
    private readonly IBlogProvider blogProvider;

    // constructor, with the dependencies being passed in as arguments
    public HomeController(
                IMembershipProvider membershipProvider,
                IBlogProvider blogProvider)
    {
        this.membershipProvider = membershipProvider;
        this.blogProvider = blogProvider;
    }

    // so taking your Registration example...
    [HttpPost]
    public ActionResult Register( Web.Models.Authentication.Registration model)
    {
        if (ModelState.IsValid)
        {
            this.membershipProvider.CreateUser(model.Email, model.Password);
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }
}

Note that you have not had to do any resolving yourself, you have just specified in the controller what the dependencies are. Nor have you actually given any indication of how the dependencies are implemented - it's all decoupled. It's very simple there is nothing complicated here :-)

Hopefully at this point you are asking, "but how does the constructor get instantiated?" This is where we start to set up your Castle container, and we do this entirely in the MVC Web project (not Persistence or Domain). Edit the Global.asax file, setting Castle Windsor to act as the controller factory:

protected void Application_Start()
{
//...   
    ControllerBuilder.Current
        .SetControllerFactory(typeof(WindsorControllerFactory));
}

...and define the WindsorControllerFactory so that your controllers are instantiated by Windsor:

/// Use Castle Windsor to create controllers and provide DI
public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IWindsorContainer container;

    public WindsorControllerFactory()
    {
        container = ContainerFactory.Current();
    }

    protected override IController GetControllerInstance(
        RequestContext requestContext,
        Type controllerType)
    {
        return (IController)container.Resolve(controllerType);
    }
}

The ContainerFactory.Current() method is static singleton that returns a configured Castle Windsor container. The configuration of the container instructs Windsor on how to resolve your application's dependencies. So for example, you might have a container configured to resolve the NHibernate SessionFactory, and your IMembershipProvider.

I like to configure my Castle container using several "installers". Each installer is responsible for a different type of dependency, so I'd have a Controller installer, an NHibernate installer, a Provider installer for example.

Firstly we have the ContainerFactory:

public class ContainerFactory
{
    private static IWindsorContainer container;
    private static readonly object SyncObject = new object();

    public static IWindsorContainer Current()
    {
        if (container == null)
        {
            lock (SyncObject)
            {
                if (container == null)
                {
                    container = new WindsorContainer();
                    container.Install(new ControllerInstaller());
                    container.Install(new NHibernateInstaller());
                    container.Install(new ProviderInstaller());
                }
            }
        }
        return container;
    }
}

...and then we need each of the installers. The ControllerInstaller first:

public class ControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            AllTypes
                .FromAssembly(Assembly.GetExecutingAssembly())
                .BasedOn<IController>()
                .Configure(c => c.Named(
                    c.Implementation.Name.ToLowerInvariant()).LifeStyle.PerWebRequest));
    }
}

... and here is my NHibernateInstaller although it is different to yours, you can use your own configuration. Note that I'm reusing the same ISessionFactory instance every time one is resolved:

public class NHibernateInstaller : IWindsorInstaller
{
    private static ISessionFactory factory;
    private static readonly object SyncObject = new object();

    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        var windsorContainer = container.Register(
            Component.For<ISessionFactory>()
                .UsingFactoryMethod(SessionFactoryFactory));
    }

    private static ISessionFactory SessionFactoryFactory()
    {
        if (factory == null)
        {
            lock (SyncObject)
            {
                if (factory == null)
                {
                    var cfg = new Configuration();
                    factory = cfg.Configure().BuildSessionFactory();
                }
            }
        }

        return factory;
    }
}

And finally you'll want to define your ProvidersInstaller:

public class ProvidersInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        var windsorContainer = container
            .Register(
                Component
                    .For<IMembershipProvider>()
                    .ImplementedBy<SubjectQueries>())
            .Register(
                Component
                    .For<IBlogProvider>()
                    .ImplementedBy<SubjectQueries>());

            // ... and any more that your need to register
    }
}

This should be enough code to get going! Hopefully you're still with me as the beauty of the Castle container becomes apparent very shortly.

When you define your implementation of your IMembershipProvider in your persistence layer, remember that it has a dependency on the NHibernate ISessionFactory. All you need to do is this:

public class NHMembershipProvider : IMembershipProvider
{
    private readonly ISessionFactory sessionFactory;

    public NHMembershipProvider(ISessionFactory sessionFactory)
    {
        this.sessionFactory = sessionFactory;
    }
}

Note that because Castle Windsor is creating your controllers and the providers passed to your controller constructor, the provider is automatically being passed the ISessionFactory implementation configured in your Windsor container!

You never have to worry about instantiating any dependencies again. Your container does it all automatically for you.

Finally, note that the IMembershipProvider should be defined as part of your domain, as it is defining the interface for how your domain behaviours. As noted above, the implementation of your domain interfaces which deal with databases are added to the persistence layer.

Mailand answered 9/12, 2010 at 20:37 Comment(14)
Yes, this makes much more sense! Thank you! The fact that you are supposed to establish the Controllers to be created by Castle.Windsor is what really threw me. It just never made sense to me where it was supposed to come together, and the examples I kept finding just didn't say that explicitly. Thank you very much - this will require a lot of tweaking to fit into my application, but it is much clearer than anything I've run across.Colet
I have to say that the Castle documentation isn't the easiest to navigate too. It really needs to be turned on it's head for newbies to understand. I explain it this way because I am a relative newbie myself, and what is obvious to the core team, just isn't to new comers. I'm glad I was able to help!Mailand
As Mauricio pointed out, it is free - as such there will always be things that need to be worked on. I understand that certain aspects of the developer platform require certain things to be understood, but I do feel that a lot of things revolving around DI are not very 'collected'. It feels like a scavenger hunt to discover it, and to understand it in general.Colet
Hrnm, I am experiencing the error "No component for supporting the service Project.Web.Controllers.HomeController was found". I've done pretty much exactly as you posted, placing it all in my Persistence assembly, and calling it in my Web assembly. Do I need to add something to my controllers extra?Colet
I believe that exception is occurring because HomeController is not registered in the container properly. Try putting a breakpoint in your ContainerFactory, and examine the contents of the container. You should see a componet for each Controller in your MVC app assuming your ControllerInstaller is working correctly.Mailand
Yeah, it isn't showing the controllers. I'm not sure what I am doing wrong. The code I am using is verbatim your own for the ControllerInstaller.Colet
I missed earlier that you'd put all the code in the Persistence assembly. Only your NHMembershipProvider should be in the Persistence layer. All of the Castle Windsor container setup code should be in your web project. Can you step through the container setup using a debugger? Is the ControllerInstaller.Install() method being calledMailand
@Stacey, @Spolto: if the docs need improving, please do so. stw.castleproject.org is an open wiki. We (the Castle team) obviously suffer from so-called "curse of knowledge" (37signals.com/svn/posts/213-the-curse-of-knowledge), so an outsider could help a lot with this.Rifling
@Stacey: about having all knowledge collected in one place: you might wanna get Mark Seemann's book: manning.com/seemannRifling
@Mauricio: Do you think that it sounds like I get IoC? I've not used it much, but I figured trying to write about it would help crystallise my knowledge. Do you think a description like the one given here would be helpful on the Castle Project wiki?Mailand
@Mauricio: All programmers suffer from this, not the Castle team. I am not qualified to speak on behalf of the entire programming community, but I have learned that most programmers forget the time when they had no idea how to get something done that they had to do, and stayed up for days on end doing web searches and pulling hair out. I never intended to make you, or the Castle team, feel attacked, and I am very sorry if I did. I am one rookie programmer, with no peers in my workplace to edify me. Stackoverflow is quite literally the closest I have ever come to speaking to another programmerColet
-> beyond college, which did a poor job of preparing me for the real world.Colet
@Spolto: Why does it need to be in the Web Assembly? Is there any particular reason?Colet
@Stacey: It's common to want to be able to configure different applications to use various different dependencies. At work we have hundreds of different library assemblies and each of our applications depends upon in different combinations of the libraries. It is each applications own responsibility to define it's own Castle container such that the relevant components for that application are registered.Mailand
R
5

Avoid using a static IoC class like this. By doing this you're using the container as a service locator, so you won't achieve the full decoupling of inversion of control. See this article for further explanations about this.

Also check out Sharp Architecture, which has best practices for ASP.NET MVC, NHibernate and Windsor.

If you have doubts about the lifecycle of the container itself, see Usage of IoC Containers; specifically Windsor

Rifling answered 9/12, 2010 at 19:27 Comment(26)
I don't understand. If I am constantly creating new instances of the locator, then I am wasting resources. What is the point of having Singleton Lifecycles if I am disposing the objects after being used?Colet
Also, the link you give doesn't really help much. It just explains why it is bad, but doesn't really offer anything more intelligent. I thought the goal was to get away from needing constructor dependency? So now that's the opposite of the goal of IoC? The static IoC class I'm using is almost right out of the Castle.Windsor documentation itself! What's the right answer, then?Colet
"If I am constantly creating new instances of the locator, then I am wasting resources." -> no, that doesn't happen.Rifling
"I thought the goal was to get away from needing constructor dependency" -> that's not the goal... where did you read that?Rifling
"The static IoC class I'm using is almost right out of the Castle.Windsor documentation itself" -> can you tell me where does it say this?Rifling
If passing objects through my constructors is what I wanted to do, why in the world would I care about containers for them? That doesn't make any sense to me. Now I'm depending on a fifth element to exist just to make code run? I thought the purpose of DI/IoC was to pass instantiation and lifecycle of objects to a container so that it handled that, and all I needed to do was tell the container what resource I need to work with and it takes care of the rest. If I'm just passing things through constructors, why do I even want an IoC Container?Colet
Sure. stw.castleproject.org/Windsor.MainPage.ashx just look at their main page. Same principle. Instantiating a resolver and calling for it. stw.castleproject.org/Windsor.Three-Calls-Pattern.ashx makes it clear that you want to do the configuration in one place, and then just call upon that instance when needed. stw.castleproject.org/Windsor.Installers.ashx shows more about installing things - why in the world would I want this code to run everytime I needed a resource? That seems even more useless than just avoiding IoC altogether. I'll list other resources next comment.Colet
dimecasts.net/Casts/ByTag/IoC the link seems to be down right now, but it was up last night when I was working. These tutorials even use the EXACT pattern of a static IoC resolver. Who am I supposed to believe then? Conflicting information from different developers. (update, exact link : dimecasts.net/Casts/CastDetails/41 )Colet
Also, you say that I'm not wasting Resources if I am constantly instantiating the locator. How? That makes no sense. When you create it, it does all of the mapping... I don't see how this isn't wasting resources, and how it is preserving different repository lifecycle. Basics of C#/.NET are pretty clear that when you use the 'new' operator, you're allocating something to memory, and running it.Colet
@Stacey: don't know about dimecasts.net, but nowhere on stw.castleproject.org mentions a static IoC class.Rifling
@Stacey: BTW dimecasts.net is not official documentation. The Castle team can't be responsible for things people say all over the internet.Rifling
@Stacey: it seems there is some confusion here about the lifecycle of components vs the lifecycle of the container itself. For the lifecycle of the container see #367678Rifling
That's fair enough - but you have to see my side of the problem, too. I'm being given different information by different developers, neither of which are 'official', and both make sense, and neither makes complete sense. Who am I supposed to believe? The link you gave me does show some flaws with the static pattern, but on the same token it doesn't offer any solution that doesn't have the same flaws that I could really find. See my confusion? I'm pretty sure I could find a third developer that could tell me both are wrong, and that a different method is the right way to go.Colet
@Stacey: BTW try to be nice with people that give you help for free. Also, I'm part of the Castle team, so I think I know what I'm talking about here.Rifling
Please do not translate confusion into hostility. I'm part of the 'developers trying to learn your product' market, so I too know quite well what I am talking about. I'm simply stating that everything you're showing me doesn't really make a lot of sense based on what I have seen so far. I did not accuse, insult, or deride you for it. I've shown you my resources and the results garnered from them. User feedback for free is as important as support for free. I am trying to understand them, that can only happen through exchange of information. -->Colet
I am simply saying "I am being told different things, I really don't know who to believe.". I apologize if you took my responses as hostility, but it was never intended that way. You presented data, I refuted what I did not understand, and why I did not understand it. This was my only intention.Colet
Your link at #367678 looks like it does touch on part of what I am confused about, but unfortunately most of the links in your post are broken, so I'm unable to see them. Yes, I am very much confused about the difference in component lifecycle and container lifecycle. My understanding was that Windsor was like an Observer model and maintained the state of my Repositories independent of my View system or Persistence system.Colet
@Stacey: fixed links in #367678Rifling
@Stacey: Windsor is not an Observer.Rifling
My answer also has a static container and I could move that into my global.asax file as a private container. While the container is public, I'm not using it as a service locator.Mailand
@Spolto: it's not the same, it's not a static gateway pattern.Rifling
@Mauricio: One last word of apology, looking back at my comments, I can see why you would take it as hostility. I was completely wrong in my approach, and you didn't deserve that at all. The windsor documentation isn't good from my perspective, but that doesn't mean it isn't good from the perspective of someone who knows what they are doing. I hope you can forgive me for the approach I took.Colet
@Stacey: no worries, I know things can be frustrating sometimes.Rifling
I'm still having trouble with the ControllerInstaller in #4404761 - It just doesn't seem to be wanting to cooperate.Colet
Dear Heavens, I finally got it to work and this is great! I think I am finally starting to see how it decouples things. It doesn't decouple the .dll files, it decouples the usage of the code. I can have my Web Project always use the same Repository code, and change the repository code as needed.Colet
@Colet RE: "The windsor documentation isn't good from my perspective" (yes, I know it's been a year). Could you elaborate on what part of the documentation you found unhelpful and/or confusing and perhaps suggest what in your opinion should be emphasized more/less or re-written to make the point of it crystal clear? Or better yet (since it's an open wiki) go ahead and just improve it. ThanksPhinney

© 2022 - 2024 — McMap. All rights reserved.