简体   繁体   English

如何在OWIN中使用Castle Windsor的PerWebRequest生活方式

[英]How to use Castle Windsor's PerWebRequest lifestyle with OWIN

I am converting an existing ASP .Net Web API 2 project to use OWIN. 我正在转换现有的ASP .Net Web API 2项目以使用OWIN。 The project uses Castle Windsor as the dependency injection framework with one of the dependencies set to use the PerWebRequest lifestyle. 该项目使用Castle Windsor作为依赖注入框架,其中一个依赖项设置为使用PerWebRequest生活方式。

When I make a request to the server I get a Castle.MicroKernel.ComponentResolutionException exception. 当我向服务器发出请求时,我得到了Castle.MicroKernel.ComponentResolutionException异常。 The exception recommends adding the following to the system.web/httpModules and system.WebServer/modules sections in the config file: 该异常建议将以下内容添加到配置文件中的system.web/httpModulessystem.WebServer/modules部分:

<add name="PerRequestLifestyle"
     type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor" />

This doesn't resolve the error. 这不能解决错误。

Taking inspiration from the example provided by SimpleInjector's OWIN integration, I attempted to set a scope in the OWIN startup class (as well as update the dependency's lifestyle) using: 从SimpleInjector的OWIN集成提供的示例中获取灵感,我尝试使用以下方法在OWIN启动类中设置范围(以及更新依赖关系的生活方式):

appBuilder.User(async (context, next) =>
{
    using (config.DependencyResolver.BeginScope()){
    {
        await next();
    }
}

Unfortunately this hasn't worked either. 不幸的是,这也没有用。

How can I use Castle Windsor's PerWebRequest lifestyle or simulate it in OWIN? 我如何使用Castle Windsor的PerWebRequest生活方式或在OWIN中模拟它?

According to the Castle Windsor documentation you can implement your own custom scope. 根据Castle Windsor文档,您可以实现自己的自定义范围。 You have to implement the Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor interface. 您必须实现Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor接口。

You then specify your scope accessor when registering your component: 然后在注册组件时指定范围访问器:

Container.Register(Component.For<MyScopedComponent>().LifestyleScoped<OwinWebRequestScopeAccessor >());

The class OwinWebRequestScopeAccessor implements Castle.Windsor's IScopeAccessor : OwinWebRequestScopeAccessor类实现了Castle.Windsor的IScopeAccessor

using Castle.MicroKernel.Context;
using Castle.MicroKernel.Lifestyle.Scoped;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Web.Api.Host
{
    public class OwinWebRequestScopeAccessor : IScopeAccessor
    {
        public void Dispose()
        {
            var scope = PerWebRequestLifestyleOwinMiddleware.YieldScope();
            if (scope != null)
            {
                scope.Dispose();
            }
        }

        public ILifetimeScope GetScope(CreationContext context)
        {
            return PerWebRequestLifestyleOwinMiddleware.GetScope();
        }
    }
}

As you can see OwinWebRequestScopeAccessor delegates the calls to GetScope and Dispose to PerWebRequestLifestyleOwinMiddleware . 如您所见, OwinWebRequestScopeAccessor GetScopeDispose的调用委托给PerWebRequestLifestyleOwinMiddleware

The class PerWebRequestLifestyleOwinMiddleware is the OWIN counter part of Castle Windsor's ASP.NET IHttpModule PerWebRequestLifestyleModule . PerWebRequestLifestyleOwinMiddleware类是Castle Windsor的ASP.NET IHttpModule PerWebRequestLifestyleModule的OWIN计数器部分。

This is the PerWebRequestLifestyleOwinMiddleware class: 这是PerWebRequestLifestyleOwinMiddleware类:

using Castle.MicroKernel;
using Castle.MicroKernel.Lifestyle.Scoped;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Web.Api.Host
{
    using AppFunc = Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;

    public class PerWebRequestLifestyleOwinMiddleware
    {
        private readonly AppFunc _next;
        private const string c_key = "castle.per-web-request-lifestyle-cache";
        private static bool _initialized;

        public PerWebRequestLifestyleOwinMiddleware(AppFunc next)
        {
            _next = next;
        }

        public async Task Invoke(IDictionary<string, object> environment)
        {
            var requestContext = OwinRequestScopeContext.Current;
            _initialized = true;

            try
            {
                await _next(environment);
            }
            finally
            {
                var scope = GetScope(requestContext, createIfNotPresent: false);
                if (scope != null)
                {
                    scope.Dispose();
                }
                requestContext.EndRequest();
            }
        }

        internal static ILifetimeScope GetScope()
        {
            EnsureInitialized();
            var context = OwinRequestScopeContext.Current;
            if (context == null)
            {
                throw new InvalidOperationException(typeof(OwinRequestScopeContext).FullName +".Current is null. " +
                    typeof(PerWebRequestLifestyleOwinMiddleware).FullName +" can only be used with OWIN.");
            }
            return GetScope(context, createIfNotPresent: true);
        }

        /// <summary>
        /// Returns current request's scope and detaches it from the request 
        /// context. Does not throw if scope or context not present. To be 
        /// used for disposing of the context.
        /// </summary>
        /// <returns></returns>
        internal static ILifetimeScope YieldScope()
        {
            var context = OwinRequestScopeContext.Current;
            if (context == null)
            {
                return null;
            }
            var scope = GetScope(context, createIfNotPresent: false);
            if (scope != null)
            {
                context.Items.Remove(c_key);
            }
            return scope;
        }

        private static void EnsureInitialized()
        {
            if (_initialized)
            {
                return;
            }
            throw new ComponentResolutionException("Looks like you forgot to register the OWIN middleware " + typeof(PerWebRequestLifestyleOwinMiddleware).FullName);
        }

        private static ILifetimeScope GetScope(IOwinRequestScopeContext context, bool createIfNotPresent)
        {
            ILifetimeScope candidates = null;
            if (context.Items.ContainsKey(c_key))
            {
                candidates = (ILifetimeScope)context.Items[c_key];
            }
            else if (createIfNotPresent)
            {
                candidates = new DefaultLifetimeScope(new ScopeCache());
                context.Items[c_key] = candidates;
            }
            return candidates;
        }
    }

    public static class AppBuilderPerWebRequestLifestyleOwinMiddlewareExtensions
    {
        /// <summary>
        /// Use <see cref="PerWebRequestLifestyleOwinMiddleware"/>.
        /// </summary>
        /// <param name="app">Owin app.</param>
        /// <returns></returns>
        public static IAppBuilder UsePerWebRequestLifestyleOwinMiddleware(this IAppBuilder app)
        {
            return app.Use(typeof(PerWebRequestLifestyleOwinMiddleware));
        }
    }
}

Castle Windsor's ASP.NET IHttpModule PerWebRequestLifestyleModule utilizes HttpContext.Current for storing the Castle Windsor ILifetimeScope on a per-web-request basis. Castle Windsor的ASP.NET IHttpModule PerWebRequestLifestyleModule利用HttpContext.Current在每个Web请求的基础上存储Castle Windsor ILifetimeScope PerWebRequestLifestyleOwinMiddleware class uses OwinRequestScopeContext.Current . PerWebRequestLifestyleOwinMiddleware类使用OwinRequestScopeContext.Current This is based on the idea of Yoshifumi Kawai . 这是基于Yoshifumi Kawai的想法。

The implementation of OwinRequestScopeContext below is my lightweight implementation of Yoshifumi Kawai's original OwinRequestScopeContext . 下面的OwinRequestScopeContext的实现是我对Yoshifumi Kawai的原始OwinRequestScopeContext轻量级实现。


Note: if you don't want this lightweight implementation you can use Yoshifumi Kawai's excellent original implementation by running this command in the NuGet Package Manager Console: 注意:如果您不想要这个轻量级实现,可以通过在NuGet包管理器控制台中运行此命令来使用Yoshifumi Kawai的优秀原始实现:

PM> Install-Package OwinRequestScopeContext


Lightweight implementation of OwinRequestScopeContext : OwinRequestScopeContext轻量级实现:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;

namespace Web.Api.Host
{
    public interface IOwinRequestScopeContext
    {
        IDictionary<string, object> Items { get; }
        DateTime Timestamp { get; }
        void EndRequest();
    }

    public class OwinRequestScopeContext : IOwinRequestScopeContext
    {
        const string c_callContextKey = "owin.reqscopecontext";
        private readonly DateTime _utcTimestamp = DateTime.UtcNow;
        private ConcurrentDictionary<string, object> _items = new ConcurrentDictionary<string, object>();

        /// <summary>
        /// Gets or sets the <see cref="IOwinRequestScopeContext"/> object 
        /// for the current HTTP request.
        /// </summary>
        public static IOwinRequestScopeContext Current
        {
            get
            {
                var requestContext = CallContext.LogicalGetData(c_callContextKey) as IOwinRequestScopeContext;
                if (requestContext == null)
                {
                    requestContext = new OwinRequestScopeContext();
                    CallContext.LogicalSetData(c_callContextKey, requestContext);
                }
                return requestContext;
            }
            set
            {
                CallContext.LogicalSetData(c_callContextKey, value);
            }
        }

        public void EndRequest()
        {
            CallContext.FreeNamedDataSlot(c_callContextKey);
        }

        public IDictionary<string, object> Items
        {
            get
            {
                return _items;
            }
        }

        public DateTime Timestamp
        {
            get
            {
                return _utcTimestamp.ToLocalTime();
            }
        }
    }
}

When you have all the pieces in place you can tie things up. 如果你有所有的部分,你可以绑定。 In your OWIN startup class call the appBuilder.UsePerWebRequestLifestyleOwinMiddleware(); 在你的OWIN启动类中调用appBuilder.UsePerWebRequestLifestyleOwinMiddleware(); extension method to register the OWIN middle ware defined above. 注册上面定义的OWIN中间件的扩展方法。 Do this before appBuilder.UseWebApi(config); appBuilder.UseWebApi(config);之前执行此appBuilder.UseWebApi(config); :

using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Diagnostics;
using Castle.Windsor;
using System.Web.Http.Dispatcher;
using System.Web.Http.Tracing;

namespace Web.Api.Host
{
    class Startup
    {
        private readonly IWindsorContainer _container;

        public Startup()
        {
            _container = new WindsorContainer().Install(new WindsorInstaller());
        }

        public void Configuration(IAppBuilder appBuilder)
        {
            var properties = new Microsoft.Owin.BuilderProperties.AppProperties(appBuilder.Properties);
            var token = properties.OnAppDisposing;
            if (token != System.Threading.CancellationToken.None)
            {
                token.Register(Close);
            }

            appBuilder.UsePerWebRequestLifestyleOwinMiddleware();

            //
            // Configure Web API for self-host. 
            //
            HttpConfiguration config = new HttpConfiguration();
            WebApiConfig.Register(config);
            appBuilder.UseWebApi(config);
        }

        public void Close()
        {
            if (_container != null)
                _container.Dispose();
        }
    }
}

The sample WindsorInstaller class shows how you can use the OWIN per-web-request scope: 示例WindsorInstaller类显示了如何使用OWIN per-web-request范围:

using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Web.Api.Host
{
    class WindsorInstaller : IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component
                .For<IPerWebRequestDependency>()
                .ImplementedBy<PerWebRequestDependency>()
                .LifestyleScoped<OwinWebRequestScopeAccessor>());

            container.Register(Component
                .For<Controllers.V1.TestController>()
                .LifeStyle.Transient);
        }
    }
}

The solution I have laid out above is based on the existing /src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs and Yoshifumi Kawai'soriginal OwinRequestScopeContext . 我上面提出的解决方案基于现有的/src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs和Yoshifumi Kawai的OwinRequestScopeContext

I tried implementing Johan Boonstra's answer , but found it wasn't working once we got to the ASP.NET MVC Controller methods. 我尝试实现Johan Boonstra的答案 ,但发现一旦我们使用ASP.NET MVC Controller方法它就无法正常工作。

Here's a simpler solution: 这是一个更简单的解决方案:

First, create some Owin middleware that sits at the start of the pipeline and creates a DefaultLifetimeScope 首先,创建一些位于管道开头的Owin中间件,并创建一个DefaultLifetimeScope

public class WebRequestLifestyleMiddleware : OwinMiddleware
{
    public const string EnvironmentKey = "WindsorOwinScope";

    public WebRequestLifestyleMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        ILifetimeScope lifetimeScope = new DefaultLifetimeScope();
        context.Environment[EnvironmentKey] = lifetimeScope;
        try
        {
            await this.Next.Invoke(context);
        }
        finally
        {
            context.Environment.Remove(EnvironmentKey);
            lifetimeScope.Dispose();
        }
    }
}

Insert it at the beginning of the pipeline in your startup configuration: 在启动配置中将其插入管道的开头:

public void Configure(IAppBuilder appBuilder)
{
    appBuilder.Use<WebRequestLifestyleMiddleware>();

    //
    // Further configuration
    //
}

Now you create a class that implements IScopeAccessor and fetches the scope that WebRequestLifestyleMiddleware pushed into the environment: 现在,您创建一个实现IScopeAccessor的类,并获取WebRequestLifestyleMiddleware推送到环境中的范围:

public class OwinWebRequestScopeAccessor : IScopeAccessor
{
    void IDisposable.Dispose() { }

    ILifetimeScope IScopeAccessor.GetScope(CreationContext context)
    {
        IOwinContext owinContext = HttpContext.Current.GetOwinContext();
        string key = WebRequestLifestyleMiddleware.EnvironmentKey;
        return owinContext.Environment[key] as ILifetimeScope;
    }
}

Finally, use this scope accessor when registering component lifetimes. 最后,在注册组件生命周期时使用此范围访问器。 For example, I have my custom component called AccessCodeProvider which I want to reuse throughout one request: 例如,我有一个名为AccessCodeProvider自定义组件,我希望在一个请求中重用它:

container.Register(
      Component.For<AccessCodeProvider>()
               .LifestyleScoped<OwinRequestScopeAccessor>()
);

In this case, AccessCodeProvider will be created the first time it's asked for within a request, and then reused throughout the web request, finally disposed of when WebRequestLifestyleMiddleware invokes lifetimeScope.Dispose() . 在这种情况下, AccessCodeProvider将在请求中第一次被请求时创建,然后在整个Web请求中重用,最后在WebRequestLifestyleMiddleware调用lifetimeScope.Dispose()时被处理掉。

I have created PerScope lifestyle behaving like PerWebRequest in a web api application which uses owin middlewares and castle windsor as IoC of the application. 我在Web api应用程序中创建了类似PerWebRequest的PerScope生活方式,该应用程序使用owin中间件和城堡windsor作为应用程序的IoC。

First of all let's make our windsor container as the IoC of the web api application as following: 首先让我们将我们的windsor容器作为web api应用程序的IoC,如下所示:

    public class WindsorHttpDependencyResolver : IDependencyResolver
    {
        private readonly IWindsorContainer container;

        public WindsorHttpDependencyResolver(IWindsorContainer container)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            this.container = container;
        }

        public object GetService(Type t)
        {
            return this.container.Kernel.HasComponent(t)
             ? this.container.Resolve(t) : null;
        }

        public IEnumerable<object> GetServices(Type t)
        {
            return this.container.ResolveAll(t).Cast<object>().ToArray();
        }

        public IDependencyScope BeginScope()
        {
            return new WindsorDependencyScope(this.container);
        }

        public void Dispose()
        {
        }
    }//end WindsorHttpDependencyResolver 

  public class WindsorDependencyScope : IDependencyScope
    {
        private readonly IWindsorContainer container;
        private readonly IDisposable scope;

        public WindsorDependencyScope(IWindsorContainer container)
        {
            if (container == null)
                throw new ArgumentNullException("container");

            this.container = container;
        }

        public object GetService(Type t)
        {
            return this.container.Kernel.HasComponent(t)
                ? this.container.Resolve(t) : null;
        }

        public IEnumerable<object> GetServices(Type t)
        {
            return this.container.ResolveAll(t).Cast<object>().ToArray();
        }

        public void Dispose()
        {
            this.scope?.Dispose();
        }
    }

Then during application's startup let's register it: 然后在应用程序启动期间让我们注册它:

container.Register(Component.For<System.Web.Http.Dependencies.IDependencyResolver>().ImplementedBy<WindsorHttpDependencyResolver>().LifestyleSingleton());

Now inside the first middleware(which will be the first and the last middleware will be executed) let's begin scope when new request comes to our web api and dispose it when it ends as following: 现在在第一个中间件(将是第一个和最后一个中间件将被执行)内部让我们在新请求到达我们的web api时开始范围,并在它结束时将其处理如下:

public class StartinMiddleware : OwinMiddleware
{

    public StartinMiddleware(OwinMiddleware next) : base(next)
    {
        if (next == null)
        {
            throw new ArgumentNullException("next");
        }
    }


    public override async Task Invoke(IOwinContext context)
    {

        this.Log().Info("Begin request");
        IDisposable scope = null;            
        try
        {
            // here we are using IoCResolverFactory which returns 
            // the instance of IoC container(which will be singleton for the 
            // whole application)
            var ioCResolver= IoCResolverFactory.GetOrCreate();
            //here we are starting new scope
            scope = ioCResolver.BeginScope();

            await Next.Invoke(context);

            this.Log().Info("End request");
        }
        catch (Exception ex)
        { 
          //here you can log exceptions
        }
        finally
        {
            //here we are desposing scope
            scope?.Dispose();
        }
    }
}

The code of IoC factory will be something like this: IoC工厂的代码将是这样的:

public static class IoCResolverFactory
{
    public static IoCResolver iocResolver;

    public static IoCResolver GetOrCreate()
    {
        if (iocResolver != null)
            return iocResolver;

        iocResolver = new IoCResolver();
        return iocResolver;
    }
}// end IoCResolverFactory

public class IoCResolver
{
    private static WindsorContainer container;

    public IoCResolver()
    {
        container = new WindsorContainer();

        container.Register(Component.For<IoCResolver>().Instance(this).LifestyleSingleton());
        container.Register(Component.For<IWindsorContainer>().Instance(container).LifestyleSingleton());
    }


    public IDisposable BeginScope()
    {
        return container.BeginScope();
    }


    public IDisposable GetCurrentScope()
    {
        return Castle.MicroKernel.Lifestyle.Scoped.CallContextLifetimeScope.ObtainCurrentScope();
    }


    public T Resolve<T>()
    {
        return container.Resolve<T>();
    }

    public IList<T> ResolveAll<T>()
    {
        return container.ResolveAll<T>();
    }

    public void Dispose()
    {
        container.Dispose();
    }
}

When registering your services during startup you can register them to be resolved per scope as following: 在启动期间注册服务时,您可以注册它们以按照以下范围解决:

container.Register(Component.For<ICurrentRequestService>().ImplementedBy<CurrentRequestService>().LifestyleScoped());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM