我正在转换现有的ASP .Net Web API 2项目以使用OWIN。该项目使用Castle Windsor作为依赖注入框架,其中一个依赖项设置为使用PerWebRequest生活方式。
当我向服务器发出请求时,我收到Castle.MicroKernel.ComponentResolutionException
个异常。该异常建议将以下内容添加到配置文件中的system.web/httpModules
和system.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中模拟它?
答案 0 :(得分:7)
根据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
将对 GetScope 和 Dispose 的调用委托给PerWebRequestLifestyleOwinMiddleware
。
班级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
在每个网络请求的基础上存储Castle Windsor ILifetimeScope
。 PerWebRequestLifestyleOwinMiddleware
班级使用OwinRequestScopeContext.Current
。这是基于Yoshifumi Kawai的想法。
以下OwinRequestScopeContext
的实现是我对Yoshifumi Kawai原始OwinRequestScopeContext
的轻量级实现。
注意:如果你不想要这个轻量级的实现,你可以通过在NuGet包管理器控制台中运行这个命令来使用Yoshifumi Kawai的优秀原始实现:
PM> 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
。
答案 1 :(得分:6)
我尝试实现here,但发现在我们使用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
将在请求中第一次被请求时创建,然后在整个Web请求中重复使用,最后在WebRequestLifestyleMiddleware
调用{{1}时被处理掉}}
答案 2 :(得分:1)
我在Web api应用程序中创建了PerScope生活方式,它使用owin中间件和城堡windsor作为应用程序的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());