How to use Castle Windsor's PerWebRequest lifestyle with OWIN

折月煮酒 提交于 2019-12-05 07:29:28

According to the Castle Windsor documentation you can implement your own custom scope. You have to implement the Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor interface.

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:

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.

The class PerWebRequestLifestyleOwinMiddleware is the OWIN counter part of Castle Windsor's ASP.NET IHttpModule PerWebRequestLifestyleModule.

This is the PerWebRequestLifestyleOwinMiddleware class:

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. PerWebRequestLifestyleOwinMiddleware class uses OwinRequestScopeContext.Current. This is based on the idea of Yoshifumi Kawai.

The implementation of OwinRequestScopeContext below is my lightweight implementation of Yoshifumi Kawai's original 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:

PM> Install-Package OwinRequestScopeContext


Lightweight implementation of 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(); extension method to register the OWIN middle ware defined above. Do this before 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:

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.

I tried implementing Johan Boonstra's answer, but found it wasn't working once we got to the ASP.NET MVC Controller methods.

Here's a simpler solution:

First, create some Owin middleware that sits at the start of the pipeline and creates a 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:

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:

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().

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.

First of all let's make our windsor container as the IoC of the web api application as following:

    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:

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:

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());
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!