如何连接 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();
}
在尝试运行服务器,以便它可以使用 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 行"
回应克里斯蒂亚诺的回答:
那么你是说我应该将以下两个文件添加到我的 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));
如果是的话,这对我有什么好处?上述是否会自动注册我的控制器和/或存储库类?
我现在使用的大量代码来自[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());
}
}
}
这个问题对于SO来说可能太笼统了,所以我将其发布在“程序员”上
注意:根据“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 将是其中之一)和安装程序工厂的文件夹,或者至少这是我用来组织解决方案的方式。