2015-07-14 12 views
5

Przekształcam istniejący projekt ASP.Net Web API 2 w OWIN. W projekcie wykorzystano Castle Windsor jako strukturę zależności między iniekcjami, a jedną z zależności ustawionych na stosowanie stylu życia PerWebRequest.Jak używać stylu życia PerWebRequest Castle Windsora z OWINEM

Po zgłoszeniu żądania do serwera otrzymuję wyjątek od Castle.MicroKernel.ComponentResolutionException. Wyjątkiem zaleca dodanie następujących do system.web/httpModules i system.WebServer/modules sekcji w pliku konfiguracyjnym:

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

To nie rozwiąże błąd.

wzorując się na przykładzie dostarczonych przez integracji SimpleInjector za OWIN próbowałem ustawić zakres w klasie startowej OWIN (jak również aktualizować życia zależność jest) używając:

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

Niestety nie pracował zarówno.

Jak mogę korzystać ze stylu życia Castle Windsor w PerWebRequest lub symulować go w OWIN?

Odpowiedz

6

Zgodnie z dokumentacją Castle Windsor można zaimplementować własny niestandardowy zakres. Musisz wdrożyć interfejs Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor.

Następnie określ zakres akcesor podczas rejestracji komponent:

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

Klasa OwinWebRequestScopeAccessor implementuje Castle.Windsor za 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(); 
     } 
    } 
} 

Jak widać OwinWebRequestScopeAccessor delegatów wywołań GetScope i Usunąć do PerWebRequestLifestyleOwinMiddleware.

Klasa Klasa PerWebRequestLifestyleOwinMiddleware jest częścią licznika OWIN modułu ASP.NET IHttpModule Castle Windsor PerWebRequestLifestyleModule.

Jest to klasa 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)); 
     } 
    } 
} 

zamku Windsor w ASP.NET IHttpModule PerWebRequestLifestyleModule wykorzystuje HttpContext.Current do przechowywania zamku Windsor ILifetimeScope na zasadzie per-web-żądanie. PerWebRequestLifestyleOwinMiddleware klasa używa OwinRequestScopeContext.Current. Jest to oparte na idei Yoshifumi Kawai.

Poniżej znajduje się implementacja OwinRequestScopeContext, która jest moją lekką implementacją oryginalnej wersji Yoshifumi Kawai OwinRequestScopeContext.


Uwaga: jeśli nie chcesz to lekki realizacji można wykorzystać doskonałą oryginalną realizację Yoshifumi Kawai poprzez uruchomienie tego polecenia w Nuget konsoli Package Manager:

PM> Install-Package OwinRequestScopeContext


Lightweight implementacja OwinRequestScopeContext:

Gdy masz wszystkie elementy na miejscu, możesz związać rzeczy. W swojej klasie startowej OWIN wywołaj metodę rozszerzenia appBuilder.UsePerWebRequestLifestyleOwinMiddleware();, aby zarejestrować zdefiniowane wyżej medium OWIN. Zrób to przed 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(); 
     } 
    } 
} 

Klasa próbka WindsorInstaller pokazuje w jaki sposób można korzystać z OWIN zakres 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); 
     } 
    } 
} 

Rozwiązanie Mam określonymi powyżej opiera się na istniejących /src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs i Yoshifumi Kawai'soriginal OwinRequestScopeContext.

+0

Interesujące - widzę to przechowuje 'OwinRequestScopeContext' w' System.Runtime.Remoting.Messaging.CallContext'. Moją pierwszą myślą było, aby to zapisać w słowniku środowiska Owin. ('OwinContext.Environment'). –

+0

Chociaż po dalszym badaniu, myślę, że widzę powód: 'System.Runtime.Remoting.Messaging.CallContext' ma globalnie dostępne statyczne elementy, ale nie ma bezpiecznego sposobu statycznego pozyskania' OwinContext'. –

2

Próbowałem wdrażanie Johan Boonstra's answer, ale okazało się, że nie działa, gdy mamy do metod kontrolera ASP.NET MVC.

Oto prostsze rozwiązanie:

pierwsze, stworzyć jakiś Owin middleware, który znajduje się na początku rurociągu i tworzy 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(); 
     } 
    } 
} 

Włóż go na początku rurociągu w konfiguracji startowej :

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

    // 
    // Further configuration 
    // 
} 

teraz utworzyć klasę, która implementuje IScopeAccessor i pobiera zakres tego WebRequestLifestyleMiddleware wciśnięty do środowiska:

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

Wreszcie, użyj tego akcesora do zakresu podczas rejestracji czasu życia komponentów. Na przykład, mam zwyczaj składnik zwany AccessCodeProvider które ma być wykorzystany w ciągu jednego wniosku:

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

W tym przypadku AccessCodeProvider zostanie utworzony po raz pierwszy to hasła w żądaniu, a następnie ponownego wykorzystania całej sieci żądanie, ostatecznie unieszkodliwione, gdy WebRequestLifestyleMiddleware wywołuje lifetimeScope.Dispose().

Powiązane problemy