我有一个使用.LifestylePerWebRequest()
注册了许多组件的Web应用程序,现在我决定实现 Quartz.NET ,一个.NET作业调度库,它在不同的线程中执行,而不是请求线程。
因此,HttpContext.Current
会产生null
。到目前为止,我的服务,存储库和IDbConnection
已使用.LifestylePerWebRequest()
实例化,因为它可以在请求结束时更轻松地处理它们。
现在我想在两种情况下使用这些组件,在Web请求期间我希望它们不受影响,在非请求上下文中我希望它们使用不同的生活方式,我想我可以自己处理处理,但是如何我应该根据当前的背景来选择组件的生活方式吗?
目前我注册服务(例如),如下所示:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
我想我应该使用某种扩展方法,但我只是看不到它..
答案 0 :(得分:24)
您应该使用Hybrid Lifestyle中的castleprojectcontrib。
混合生活方式实际上融合了两种潜在的生活方式:主要生活方式和次要生活方式。混合生活方式首先尝试使用主要的生活方式;如果由于某种原因它不可用,它会使用次要的生活方式。这通常与PerWebRequest一起用作主要生活方式:如果HTTP上下文可用,则将其用作组件实例的范围;否则使用次要生活方式。
答案 1 :(得分:6)
请勿使用相同的组件。事实上,在大多数情况下,我看到“后台处理”在开始的网络流程中甚至没有意义。
根据评论进行阐述。
在Web管道中进行背景处理会影响您的体系结构,从而在EC2实例上节省一些$。我强烈建议再考虑一下,但我离题了。
我的陈述仍然有效,即使你将两个组件都放在网络流程中,它们是在两个不同的上下文中使用的两个不同的组件,应该这样对待。
答案 2 :(得分:3)
我最近遇到了一个非常类似的问题 - 当HttpContext.Request尚不存在时,我希望能够在Application启动时根据我的容器运行初始化代码。我没有找到任何方法,所以我修改了PerWebRequestLifestyleModule的源代码以允许我做我想做的事情。不幸的是,如果不重新编译温莎,似乎不可能做出这种改变 - 我希望我能够以可扩展的方式做到这一点,所以我可以继续使用温莎的主要发行版。
无论如何,为了使这项工作,我修改了GetScope
的{{1}}函数,以便它不在HttpContext中运行(或者如果HttpContext.Request抛出异常,就像它在Application_Start)然后它会查找从容器开始的Scope。这允许我使用以下代码在Application_Start中使用我的容器:
PerWebRequestLifestyleModule
没有必要担心明确处理事物,因为它们将在范围内被处置。
我已经为下面的模块提供了完整的代码。我必须在这个课程中改变其他一些东西才能使它工作,但它基本上是一样的。
using (var scope = container.BeginScope())
{
// LifestylePerWebRequest components will now be scoped to this explicit scope instead
// _container.Resolve<...>()
}
答案 3 :(得分:2)
好的,我想出了一个非常干净的方法来做到这一点!
首先,我们需要IHandlerSelector
的实现,这可以根据我们对此事的看法选择处理程序,或保持中立(通过返回null
,这意味着“没有意见” )。
/// <summary>
/// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle.
/// </summary>
public class LifestyleSelector : IHandlerSelector
{
public bool HasOpinionAbout(string key, Type service)
{
return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null.
}
public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
{
if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest))
{
if (HttpContext.Current == null)
{
return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest);
}
else
{
return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest);
}
}
return null; // we don't have an opinion in this case.
}
}
我这么做是因为意见非常有限。只有当有两个处理程序并且其中一个具有PerWebRequest
生活方式时,我才会有意见;意思是另一个是 可能 非HttpContext替代方案。
我们需要在Castle中注册此选择器。我在开始注册任何其他组件之前这样做了:
container.Kernel.AddHandlerSelector(new LifestyleSelector());
最后,我希望我有任何关于如何复制注册以避免这种情况的线索:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerThread()
);
如果您可以找到克隆注册的方法,请更改生活方式并注册两者(使用container.Register
或IRegistration.Register
),请在此处将其作为答案发布! :)
更新:在测试中,我需要唯一地命名相同的注册名称,我这样做:
.NamedRandomly()
public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> registration) where T : class
{
string name = registration.Implementation.FullName;
string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid());
return registration.Named(random);
}
public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration)
{
return registration.Configure(x => x.NamedRandomly());
}
答案 4 :(得分:1)
我不知道.LifestylePerWebRequest()
幕后发生了什么事;但这就是我为“每个请求的上下文”场景所做的事情:
检查HttpContext
会话,如果存在则从.Items
拉出上下文。
如果它不存在,请从System.Threading.Thread.CurrentContext
。
希望这有帮助。