如何连接我的 Web API 温莎城堡 DI 代码的各个部分



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

注意:在几次错误的开始/死胡同和部分胜利(这里,这里和这里)之后,我将尽快反弹最高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,将默认的控制器路由业务替换为温莎城堡的替代品)。

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

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

还是存储库和 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);
}

注意:下面的"东西"有望最终成为"部门",然后是现在在模型中表示的其他类及其相应的存储库和控制器

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

某物提供者:

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

控制器:

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 消息=类型为"城堡.设施.类型d工厂.类型d工厂设施"的设施已在容器中注册。容器中只能存在一个给定类型的设施。 来源=温莎城堡 堆栈跟踪: 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 28at HandheldServer.WebApiApplication.BootstrapContainer() in c:\HandheldServer\HandheldServer\Global.asax.cs:line 69 在 c:\HandheldServer\HandheldServer\Global.asax.cs:line 39" 中的 HandheldServer.WebApiApplication.Application_Start()

更新 2

针对克里斯蒂亚诺的回答:

所以你是说我应该将以下两个文件添加到我的安装程序文件夹中(我已经有一个 DIInstallers 文件夹)

鸭嘴兽安装工.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));

如果是,那对我有什么作用?上述操作是否自动注册我的控制器和/或存储库类?

更新 3

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

我认为,关键部分是:

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

更新 4

这个问题对于SO来说可能太笼统了,所以我把它贴在"程序员"上

更新 5

注意:根据"DI Whisperer"(Mark Seemann)的说法,不应该使用IDependencyResolver,因为它缺少释放方法(他的书第207页)

您不应该将安装与解析混合使用。 欠条你不应该有

kernel.AddFacility<TypedFactoryFacility>();

在温莎控制器工厂

但是通用容器配置(如注册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));

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

在回答我自己的问题时,我只想说:没有酥饼!事不宜迟,去这里拿这本书。辞职,花必要的时间仔细阅读。

所以我不是唯一一个;这是Jeff Beck的一句话,他写了这本书的前言:">通常那些开始使用DI的人很快就会发现自己迷失在混乱的海洋中。

不想再重复所有内容,所以请查看我的答案 如何让Web API/Castle Windsor识别控制器?.

作为另一个说明 - 如果您可以帮助它,我建议不要在您的存储库构造函数中执行任何操作。我这样说的原因是构造函数被调用,因为 Windsor 正在尝试实例化存储库的正确实例。这意味着,如果发生任何类型的错误,则在 WebApi 尝试创建控制器时会发生错误。这有时会使跟踪问题变得有点棘手,并且最终还会将真正的问题隐藏在大量异常层下。

最新更新