如何连接 Web API Castle Windsor DI 代码的各个部分?

2023-12-10

如何连接 Web API Castle Windsor DI 代码的各个部分,以便控制器的路由选择正确的接口实现?

Note:经过几次错误的开始/死胡同和部分胜利(here and here and here),我将尽快给予最大的赏金500点。但我只会奖励一个非常好的答案 - IOW,一个足够清晰的答案,我可以理解它并将其“插入”到我的项目中,以便我可以将给定的具体类挂接到特定的控制器。

这里没有任何内容:我有一个 Web API(“MVC”)项目。不过,实际上,这个服务器项目没有“V”(视图),所以也许更好的缩写词是 MRC(模型/存储库/控制器)。

无论如何,我正在尝试使用温莎城堡向其中添加 DI。

我理解并挖掘了通过构造函数接口参数交换具体类的概念。不过,如何实现这个功能,

它是我一直在与之搏斗的一头野兽,而我现在遍体鳞伤、血淋淋的,头发乱糟糟的,鼻孔里还沾满了泥。

我想,无论如何,我已经拥有了我需要的大部分代码。考虑到 DI,我现在有了一个“DIPlumbing”文件夹和一个“DIInstallers”文件夹。 “DIPlumbing”文件夹包含两个类:WindsorCompositionRoot.cs 和 WindsorControllerFactory.cs。

“DIInstallers”文件夹目前包含三个文件,即 ISomethingProvider.cs、SomethingProvider.cs 和 SomethingProviderInstaller.cs

SomethingProviderInstaller 似乎是将 DIInstallers 中的接口/类连接到 DIPlumbing 文件夹中的内容的关键。

(我还修改了 Global.asax.cs,将默认控制器路由业务替换为 Castle Windsor 替换)。

但我很困惑应该将哪些类放入 DIInstallers 文件夹中。这些是否应该取代我的存储库(它同样有一个接口和一个为每个模型实现该接口的具体类)? IOW,我应该基本上将我的存储库代码移到 DIInstallers 文件夹中 - 然后删除 IRepository 和存储库单元吗?

当然,这将导致在控制器类中进行必要的更改,控制器类目前引用存储库类。

或者 Repositories 和 DIInstallers 类是否共存?如果是,控制器、安装程序和存储库之间的联系/从属关系是什么?

我似乎对 DI 和温莎城堡了解得越多,就越感到困惑。我不知道我是否太密集了,或者我是否试图让它变得更难,或者使用/呈现它的冲突风格是否是问题所在。底线是:我被困在流沙中,需要约翰尼·奎斯特伸出一根坚固的竹竿。

也许,最好的答案可能是所有这些组件(控制器、模型、存储库、安装程序、Global.asax.cs、组合根、工厂、提供程序等)如何直观地表示。 ,相互关联。

为了“充分披露”,我将在下面添加我希望的代码的关键元素,以展示我所拥有的内容以及它(希望)如何相互连接。

成分根:

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

控制器工厂:

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

// 注意:下面的“Something”最终有望成为“Departments”,然后是现在在模型及其相应的存储库和控制器中表示的其他类

我的东西提供者:

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

某物提供者:

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

东西提供商安装程序:

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

控制器:

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

我的存储库:

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

存储库:

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

在尝试运行服务器,以便它可以使用 Fiddler2 进行测试以查看正在传递的内容时,它在 WindsorControllerFactory 中的这一行失败:

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

...和 ​​”用户代码未处理 System.ArgumentException HResult=-2147024809 消息=“Castle.Facilities.TypedFactory.TypedFactoryFacility”类型的设施已在容器中注册。容器中只能存在一个给定类型的设施。 来源=温莎城堡 堆栈跟踪: 在 Castle.MicroKernel.DefaultKernel.AddFacility(字符串键,IFacility 设施) 在 Castle.MicroKernel.DefaultKernel.AddFacility(IFacility 设施) 在 Castle.MicroKernel.DefaultKernel.AddFacilityT 在 c:\HandheldServer\HandheldServer\DIPlumbing\WindsorControllerFactory.cs 中的 HandheldServer.DIPlumbing.WindsorControllerFactory..ctor(IKernel 内核):第 28 行 在 c:\HandheldServer\HandheldServer\Global.asax.cs 中的 HandheldServer.WebApiApplication.BootstrapContainer() 处:第 69 行 在 c:\HandheldServer\HandheldServer\Global.asax.cs 中的 HandheldServer.WebApiApplication.Application_Start() 处:第 39 行"

UPDATE 2

回应克里斯蒂亚诺的回答:

那么你是说我应该将以下两个文件添加到我的 Installers 文件夹中(我已经有一个 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;
    }
}

温莎基础设施安装程序.cs:

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

在您的 global.asax 中,您将创建使用安装程序工厂,如下所示

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

如果是的话,这对我有什么好处?上述是否会自动注册我的控制器和/或存储库类?

UPDATE 3

我现在使用的大量代码来自[http://blog.kerbyyoung.com/2013/01/setting-up-castle-windsor-for-aspnet.html#comment-form]

我认为关键部分是:

全局.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;
}  

温莎DependencyResolver.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

这个问题对于SO来说可能太笼统了,所以我将其发布在“程序员”上

UPDATE 5

注意:根据“The DI Whisperer”(Mark Seemann),不应使用 IDependencyResolver,因为它缺少 Release 方法(他的书第 207 页)


您不应该混合安装和解析。 IOW你不应该有

kernel.AddFacility<TypedFactoryFacility>();

在 WindsorControllerFactory 中

但是通用容器配置(例如注册 TypedFactoryFacility)应该在尽可能早调用的安装程序中执行。

为了驱动安装程序执行,您应该使用安装程序工厂

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

温莎基础设施安装程序将是这样的

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

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

在您的 global.asax 中,您将创建使用安装程序工厂,如下所示

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

您的“FrontEnd”(例如 mvc/webapi)项目有一个包含所有安装程序(WindsorInfrastructInstaller 将是其中之一)和安装程序工厂的文件夹,或者至少这是我用来组织解决方案的方式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何连接 Web API Castle Windsor DI 代码的各个部分? 的相关文章