Skip to main content
 首页 » 编程设计

c#之基于路由参数的子容器注册

2025年05月04日35虾米哥

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

目前,我们正在通过执行以下操作来实现此目的:

  • Controller 有一个属性 ServiceLocator,它使用一个统一容器来解决依赖关系。
  • Controller 将 IUnityContainer 注入(inject)并分配给一个属性。
  • Controller 有一个自定义的 ActionFilterAttribute 访问 Controller 的统一容器,创建一个子容器,根据客户端和包路由参数有条件地注册依赖实现,然后将此子容器分配给 Controller 的 serviceLocator。
  • Controller 按需使用 serviceLocator 来解决各个依赖项。

  • 这行得通,但真的很笨拙,我觉得最终它将是不可持续的。我正在寻找更好的解决方案。

    我们目前停留在 .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));