Comment utiliser le mode de vie PerWebRequest de Castle Windsor avec OWIN

Je suis en train de convertir un projet ASP .Net Web API 2 existant pour utiliser OWIN. Le projet utilise Castle Windsor comme cadre d’dependency injection, l’une des dépendances étant configurée pour utiliser le style de vie PerWebRequest.

Lorsque je fais une demande au serveur, une exception Castle.MicroKernel.ComponentResolutionException . L’exception recommande d’append les éléments suivants aux system.web/httpModules et system.WebServer/modules du fichier config:

  

Cela ne résout pas l’erreur.

En s’inspirant de l’exemple fourni par l’intégration OWIN de SimpleInjector, j’ai tenté de définir une étendue dans la classe de démarrage OWIN (ainsi que de mettre à jour le mode de vie de la dépendance) en utilisant:

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

Malheureusement, cela n’a pas fonctionné non plus.

Comment puis-je utiliser le style de vie PerWebRequest de Castle Windsor ou le simuler dans OWIN?

Selon la documentation de Castle Windsor, vous pouvez implémenter votre propre étendue personnalisée. Vous devez implémenter l’interface Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor .

Vous spécifiez ensuite votre accesseur de scope lors de l’enregistrement de votre composant:

 Container.Register(Component.For().LifestyleScoped()); 

La classe OwinWebRequestScopeAccessor implémente IScopeAccessor de 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(); } } } 

Comme vous pouvez le voir, OwinWebRequestScopeAccessor délègue les appels à GetScope et à PerWebRequestLifestyleOwinMiddleware à PerWebRequestLifestyleOwinMiddleware .

La classe PerWebRequestLifestyleOwinMiddleware est le compteur OWIN du module ASP.NET IHttpModule de Castle Windsor, PerWebRequestLifestyleModule .

C’est la classe 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.Threading.Tasks.Task>; public class PerWebRequestLifestyleOwinMiddleware { private readonly AppFunc _next; private const ssortingng c_key = "castle.per-web-request-lifestyle-cache"; private static bool _initialized; public PerWebRequestLifestyleOwinMiddleware(AppFunc next) { _next = next; } public async Task Invoke(IDictionary 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); } ///  /// 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. ///  ///  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 { ///  /// Use . ///  /// Owin app. ///  public static IAppBuilder UsePerWebRequestLifestyleOwinMiddleware(this IAppBuilder app) { return app.Use(typeof(PerWebRequestLifestyleOwinMiddleware)); } } } 

Le module ASP.NET IHttpModule de PerWebRequestLifestyleModule Castle Windsor utilise HttpContext.Current pour stocker le Castle Windsor ILifetimeScope sur une base de requête Web. PerWebRequestLifestyleOwinMiddleware classe PerWebRequestLifestyleOwinMiddleware utilise OwinRequestScopeContext.Current . Ceci est basé sur l’idée de Yoshifumi Kawai .

L’implémentation de OwinRequestScopeContext ci-dessous est mon implémentation légère de l’original OwinRequestScopeContext de Yoshifumi Kawai.


Remarque: si vous ne souhaitez pas cette implémentation légère, vous pouvez utiliser l’excellente implémentation d’origine de Yoshifumi Kawai en exécutant cette commande dans la console NuGet Package Manager:

PM> Install-Package OwinRequestScopeContext


OwinRequestScopeContext légère de 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 Items { get; } DateTime Timestamp { get; } void EndRequest(); } public class OwinRequestScopeContext : IOwinRequestScopeContext { const ssortingng c_callContextKey = "owin.reqscopecontext"; private readonly DateTime _utcTimestamp = DateTime.UtcNow; private ConcurrentDictionary _items = new ConcurrentDictionary(); ///  /// Gets or sets the  object /// for the current HTTP request. ///  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 Items { get { return _items; } } public DateTime Timestamp { get { return _utcTimestamp.ToLocalTime(); } } } } 

Lorsque vous avez toutes les pièces en place, vous pouvez lier les choses. Dans votre classe de démarrage OWIN, appelez appBuilder.UsePerWebRequestLifestyleOwinMiddleware(); méthode d’extension pour enregistrer le middleware OWIN défini ci-dessus. Faites cela avant 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(); } } } 

L’exemple de classe WindsorInstaller montre comment utiliser l’étendue OWIN par demande Web:

 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() .ImplementedBy() .LifestyleScoped()); container.Register(Component .For() .LifeStyle.Transient); } } } 

La solution que j’ai présentée ci-dessus est basée sur le /src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs existant et sur le fichier OwinRequestScopeContext original de Yoshifumi OwinRequestScopeContext .

J’ai essayé d’implémenter la réponse de Johan Boonstra , mais j’ai constaté que cela ne fonctionnait pas une fois les méthodes du contrôleur ASP.NET MVC mises en œuvre.

Voici une solution plus simple:

Commencez par créer un middleware Owin situé au début du pipeline et créant un DefaultLifetimeScope

 public class WebRequestLifestyleMiddleware : OwinMiddleware { public const ssortingng 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(); } } } 

Insérez-le au début du pipeline dans votre configuration de démarrage:

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

Maintenant, vous créez une classe qui implémente IScopeAccessor et récupère la scope que WebRequestLifestyleMiddleware a WebRequestLifestyleMiddleware dans l’environnement:

 public class OwinWebRequestScopeAccessor : IScopeAccessor { void IDisposable.Dispose() { } ILifetimeScope IScopeAccessor.GetScope(CreationContext context) { IOwinContext owinContext = HttpContext.Current.GetOwinContext(); ssortingng key = WebRequestLifestyleMiddleware.EnvironmentKey; return owinContext.Environment[key] as ILifetimeScope; } } 

Enfin, utilisez cet accesseur d’étendue lors de l’enregistrement de la durée de vie d’un composant. Par exemple, j’ai un composant personnalisé appelé AccessCodeProvider que je souhaite réutiliser tout au long d’une requête:

 container.Register( Component.For() .LifestyleScoped() ); 

Dans ce cas, AccessCodeProvider sera créé la première fois que cela est demandé dans une demande, puis réutilisé tout au long de la demande Web, et finalement éliminé lorsque WebRequestLifestyleMiddleware invoqueralifeScope.Dispose lifetimeScope.Dispose() .

J’ai créé le mode de vie PerScope se comportant comme PerWebRequest dans une application Web api qui utilise des middlewares owin et un château windsor comme IoC de l’application.

Tout d’abord, faisons de notre conteneur windsor l’IoC de l’application API Web suivant:

  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 GetServices(Type t) { return this.container.ResolveAll(t).Cast().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 GetServices(Type t) { return this.container.ResolveAll(t).Cast().ToArray(); } public void Dispose() { this.scope?.Dispose(); } } 

Puis, au démarrage de l’application, enregistrons-le:

 container.Register(Component.For().ImplementedBy().LifestyleSingleton()); 

Maintenant, dans le premier middleware (qui sera le premier et le dernier middleware à être exécuté), commençons la scope lorsque de nouvelles requêtes arrivent sur notre API Web et les supprimons à la fin de la procédure suivante:

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

Le code de l’usine IoC ressemblera à ceci:

 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().Instance(this).LifestyleSingleton()); container.Register(Component.For().Instance(container).LifestyleSingleton()); } public IDisposable BeginScope() { return container.BeginScope(); } public IDisposable GetCurrentScope() { return Castle.MicroKernel.Lifestyle.Scoped.CallContextLifetimeScope.ObtainCurrentScope(); } public T Resolve() { return container.Resolve(); } public IList ResolveAll() { return container.ResolveAll(); } public void Dispose() { container.Dispose(); } } 

Lorsque vous enregistrez vos services au démarrage, vous pouvez les enregistrer pour qu’ils soient résolus comme suit:

 container.Register(Component.For().ImplementedBy().LifestyleScoped());