Jak połączyć różne elementy mojego kodu API Web Castle Castle Windsor DI?

Jak połączyć różne elementy mojego kodu DI Web API Castle Windsor, aby routing kontrolera wybrał poprawną implementację interfejsu?

Uwaga: Po kilku fałszywych startach / ślepych zaułkach i częściowych zwycięstwach (tutaj itutaj itutaj), Zamierzam maksymalizować ten ASAP500 zwrotnica. Ale nagrodzę tylko naprawdę dobrą odpowiedź - IOW, taką, która jest wystarczająco jasna, że ​​mogę ją zrozumieć i „podłączyć” do mojego projektu, aby móc podłączyć konkretną klasę do konkretnego kontrolera.

Tutaj nic nie ma: mam projekt Web API („MVC”). Naprawdę, ten projekt serwera nie ma „V” (Widok), więc może lepszym akronimem byłby MRC (Model / Repozytorium / Kontroler).

W każdym razie próbuję dodać DI do niego za pomocą Castle Windsor.

Grok i wykopuję koncepcję zamiany konkretnych klas za pomocą argumentów interfejsu konstruktora. Jak jednak wdrożyć tę funkcjonalność,

jest bestią, z którą się zmagam, a teraz jestem dość posiniaczona i zakrwawiona, z rozczochranymi włosami i nozdrzami wysadzanymi błotem.

Myślę, że mam większość kodu, którego potrzebuję - w każdym razie na początek. Z myślą o DI mam teraz folder „DIPlumbing” i folder „DIInstallers”. Folder „DIPlumbing” zawiera dwie klasy: WindsorCompositionRoot.cs i WindsorControllerFactory.cs.

Folder „DIInstallers” ma na razie trzy pliki, mianowicie ISomethingProvider.cs, SomethingProvider.cs i SomethingProviderInstaller.cs

CośProviderInstaller wydaje się być kluczem do połączenia interfejsów / klas w DIInstallers z materiałami w folderze DIPlumbing.

(Zmodyfikowałem także Global.asax.cs, aby zastąpić domyślny biznes routingu kontrolera wymianą Castle Windsor).

Ale jestem zdezorientowany, jakie klasy powinienem umieszczać w folderze DIInstallers. Czy mają one zastąpić moje repozytoria (które również mają interfejs i konkretną klasę, która implementuje ten interfejs dla każdego modelu)? IOW, czy powinienem zasadniczo przenieść mój kod repozytorium do folderu DIInstallers - a następnie pozbyć się jednostek IRepository i Repository?

To oczywiście spowodowałoby konieczność wprowadzenia zmian w klasach kontrolera, które od teraz odwołują się do klas repozytorium.

Czy współistnieją klasy Repozytoria i DIInstallers? Jeśli tak, jakie jest połączenie / powiązanie między kontrolerami, instalatorami i repozytoriami?

Wygląda na to, że im więcej czytam na DI i Castle Windsor, tym bardziej się mylę. Nie wiem, czy jestem na to zbyt gęsty, czy staram się uczynić go trudniejszym, niż jest, lub czy problem stanowią sprzeczne style używania / prezentowania. Najważniejsze jest to, że utknąłem w ruchomych piaskach i potrzebuję Johnny'ego Questa, by wyciągnąć solidny bambusowy pręt.

Najlepszą odpowiedzią na wszystkie, być może i zbyt wiele, by wymagać, byłaby wizualna reprezentacja tego, jak wszystkie te składniki - Kontrolery, Modele, Repozytoria, Instalatorzy, Global.asax.cs, korzenie kompozycji, fabryki, dostawcy itp. , odnoszą się do siebie nawzajem.

Dla celów „pełnego ujawnienia” dodam to, co mam nadzieję, to kluczowe elementy mojego kodu poniżej, aby pokazać, co mam i jak (mam nadzieję) łączy się ze sobą.

Korzeń kompozycji:

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

Fabryka kontrolerów:

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

// Uwaga: Miejmy nadzieję, że „Coś” poniżej będzie w końcu „Wydziałami”, a następnie innymi klasami reprezentowanymi obecnie w modelach i odpowiadających im repozytoriach i kontrolerach

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

Coś dostawcy:

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

Kontroler:

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

Magazyn:

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

Próbując uruchomić serwer, aby mógł przetestować go z Fiddler2, aby zobaczyć, co jest przekazywane, nie powiodło się na tej linii w WindsorControllerFactory:

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

...z "System.ArgumentException nie był obsługiwany przez kod użytkownika HResult = -2147024809 Wiadomość = Obiekt typu „Castle.Facilities.TypedFactory.TypedFactoryFacility” został już zarejestrowany w kontenerze. Tylko jeden obiekt danego typu może istnieć w kontenerze. Source = Castle.Windsor StackTrace: na Castle.MicroKernel.DefaultKernel.AddFacility (String string, IFacility facility) na Castle.MicroKernel.DefaultKernel.AddFacility (IFacility facility) na Castle.MicroKernel.DefaultKernel.AddFacilityT na HandheldServer.DIPlumbing.WindsorControllerFactory .. ctor (jądro IKernel) w c: HandheldServer HandheldServerIPlumbing WindsorControllerFactory.cs: linia 28 w HandheldServer.WebApiApplication.BootstrapContainer () w c: HandheldServer HandheldServer Global.asax.cs: linia 69 w HandheldServer.WebApiApplication. Application_Start () in c: HandheldServer HandheldServer Global.asax.cs: linia 39"

AKTUALIZACJA 2

W odpowiedzi na odpowiedź Cristiano:

Więc mówisz, że powinienem dodać następujące dwa pliki do mojego folderu Instalatorów (mam już folder DIInstallers)

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

W swoim global.asax stworzysz i użyjesz fabryki instalatora w następujący sposób

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

Jeśli tak, co to dla mnie zrobi? Czy powyższe automagicznie rejestruje moje klasy kontrolera i / lub repozytorium?

AKTUALIZACJA 3

Używam teraz dużo kodu z [http://blog.kerbyyoung.com/2013/01/setting-up-castle-windsor-for-aspnet.html#comment-form]

Myślę, że kluczowe części to:

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

To pytanie jest prawdopodobnie zbyt ogólny dla SO, więc opublikowałem to na „Programistach”

AKTUALIZACJA 5

Uwaga: Według „Zaklinacza DI” (Mark Seemann), IDependencyResolver nie powinien być używany, ponieważ brakuje mu metody Release (s. 207 jego książki)

questionAnswers(3)

yourAnswerToTheQuestion