问题描述
我正在将现有的ASP .Net Web API 2项目转换为使用OWIN.该项目使用Castle Windsor作为依赖项注入框架,并设置了其中一种依赖项以使用PerWebRequest生活方式.
I am converting an existing ASP .Net Web API 2 project to use OWIN. The project uses Castle Windsor as the dependency injection framework with one of the dependencies set to use the PerWebRequest lifestyle.
当我向服务器发出请求时,出现了Castle.MicroKernel.ComponentResolutionException
异常.异常建议将以下内容添加到配置文件中的system.web/httpModules
和system.WebServer/modules
部分:
When I make a request to the server I get a Castle.MicroKernel.ComponentResolutionException
exception. The exception recommends adding the following to the system.web/httpModules
and system.WebServer/modules
sections in the config file:
<add name="PerRequestLifestyle"
type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor" />
这不能解决错误.
从SimpleInjector的OWIN集成提供的示例中汲取灵感,我尝试使用以下方法在OWIN启动类中设置范围(以及更新依赖项的生活方式):
Taking inspiration from the example provided by SimpleInjector's OWIN integration, I attempted to set a scope in the OWIN startup class (as well as update the dependency's lifestyle) using:
appBuilder.User(async (context, next) =>
{
using (config.DependencyResolver.BeginScope()){
{
await next();
}
}
不幸的是,这也不起作用.
Unfortunately this hasn't worked either.
如何使用温莎城堡的PerWebRequest生活方式或在OWIN中进行模拟?
How can I use Castle Windsor's PerWebRequest lifestyle or simulate it in OWIN?
推荐答案
根据Castle Windsor文档,您可以实现自己的自定义范围.您必须实现Castle.MicroKernel.Lifestyle.Scoped.IScopeAccessor
接口.
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 >());
类OwinWebRequestScopeAccessor
实现了Castle.Windsor的IScopeAccessor
:
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();
}
}
}
如您所见,OwinWebRequestScopeAccessor
将调用委派给 GetScope ,并将处置委派给PerWebRequestLifestyleOwinMiddleware
.
As you can see OwinWebRequestScopeAccessor
delegates the calls to GetScope and Dispose to PerWebRequestLifestyleOwinMiddleware
.
类PerWebRequestLifestyleOwinMiddleware
是温莎城堡的ASP.NET IHttpModule的OWIN计数器部分 PerWebRequestLifestyleModule .
The class PerWebRequestLifestyleOwinMiddleware
is the OWIN counter part of Castle Windsor's ASP.NET IHttpModule PerWebRequestLifestyleModule.
这是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请求存储Castles Windsor ILifetimeScope
. PerWebRequestLifestyleOwinMiddleware
类使用OwinRequestScopeContext.Current
.这基于 Yoshifumi Kawai 的想法.
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.
以下OwinRequestScopeContext
的实现是我对Yoshifumi Kawai原始的OwinRequestScopeContext
的轻量级实现.
The implementation of OwinRequestScopeContext
below is my lightweight implementation of Yoshifumi Kawai's original OwinRequestScopeContext
.
注意:如果您不希望这种轻量级实现,可以在NuGet软件包管理器控制台中运行以下命令来使用Yoshifumi Kawai出色的原始实现:
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
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);
之前执行此操作:
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();
}
}
}
示例WindsorInstaller类展示了如何使用OWIN每个Web请求范围:
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);
}
}
}
我在上面提出的解决方案是基于现有的/src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs
和川义芳纯正的OwinRequestScopeContext
.
The solution I have laid out above is based on the existing /src/Castle.Windsor/MicroKernel/Lifestyle/PerWebRequestLifestyleModule.cs
and Yoshifumi Kawai'soriginal OwinRequestScopeContext
.
这篇关于如何在OWIN中使用温莎城堡的PerWebRequest生活方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!