根据路线参数注册子集装箱

Sta*_*Gay 5 c# asp.net-mvc dependency-injection unity-container

我们有一个多租户ASP.NET MVC应用程序,该应用程序为多个客户端托管预订引擎。这些客户端中的每个客户端都有多个程序包,这些程序包可能会影响Unity容器的配置。我们将为每个请求创建一个子容器,并根据通过路由传递的客户端和程序包参数注册不同的接口实现。

当前,我们通过执行以下操作来完成此任务:

  1. 控制器具有ServiceLocator属性,该属性使用统一容器来解决依赖关系。
  2. 控制器获取IUnityContainer注入并分配给属性。
  3. 控制器具有自定义的ActionFilterAttribute,可以访问控制器的统一容器,创建子容器,基于客户端和程序包路由参数有条件地注册依赖项实现,然后将此子容器分配给控制器的serviceLocator。
  4. 控制器按需使用serviceLocator来解决各个依赖性。

这是可行的,但确实很笨拙,我最终认为这将是不可持续的。我正在寻找更好的解决方案。

目前,我们一直停留在.NET 4.0上,直到我们打包了一些旧的东西,所以我专门针对Unity 2。

我尝试创建一个自定义IDependencyResolver来创建子容器,并根据将参数存储在Session或HttpContext项目中的路由参数来注册依赖项,但遇到了空HttpContext问题。还有其他方法可以将注册基于路由并将依赖项注入到控制器构造函数中吗?

最终,我还将需要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;
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

网址:/ ClientA / Package1 / Rate-RateController获取RemoteRateService
网址:/ ClientB / Package2 / Rate-RateController获取LocalRateService

Sta*_*Gay 3

Abatishchev 在评论中回答了我的问题,用 IControllerFactory 为我指明了正确的方向。对于到这里结束的随机 google 搜索,这是我通过继承 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请原谅这里使用会话。将来,一旦我能够解决我们项目对会话的使用问题,我就会将其交换为 HttpContext.Items。

为了启用自定义控制器工厂,我将此行添加到 Bootstrapper.Initialise() 方法中

ControllerBuilder.Current
    .SetControllerFactory(new UnitySessionControllerFactory(container));
Run Code Online (Sandbox Code Playgroud)