Wie verbinde ich die verschiedenen Teile meines Web-API-Codes für Castle Windsor DI?

Wie verbinde ich die verschiedenen Teile meines Web API Castle Windsor DI-Codes, damit das Routing des Controllers die richtige Schnittstellenimplementierung auswählt?

Hinweis: Nach mehreren Fehlstarts / Sackgassen und Teilsiegen (Hier undHier undHier), Ich werde diese ASAP für das Maximum belasten500 Punkte. Aber ich werde nur eine wirklich gute Antwort vergeben - IOW, eine, die klar genug ist, dass ich sie verstehen und in mein Projekt "einbinden" kann, damit ich eine bestimmte konkrete Klasse mit einem bestimmten Controller verknüpfen kann.

Hier geht nichts: Ich habe ein Web-API-Projekt ("MVC"). In Wirklichkeit hat dieses Serverprojekt jedoch kein "V" (View). Ein besseres Akronym wäre also MRC (Model / Repository / Controller).

Auf jeden Fall versuche ich, DI mit Castle Windsor hinzuzufügen.

Ich befasse mich mit dem Konzept des Austauschs konkreter Klassen über Konstruktorschnittstellenargumente. Nur, wie diese Funktionalität implementiert wird,

Es war ein Biest, mit dem ich gerungen habe, und ich bin im Moment ziemlich verletzt und blutig, mit durcheinandergebrachten Haaren und mit Schlamm verkrusteten Nasenlöchern.

Ich habe, glaube ich, den größten Teil des Codes, den ich brauche - jedenfalls, um damit zu beginnen. Unter Berücksichtigung von DI habe ich jetzt einen Ordner "DIPlumbing" und einen Ordner "DIInstallers". Der Ordner "DIPlumbing" enthält zwei Klassen: WindsorCompositionRoot.cs und WindsorControllerFactory.cs.

Der Ordner "DIInstallers" enthält derzeit drei Dateien, nämlich ISomethingProvider.cs, SomethingProvider.cs und SomethingProviderInstaller.cs

SomethingProviderInstaller scheint der Schlüssel zu sein, um die Interfaces / Klassen in DIInstallers mit den Inhalten im DIPlumbing-Ordner zu verbinden.

(Ich habe auch Global.asax.cs geändert, um das Standard-Controller-Routing-Geschäft durch den Castle Windsor-Ersatz zu ersetzen.)

Ich bin jedoch verwirrt, welche Klassen ich im DIInstaller-Ordner ablegen soll. Sollen diese an die Stelle meiner Repositories treten (die ebenfalls eine Schnittstelle und eine konkrete Klasse haben, die diese Schnittstelle für jedes Modell implementiert)? IOW, sollte ich meinen Repository-Code grundsätzlich in den DIInstallers-Ordner verschieben - und dann die IRepository- und Repository-Einheiten entfernen?

Dies würde natürlich zu notwendigen Änderungen in den Controller-Klassen führen, die ab sofort auf Repository-Klassen verweisen.

Oder existieren die Klassen Repositorys und DIInstaller gleichzeitig? Wenn ja, wie ist die Verbindung / Zugehörigkeit zwischen den Controllern, Installationsprogrammen und Repositorys?

Je mehr ich über DI und Castle Windsor nachlese, desto verwirrter wird es. Ich weiß nicht, ob ich zu dicht dafür bin oder ob ich versuche, es schwieriger zu machen, als es ist, oder ob widersprüchliche Stile der Verwendung / Präsentation das Problem sind. Das Fazit lautet: Ich stecke im Treibsand fest und brauche Johnny Quest, um eine robuste Bambusstange zu verlängern.

Die beste und wahrscheinlich zu viel verlangte Antwort wäre eine visuelle Darstellung all dieser Komponenten - Controller, Modelle, Repositorys, Installer, Global.asax.cs, Kompositionsstämme, Fabriken, Anbieter usw. , Sich aufeinander beziehen.

Für die Zwecke der "vollständigen Offenlegung" füge ich unten hoffentlich die Schlüsselelemente meines Codes hinzu, um zu zeigen, was ich habe und wie es (hoffentlich) miteinander verbunden ist.

Wurzel der Zusammensetzung:

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);
    }

// Hinweis: Das unten stehende "Etwas" wird hoffentlich "Abteilungen" und dann andere Klassen sein, die jetzt in Models und ihren entsprechenden Repositories und Controllern vertreten sind

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()); 
    }
}

Regler:

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();
    }
AKTUALISIEREN

Beim Versuch, den Server auszuführen, damit er mit Fiddler2 getestet werden kann, um festzustellen, was gerade passiert, ist in WindsorControllerFactory in dieser Zeile ein Fehler aufgetreten:

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

... mit "System.ArgumentException wurde vom Benutzercode nicht behandelt HResult = -2147024809 Message = Einrichtung vom Typ 'Castle.Facilities.TypedFactory.TypedFactoryFacility' wurde bereits im Container registriert. In dem Container kann nur eine Einrichtung eines bestimmten Typs vorhanden sein. Quelle = Castle.Windsor StackTrace: bei Castle.MicroKernel.DefaultKernel.AddFacility (String-Schlüssel, IFacility-Funktion) bei Castle.MicroKernel.DefaultKernel.AddFacility (IFacility-Funktion) bei Castle.MicroKernel.DefaultKernel.AddFacility.Winder .. ctor (IKernel-Kernel) unter c: \ HandheldServer \ HandheldServer \ DIPlumbing \ WindsorControllerFactory.cs: Zeile 28 unter HandheldServer.WebApiApplication.BootstrapContainer () unter c: \ HandheldServer \ HandheldServer \ Global.asax.cs: Zeile 69 unter HandheldServer.Web. Application_Start () in c: \ HandheldServer \ HandheldServer \ Global.asax.cs: Zeile 39"

UPDATE 2

Als Antwort auf Cristianos Antwort:

Wollen Sie damit sagen, dass ich die folgenden zwei Dateien zu meinem Installationsordner hinzufügen soll (ich habe bereits einen DIInstallers-Ordner)?

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 Ihrem global.asax erstellen und verwenden Sie Ihre Installer-Factory wie folgt

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

Wenn ja, was bringt mir das? Werden meine Controller- und / oder Repository-Klassen automatisch registriert?

UPDATE 3

Ich verwende jetzt viel Code von [http://blog.kerbyyoung.com/2013/01/setting-up-castle-windsor-for-aspnet.html#comment-form]

Die Schlüsselteile sind, denke ich:

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

Diese Frage ist wahrscheinlich zu allgemein für SO, also habe ich es auf "Programmers" gepostet

UPDATE 5

Hinweis: Laut "The DI Whisperer" (Mark Seemann) sollte IDependencyResolver nicht verwendet werden, da es keine Release-Methode gibt (S. 207 seines Buches).

Antworten auf die Frage(3)

Ihre Antwort auf die Frage