Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I connect the various pieces of my Web API Castle Windsor DI code?

How do I connect the various pieces of my Web API Castle Windsor DI code so that the Controller's routing selects the correct interface implementation?

Note: After several false starts/dead ends and partial victories (here and here and here), I am going to bountify this ASAP for the maximum 500 points. But I'm only going to award a really good answer - IOW, one that is clear enough that I can understand it and "plug it in" to my project so that I can hook a given concrete class to a particular Controller.

Here goes nothing: I have a Web API ("MVC") project. Really, though, this server project has no "V" (View), so maybe a better acronym would be MRC (Model/Repository/Controller).

At any rate, I'm trying to add DI to it using Castle Windsor.

I grok, and dig, the concept of swapping out concrete classes via constructor interface args. Just how to implement this functionality, though,

has been a beast I've been wrestling with, and I'm quite bruised and bloody at present, with mussed-up hair and mud-encrusted nostrils to boot.

I have, I think, most of the code I need - to start with, anyway. With DI in mind, I've now got a "DIPlumbing" folder, and a "DIInstallers" folder. The "DIPlumbing" folder contains two classes: WindsorCompositionRoot.cs, and WindsorControllerFactory.cs.

The "DIInstallers" folder has, for now, three files, namely ISomethingProvider.cs, SomethingProvider.cs, and SomethingProviderInstaller.cs

SomethingProviderInstaller seems to be key in connecting the interfaces/classes in DIInstallers to the stuff in the DIPlumbing folder.

(I have also modified Global.asax.cs to replace the default controller routing business with the Castle Windsor replacement).

But I'm confused as to what classes I should be placing in the DIInstallers folder. Are these supposed to take the place of my Repositories (which likewise have an interface and a concrete class that implements that interface for each model)? IOW, should I basically move my Repository code into the DIInstallers folder - and then get rid of the IRepository and Repository units?

This, of course, would cause necessary changes to be made in the Controller classes, which as of now reference Repository classes.

Or do the Repositories and DIInstallers classes coexist? If so, what is the connection/affiliation between the Controllers, Installers, and Repositories?

It seems the more I read up on DI and Castle Windsor, the more confused I get. I don't know if I'm too dense for it, or if I'm trying to make it harder than it is, or if conflicting styles of using/presenting it are the problem. The bottom line is: I'm stuck in quicksand and need Johnny Quest to extend a sturdy bamboo rod.

The best answer of all, perhaps, and probably too much to ask for, would be a visual representation of how all these components - Controllers, Models, Repositories, Installers, Global.asax.cs, composition roots, factories, providers, etc., relate to each other.

For purposes of "full disclosure," I will add what I hope are the key elements of my code below to show what I've got and how it (hopefully) connects to each other.

Composition Root:

public class WindsorCompositionRoot : IHttpControllerActivator
{
    private readonly IWindsorContainer container;

    public WindsorCompositionRoot(IWindsorContainer container)
    {
        this.container = container;
    }

    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller =
            (IHttpController)this.container.Resolve(controllerType);

        request.RegisterForDispose(
            new Release(
                () => this.container.Release(controller)));

        return controller;
    }

    private class Release : IDisposable
    {
        private readonly Action release;

        public Release(Action release)
        {
            this.release = release;
        }

        public void Dispose()
        {
            this.release();
        }
    }
}

Controller Factory:

public class WindsorControllerFactory : DefaultControllerFactory
{
    private readonly IKernel kernel;

    public WindsorControllerFactory(IKernel kernel)
    {
        this.kernel = kernel;
        //According to my understanding of http://docs.castleproject.org/Windsor.Typed-Factory-Facility.ashx, I might need this:
        kernel.AddFacility<TypedFactoryFacility>();
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
        }
        return (IController)kernel.Resolve(controllerType);
    }

    public override void ReleaseController(IController controller)
    {
        kernel.ReleaseComponent(controller);
    }

// Note: The "Something" below will hopefully eventually be "Departments" and then other classes now represented in Models and their corresponding Repositories and Controllers

ISomethingProvider:

public interface ISomethingProvider
{
    // These are placeholder methods; I don't know which I will need yet...
    //bool Authenticate(string username, string password, bool createPersistentCookie);
    //void SignOut();
}

SomethingProvider:

public class SomethingProvider : ISomethingProvider
{
    // TODO: Implement methods in ISomethingProvider, once they have been added
}

SomethingProviderInstaller:

public class SomethingProviderInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
                               .BasedOn(typeof(ISomethingProvider))
                               .WithServiceAllInterfaces());
        // From http://app-code.net/wordpress/?p=676; see also http://devlicio.us/blogs/krzysztof_kozmic/archive/2009/12/24/castle-typed-factory-facility-reborn.aspx
        container.AddFacility<TypedFactoryFacility>();
        container.Register(Component.For<IMyFirstFactory>().AsFactory()); 
    }
}

Controller:

public class DepartmentsController : ApiController
{
    private readonly IDepartmentRepository _deptsRepository;

    public DepartmentsController(IDepartmentRepository deptsRepository)
    {
        if (deptsRepository == null)
        {
            throw new ArgumentNullException("deptsRepository is null");
        }
        _deptsRepository = deptsRepository;
    }

    public int GetCountOfDepartmentRecords()
    {
        return _deptsRepository.Get();
    }

    public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch)
    {
        return _deptsRepository.Get(ID, CountToFetch);
    }
. . .
}

IRepository:

public interface IDepartmentRepository
{
    int Get();
    IEnumerable<Department> Get(int ID, int CountToFetch);
}

Repository:

public class DepartmentRepository : IDepartmentRepository
{
    private readonly List<Department> departments = new List<Department>();

    public DepartmentRepository()
    {
        using (var conn = new OleDbConnection(
            @"Provider=Microsoft.ACE.OLEDB.12.0;[bla]"))
        {
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = "SELECT td_department_accounts.dept_no, IIF(ISNULL(t_accounts.name),'No Name provided',t_accounts.name) AS name FROM t_accounts INNER JOIN td_department_accounts ON t_accounts.account_no = td_department_accounts.account_no ORDER BY td_department_accounts.dept_no";
                cmd.CommandType = CommandType.Text;
                conn.Open();
                int i = 1;
                using (OleDbDataReader oleDbD8aReader = cmd.ExecuteReader())
                {
                    while (oleDbD8aReader != null && oleDbD8aReader.Read())
                    {
                        int deptNum = oleDbD8aReader.GetInt16(0);
                        string deptName = oleDbD8aReader.GetString(1);
                        Add(new Department { Id = i, AccountId = deptNum, Name = deptName });
                        i++;
                    }
                }
            }
        }
    }

    public int Get()
    {
        return departments.Count;
    }

    private Department Get(int ID) // called by Delete()
    {
        return departments.First(d => d.Id == ID);
    }

    public IEnumerable<Department> Get(int ID, int CountToFetch)
    {
        return departments.Where(i => i.Id > ID).Take(CountToFetch);
    }
. . .
}

Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication
{
    private static IWindsorContainer container;

    protected void Application_Start()
    {

        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        BootstrapContainer();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    private static void BootstrapContainer()
    {
        container = new WindsorContainer().Install(FromAssembly.This());
        var controllerFactory = new WindsorControllerFactory(container.Kernel);

        ControllerBuilder.Current.SetControllerFactory(controllerFactory);

        GlobalConfiguration.Configuration.Services.Replace(
            typeof(IHttpControllerActivator), new WindsorCompositionRoot(container));
    }

    protected void Application_End()
    {
        container.Dispose();
    }

UPDATE

In trying to run the server, so that it could test it with Fiddler2 to see just what is being passed around, it failed on this line in WindsorControllerFactory:

public WindsorControllerFactory(IKernel kernel)
{
    this.kernel = kernel;
    kernel.AddFacility<TypedFactoryFacility>(); <-- throws exception here
}

...with "System.ArgumentException was unhandled by user code HResult=-2147024809 Message=Facility of type 'Castle.Facilities.TypedFactory.TypedFactoryFacility' has already been registered with the container. Only one facility of a given type can exist in the container. Source=Castle.Windsor StackTrace: at Castle.MicroKernel.DefaultKernel.AddFacility(String key, IFacility facility) at Castle.MicroKernel.DefaultKernel.AddFacility(IFacility facility) at Castle.MicroKernel.DefaultKernel.AddFacilityT at HandheldServer.DIPlumbing.WindsorControllerFactory..ctor(IKernel kernel) in c:\HandheldServer\HandheldServer\DIPlumbing\WindsorControllerFactory.cs:line 28 at HandheldServer.WebApiApplication.BootstrapContainer() in c:\HandheldServer\HandheldServer\Global.asax.cs:line 69 at HandheldServer.WebApiApplication.Application_Start() in c:\HandheldServer\HandheldServer\Global.asax.cs:line 39"

UPDATE 2

In response to Cristiano's answer:

So are you saying I should add the following two files to my Installers folder (I do have a DIInstallers folder already)

PlatypusInstallerFactory.cs:

public class PlatypusInstallerFactory : InstallerFactory
{
    public override IEnumerable<Type> Select(IEnumerable<Type> installerTypes)
    {
        var windsorInfrastructureInstaller = installerTypes.FirstOrDefault(it => it == typeof(WindsorInfrastructureInstaller));

        var retVal = new List<Type>();
        retVal.Add(windsorInfrastructureInstaller);
        retVal.AddRange(installerTypes
            .Where(it =>
                typeof(IWindsorInstaller).IsAssignableFrom(it) &&
                !retVal.Contains(it)
                ));

        return retVal;
    }
}

WindsorInfrastructureInstaller.cs:

public class WindsorInfrastructureInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.AddFacility<TypedFactoryFacility>();
    }
}

In your global.asax you'll create&use you installer factory as following

   var installerFactory = new PlatypusInstallerFactory();
   container.Install(FromAssembly.This(installerFactory));

If yes, what will that do for me? Does the above automagically register my Controller and/or Repository classes?

UPDATE 3

I am now using a lot of code from [http://blog.kerbyyoung.com/2013/01/setting-up-castle-windsor-for-aspnet.html#comment-form]

The key parts are, I think:

global.asax.cs:

private static IWindsorContainer _container;

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

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    ConfigureWindsor(GlobalConfiguration.Configuration);
}

public static void ConfigureWindsor(HttpConfiguration configuration)
{
    _container = new WindsorContainer();
    _container.Install(FromAssembly.This());
    _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
    var dependencyResolver = new WindsorDependencyResolver(_container);
    configuration.DependencyResolver = dependencyResolver;
}  

WindsorDependencyResolver.cs:

namespace HandheldServer
{
    public class WindsorDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
    {
        private readonly IWindsorContainer _container;

        public WindsorDependencyResolver(IWindsorContainer container)
        {
            _container = container;
        }

        public IDependencyScope BeginScope()
        {
            return new WindsorDependencyScope(_container);
        }

        public object GetService(Type serviceType)
        {
            if (!_container.Kernel.HasComponent(serviceType))
            {
                return null;
            }
            return this._container.Resolve(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            if (!_container.Kernel.HasComponent(serviceType))
            {
                return new object[0];
            }

            return _container.ResolveAll(serviceType).Cast<object>();
        }

        public void Dispose()
        {
            _container.Dispose();
        }
    }

    public class WindsorDependencyScope : IDependencyScope
    {
        private readonly IWindsorContainer _container;
        private readonly IDisposable _scope;

        public WindsorDependencyScope(IWindsorContainer container)
        {
            this._container = container;
            this._scope = container.BeginScope(); 
        }

        public object GetService(Type serviceType)
        {
            if (_container.Kernel.HasComponent(serviceType))
            {
                return _container.Resolve(serviceType);
            }
            else
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return this._container.ResolveAll(serviceType).Cast<object>();
        }

        public void Dispose()
        {
            this._scope.Dispose();
        }
    }

    public class ApiControllersInstaller : IWindsorInstaller
    {
        public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
        {
            container.Register(Classes.FromThisAssembly() // should it be Types instead of Classes?
             .BasedOn<ApiController>()
             .LifestylePerWebRequest());
        }
    }

    // This idea from https://github.com/argeset/set-locale/blob/master/src/client/SetLocale.Client.Web/Configurations/IocConfig.cs
    public class ServiceInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(
                Component.For<IDeliveryItemRepository>().ImplementedBy<DeliveryItemRepository>().LifestylePerWebRequest(),
                Component.For<IDeliveryRepository>().ImplementedBy<DeliveryRepository>().LifestylePerWebRequest(),
                Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>().LifestylePerWebRequest(),
                Component.For<IExpenseRepository>().ImplementedBy<ExpenseRepository>().LifestylePerWebRequest(),
                Component.For<IInventoryItemRepository>().ImplementedBy<InventoryItemRepository>().LifestylePerWebRequest(),
                Component.For<IInventoryRepository>().ImplementedBy<InventoryRepository>().LifestylePerWebRequest(),
                Component.For<IItemGroupRepository>().ImplementedBy<ItemGroupRepository>().LifestylePerWebRequest());
        }
    }
}

UPDATE 4

This question is probably too general for SO, so I posted it on "Programmers"

UPDATE 5

Note: According to "The DI Whisperer" (Mark Seemann), IDependencyResolver should not be used, because it lacks a Release method (p. 207 of his book)

like image 859
B. Clay Shannon-B. Crow Raven Avatar asked Mar 21 '23 03:03

B. Clay Shannon-B. Crow Raven


1 Answers

You should not mix installation vs resolving. IOW your should not have

kernel.AddFacility<TypedFactoryFacility>();

in the WindsorControllerFactory

But the generic container configuration such registering TypedFactoryFacility should be executed in an installer called as earlier as possible.

In order to drive installer execution, you should use an Installer factory

public class YourInstallerFactory : InstallerFactory
{
    public override IEnumerable<Type> Select(IEnumerable<Type> installerTypes)
    {
        var windsorInfrastructureInstaller = installerTypes.FirstOrDefault(it => it == typeof(WindsorInfrastructureInstaller));

        var retVal = new List<Type>();
        retVal.Add(windsorInfrastructureInstaller);
        retVal.AddRange(installerTypes
            .Where(it =>
                typeof(IWindsorInstaller).IsAssignableFrom(it) &&
                !retVal.Contains(it)
                ));

        return retVal;
    }
}

Where windsorInfrastructureInstaller will be somenthing like this

public class WindsorInfrastructureInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        // Resolvers
        //container.Kernel.Resolver.AddSubResolver(new ArrayResolver(container.Kernel));

        // TypedFactoryFacility
        container.AddFacility<TypedFactoryFacility>();
    }
}

In your global.asax you'll create&use you installer factory as following

 var installerFactory = new YourInstallerFactory();
 container.Install(FromAssembly.This(installerFactory));

Your "FrontEnd"(for example the mvc/webapi) project has a folder containing all installers(WindsorInfrastructureInstaller will be one of those) and the installer factory as well or at least that's the way I'm use to organize my solution.

like image 98
Crixo Avatar answered Apr 08 '23 08:04

Crixo