我们有一个 Multi-Tenancy ASP.NET MVC 应用程序,它为多个客户端托管一个预订引擎。这些客户端中的每一个都有多个可以影响 Unity Container 配置的包。我们为每个请求创建一个子容器,并根据通过路由传递的客户端和包参数注册不同的接口(interface)实现。
目前,我们正在通过执行以下操作来实现此目的:
这行得通,但真的很笨拙,我觉得最终它将是不可持续的。我正在寻找更好的解决方案。
我们目前停留在 .NET 4.0 上,直到我们整理了一些遗留的东西,所以我专门针对 Unity 2。
我尝试创建一个自定义 IDependencyResolver 来创建子容器并根据将容器存储在 Session 或 HttpContext 项中的路由参数注册依赖项,但遇到了 null HttpContext 问题。有没有其他方法可以在路由上进行注册并将依赖项注入(inject)到 Controller 构造函数中?
最终我也需要一个 Web API 的解决方案。
编辑:示例
public interface IRateService { ... }
public class RemoteRateService : IRateService { ... }
public class LocalRateService : IRateService { ... }
public class CustomDependencyResolver : IDependencyResolver
{
public object GetService(Type serviceType)
{
if(ChildContainer == null)
{
ChildContainer = _container.CreateChildContainer();
var routeData = HttpContext.Current.Request.RequestContext.RouteData.Values;
if(routeData["client"] == "ClientA")
ChildContainer.RegisterType<IRateService, RemoteRateService>();
else
ChildContainer.RegisterType<IRateService, LocalRateService>();
}
return ChildContainer.Resolve(serviceType);
}
}
public class RateController : Controller
{
private IRateService _rateService;
public RateController(IRateService rateService)
{
_rateService = rateService;
}
...
}
url:/ClientA/Package1/Rate - RateController 获取 RemoteRateService
url:/ClientB/Package2/Rate - RateController 获取 LocalRateService
请您参考如下方法:
Abatishchev 在评论中回答了我的问题,用 IControllerFactory 为我指明了正确的方向。对于到此结束的随机谷歌搜索,这是我通过继承 DefaultControllerFactory 使用的基本设置:
public class UnitySessionControllerFactory : DefaultControllerFactory
{
private const string HttpContextKey = "Container";
private readonly IUnityContainer _container;
public UnitySessionControllerFactory (IUnityContainer container)
{
_container = container;
}
protected IUnityContainer GetChildContainer(RequestContext requestContext)
{
var routeData = requestContext.RouteData.Values
?? new RouteValueDictionary();
var clientName = routeData["clientName"] as string;
var packageId = routeData["packageID"] as int?;
if (clientName == null)
throw new ArgumentException("ClientName not included in route parameters");
var childContainer = requestContext.HttpContext.Session[clientName + HttpContextKey] as IUnityContainer;
if (childContainer != null)
return childContainer;
requestContext.HttpContext.Session[clientName + HttpContextKey] = childContainer = _container.CreateChildContainer();
var moduleLoader = childContainer.Resolve<ModuleLoader>();
moduleLoader.LoadModules(clientName, packageId);
return childContainer;
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controllerType = GetControllerType(requestContext, controllerName);
var container = GetChildContainer(requestContext);
return container.Resolve(controllerType) as IController;
}
public override void ReleaseController(IController controller)
{
_container.Teardown(controller);
}
}
原谅这里使用 session。将来,一旦我能够解决我们项目对 session 的使用,我将把它换成 HttpContext.Items。
为了启用自定义 Controller 工厂,我将此行添加到 Bootstrapper.Initialise() 方法
ControllerBuilder.Current
.SetControllerFactory(new UnitySessionControllerFactory(container));