【问题标题】:How to use Castle Windsor's PerWebRequest lifestyle with OWIN如何在 OWIN 中使用 Castle Windsor 的 PerWebRequest 生活方式
【发布时间】:2015-10-03 10:06:21
【问题描述】:

我正在将现有的 ASP .Net Web API 2 项目转换为使用 OWIN。该项目使用 Castle Windsor 作为依赖注入框架,其中一个依赖项设置为使用 PerWebRequest 生活方式。

当我向服务器发出请求时,我得到一个Castle.MicroKernel.ComponentResolutionException 异常。异常建议将以下内容添加到配置文件中的system.web/httpModulessystem.WebServer/modules 部分:

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

这并不能解决错误。

从 SimpleInjector 的 OWIN 集成提供的示例中获得灵感,我尝试使用以下方法在 OWIN 启动类中设置范围(以及更新依赖项的生活方式):

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

不幸的是,这也没有奏效。

如何使用 Castle Windsor 的 PerWebRequest 生活方式或在 OWIN 中模拟它?

【问题讨论】:

    标签: c# asp.net-web-api owin castle-windsor perwebrequest


    【解决方案1】:

    根据 Castle Windsor 文档,您可以实现自己的自定义范围。你必须实现Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor 接口。

    然后在注册组件时指定范围访问器:

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

    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();
            }
        }
    }
    

    如您所见,OwinWebRequestScopeAccessor 将调用委托给 GetScopeDisposePerWebRequestLifestyleOwinMiddleware

    PerWebRequestLifestyleOwinMiddleware 类是 Castle Windsor 的 ASP.NET IHttpModule PerWebRequestLifestyleModule 的 OWIN 计数器部分。

    这是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 的 ASP.NET IHttpModule PerWebRequestLifestyleModule 使用 HttpContext.Current 在每个 web 请求的基础上存储 Castle Windsor ILifetimeScopePerWebRequestLifestyleOwinMiddleware 类使用 OwinRequestScopeContext.Current。这是基于Yoshifumi Kawai的思想。

    下面OwinRequestScopeContext的实现是我对川井佳文原创OwinRequestScopeContext的轻量级实现。


    注意:如果您不想要这种轻量级实现,您可以通过在 NuGet 包管理器控制台中运行以下命令来使用 Yoshifumi Kawai 的优秀原始实现:

    PM&gt; Install-Package 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();
                }
            }
        }
    }
    

    当你把所有的部分都准备好后,你就可以把它们绑起来了。在您的 OWIN 启动类中调用 appBuilder.UsePerWebRequestLifestyleOwinMiddleware(); 扩展方法来注册上面定义的 OWIN 中间件。在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();
            }
        }
    }
    

    示例 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);
            }
        }
    }
    

    我上面列出的解决方案是基于现有的/src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs和Yoshifumi Kawai的原始OwinRequestScopeContext

    【讨论】:

    • 有趣 - 我在 System.Runtime.Remoting.Messaging.CallContext 看到这家商店 OwinRequestScopeContext。我的第一个想法是将其存储在 Owin 环境字典中。 (OwinContext.Environment)。
    • 虽然经过进一步检查,我想我可以看到一个原因:System.Runtime.Remoting.Messaging.CallContext 具有全局可访问的静态成员,但没有安全的方法来静态获取 OwinContext
    【解决方案2】:

    我尝试实现 Johan Boonstra's answer,但在我们使用 ASP.NET MVC 控制器方法后发现它不起作用。

    这是一个更简单的解决方案:

    首先,创建一些位于管道开头的 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();
            }
        }
    }
    

    将它插入到启动配置中管道的开头:

    public void Configure(IAppBuilder appBuilder)
    {
        appBuilder.Use<WebRequestLifestyleMiddleware>();
    
        //
        // Further configuration
        //
    }
    

    现在您创建一个实现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;
        }
    }
    

    最后,在注册组件生命周期时使用这个范围访问器。例如,我有一个名为 AccessCodeProvider 的自定义组件,我想在一个请求中重用它:

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

    在这种情况下,AccessCodeProvider 将在第一次在请求中被请求时创建,然后在整个网络请求中重复使用,最后在 WebRequestLifestyleMiddleware 调用 lifetimeScope.Dispose() 时被处理。

    【讨论】:

    • 我发现这很有帮助,但在我的情况下,我使用的是自托管和 HttpContext.Current 对我不起作用......相反,我使用了OwinRequestScopeContext nuget 包(github.com/neuecc/OwinRequestScopeContext ) 然后我只需要修改该行以将 OwinContext 设置为 IOwinContext owinContext = new Microsoft.Owin.OwinContext(OwinRequestScopeContext.Current.Environment); 并且它起作用了。
    • 对我来说效果很好,除了线程问题。 DefaultLifetimeScope 应该替换为 ThreadSafeDefaultLifetimeScope 因为这是一个多线程环境..
    【解决方案3】:

    我在一个 web api 应用程序中创建了 PerScope 生活方式,其行为类似于 PerWebRequest,该应用程序使用 owin 中间件和城堡温莎作为应用程序的 IoC。

    首先让我们将 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();
            }
        }
    

    然后在应用程序启动时注册它:

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

    现在在第一个中间件中(这将是第一个和最后一个中间件将被执行)让我们在新请求到达我们的 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();
            }
        }
    }
    

    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();
        }
    }
    

    在启动期间注册服务时,您可以将它们注册为按范围解析,如下所示:

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

    【讨论】:

      猜你喜欢
      • 2011-08-12
      • 1970-01-01
      • 2018-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多