Dependency injection 在unity中注册具有多个生存期的服务

Dependency injection 在unity中注册具有多个生存期的服务,dependency-injection,asp.net-mvc-5,unity-container,Dependency Injection,Asp.net Mvc 5,Unity Container,我在一个MVC5项目(.net461)中为DI使用Unity,我想注册一个具有多个生命周期的服务 对于经典的核心DI,我将使用RegisterScoped,仅此而已。每当在Http请求中解析服务时,我都会在请求期间重用相同的实例。如果我想启动一个后台任务,该后台任务应该打开一个服务作用域,并且我将在该作用域期间为该服务解析一个新实例。不需要为该服务进行不同的注册。在第一种情况下,范围由运行时创建,在第二种情况下,范围由开发人员手动创建。在这两种情况下,服务提供者只知道服务的作用域,而不关心作用域

我在一个MVC5项目(.net461)中为DI使用Unity,我想注册一个具有多个生命周期的服务

对于经典的核心DI,我将使用RegisterScoped,仅此而已。每当在Http请求中解析服务时,我都会在请求期间重用相同的实例。如果我想启动一个后台任务,该后台任务应该打开一个服务作用域,并且我将在该作用域期间为该服务解析一个新实例。不需要为该服务进行不同的注册。在第一种情况下,范围由运行时创建,在第二种情况下,范围由开发人员手动创建。在这两种情况下,服务提供者只知道服务的作用域,而不关心作用域在哪里以及如何打开

通过Unity,第一个案例通过PerRequestLifetimeManager解决。第二种情况通过层次结构CallifeTimeManager解决

但我应该如何将两者结合起来呢

无论何时在HttpRequest中解析服务(在Instance的控制器构造函数中),都应使用PerRequestLifetimeManager,无论何时在子容器中解析服务(在子容器中实例化的另一个服务的构造函数中),都应使用HierarchyCallifeTimeManager


如何向两位经理注册该服务?

在一天结束时,我必须实施我自己的解决方案,该解决方案基于(但不使用)、包和

我在网上找到的解决方案都不适用于我的案例。其中大多数只涉及每个请求部分,而不涉及每个自定义用户范围部分

解决方案的关键不是生存期管理器,而是依赖项解析程序。我的需求的生命周期管理器应该始终是
hierarchycallifetimemanager
,因为这才是我真正需要的。每个作用域的一个新容器,由子容器和
层次结构CallifeTimeManager
覆盖

以如何实现自己的依赖项解析器为例,我提出了以下解决方案

我必须做的是确保在Http请求开始时创建一个新的作用域,并在Http请求结束时释放。本部分通过实现一个简单的
HttpModule
来实现。这一部分类似于官方的Unity Per Request生命周期实现所使用的HttpModule

class TestController{

   private readonly IMyScopedService service;
   private readonly IUnityContainer container;

   public TestController(IUnityContainer container, IMyScopedService service){
       this.service = service;
       this.container = container;
   }

   public ActionResult Post( ... ){

       var childContainer = this.container.CreateChildContainer();
       var scopedService = childContainer.GetService<IMyScopedService>()       

       HostingEnviroment.QueueBackgroundWorkItem(() => {
             using(childContainer){
                   scopedService.DoWork();
             }
       });

   }
}
每个Http请求模块 这是模块的实现

internal class UnityPerHttpRequestModule : IHttpModule
    {
        private static IUnityContainer _rootContainer;

        public void Init(HttpApplication context)
        {
            context.BeginRequest += (s, e) =>
               ((HttpApplication)s).Context.Items[typeof(UnityPerHttpRequestModule)]
                     = _rootContainer.CreateChildContainer();

            context.EndRequest += (s, e) =>
               (((HttpApplication)s).Context.Items[typeof(UnityPerHttpRequestModule)]
                       as IUnityContainer)?.Dispose();
        }

        public static void SetRootContainer(IUnityContainer rootContainer)
        {
            _rootContainer = rootContainer ?? throw new ArgumentNullException(nameof(rootContainer));
        }

        public void Dispose() { }
    }
在开始请求时,我们创建一个新的子容器,并将其放在HttpRequestItems字典中

在结束请求时,我们从Items字典中检索子容器并对其进行处理

静态方法
SetRootContainer
应该在应用程序启动时调用一次,以传入初始根Unity容器,即服务注册的容器

public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            UnityPerHttpRequestModule.SetRootContainer(UnityConfig.Container); // pass here the root container instance
            ...
        }
    }
我们还需要向owin注册模块

using Microsoft.Owin;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Owin;

[assembly: OwinStartup(typeof(MyApp.Startup))]
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(MyApp.Startup), nameof(MyApp.Startup.InitScopedServicesModule))]

namespace MyApp
{
    public partial class Startup
    {
        public static void InitScopedServicesModule()
        {
            DynamicModuleUtility.RegisterModule(typeof(UnityPerHttpRequestModule));
        }

        public void Configuration(IAppBuilder app)
        {

        }
    }
}
MVC依赖解析程序 现在http模块已经注册,我们在每个请求上都创建了一个新的作用域。现在我们需要指示MVC和WebApi使用该范围。为此,我们需要创建适当的依赖项解析器。我为MVC和WebApi创建了一个依赖项解析器,因为它们需要实现不同的接口(虽然我可以在同一个类中实现这两个)

MVC的依赖项解析程序如下所示:

internal class UnityMvcPerHttpRequestDependencyResolver : IDependencyResolver
    {
        private readonly IUnityContainer rootContainer;

        internal UnityMvcPerHttpRequestDependencyResolver(IUnityContainer rootContainer)
        {
            this.rootContainer = rootContainer;
        }

        internal IUnityContainer Current => (HttpContext.Current?.Items[typeof(UnityPerHttpRequestModule)] as IUnityContainer) ?? this.rootContainer;

        public void Dispose() { }
        public object GetService(Type serviceType)
        {
            try
            {
                return Current.Resolve(serviceType);
            }
            catch (ResolutionFailedException)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return Current.ResolveAll(serviceType);
            }
            catch (ResolutionFailedException)
            {
                return null;
            }
        }
    }
注意事项如下:

  • 不要注册官方UnityPerRequestHttpModule,因为我们实现了自己的。(我可能会使用该模块,但我的实现将取决于官方模块的内部实现,我不希望这样,因为它以后可能会更改)
Web Api依赖项解析器 对于MVC依赖解析程序,我们需要为Web Api实现一个

internal class UnityWebApiPerHttpRequestDependencyResolver : IDependencyResolver
    {
        private readonly IUnityContainer rootContainer;

        internal UnityWebApiPerHttpRequestDependencyResolver(IUnityContainer rootContainer)
        {
            this.rootContainer = rootContainer;
        }

        internal IUnityContainer Current => (HttpContext.Current?.Items[typeof(UnityPerHttpRequestModule)] as IUnityContainer) ?? this.rootContainer;

        public IDependencyScope BeginScope() => this;

        // Dispose, GetService and GetServices are the same as MVC dependency resolver
    }
注意事项如下:

  • idependencysolver
    这里的类型是
    System.Web.Http.Dependencies.idependencysolver
    。它与MVC的IDependencyResolver不同
  • 此依赖项解析器接口实现了另一种方法:
    BeginScope
    这在这里很重要。WebApi管道与MVC管道不同。默认情况下,WebApi引擎调用
    BeginScope
    为每个web api请求打开一个新范围,并使用该范围解析控制器和服务。因此,WebAPI已经有了一个作用域机制但是我们已经用每个请求模块创建了一个作用域,我们希望使用该作用域。所以我们在这里要做的是不要再创建新的作用域。它已经存在了。因此,在解析器上调用
    BeginScope
    应该返回相同的解析器作用域,因此我们返回
    this
现在我们已经创建了WebApi解析器,我们还必须将其注册到WebApi

using System.Web.Http;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(MyApp.UnityWebApiActivator), nameof(MyApp.UnityWebApiActivator.Start))]

namespace MyApp
{
    /// <summary>
    /// Provides the bootstrapping for integrating Unity with WebApi when it is hosted in ASP.NET.
    /// </summary>
    public static class UnityWebApiActivator
    {
        /// <summary>
        /// Integrates Unity when the application starts.
        /// </summary>
        public static void Start()
        {
            // Use UnityHierarchicalDependencyResolver if you want to use
            // a new child container for each IHttpController resolution.
            // var resolver = new UnityHierarchicalDependencyResolver(UnityConfig.Container);
            var resolver = new UnityWebApiPerHttpRequestDependencyResolver(UnityConfig.Container);

            GlobalConfiguration.Configuration.DependencyResolver = resolver;
        }
    }
}
使用System.Web.Http;
[程序集:WebActivatorEx.PreApplicationStartMethod(typeof(MyApp.unityWebPiActivator)、nameof(MyApp.unityWebPiActivator.Start))]
名称空间MyApp
{
/// 
///当Unity托管在ASP.NET中时,提供将其与WebApi集成的引导。
/// 
公共静态类UnityWebPiActivator
{
/// 
///在应用程序启动时集成Unity。
/// 
公共静态void Start()
{
//如果要使用,请使用UnityHierarchicalDependencyResolver
//每个IHTTP控制器分辨率的新子容器。
//var解析器=新的UnityHierarchicalDependencyResolver(UnityConfig.Container);
var解析器=新的UnityWebPiperHttPrequestDependencyResolver(UnityConfig.Container);
GlobalConfiguration.Configuration.DependencyResolver=解析程序;
}
}
}
注册服务 现在,我们已经设置并注册了所有解析程序和模块,最后要做的就是记住向HierarchyCallifeTimeManager注册每个作用域服务。信诺
class TestController{

   private readonly IMyScopedService service;
   private readonly IUnityContainer container;

   public TestController(IUnityContainer container, IMyScopedService service){
       this.service = service;
       this.container = container;
   }

   public ActionResult Post( ... ){

       var childContainer = this.container.CreateChildContainer();
       var scopedService = childContainer.GetService<IMyScopedService>()       

       HostingEnviroment.QueueBackgroundWorkItem(() => {
             using(childContainer){
                   scopedService.DoWork();
             }
       });

   }
}