我有很多地方部件均采用注册的Web应用程序.LifestylePerWebRequest()
现在我已经决定实施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()
);
我想我应该用某种扩展方法,但我就是不明白它..
您应该使用混合的生活方式从castleprojectcontrib 。
一个混合的生活方式是一个实际的共混物2种底层生活方式:主生活方式和次级生活方式。 混合动力生活方式首先尝试使用主生活方式; 如果它是出于某种原因无法使用,它使用的二次生活方式。 这通常与PerWebRequest作为主要的生活方式:如果HTTP上下文可用,它用作范围组件实例; 否则使用次级生活方式。
不要使用相同的组件。 事实上,在大多数情况下,我看到了“后台处理”甚至没有意义的,是在web过程开始。
在阐述基础上的意见。
Shoehorning后台处理在网络管道影响您的架构,以节省一个EC2实例数$。 我强烈建议重新考虑这一点,但我离题。
我声明仍然有效,即使你把两个组件在网络过程中,他们是在两个不同的上下文中使用两个不同的组成部分,应受到同样的对待。
我最近有一个非常类似的问题 - 我希望能够基于初始化代码把我在应用程序启动容器,当HttpContext.Request尚不存在运行。 我没有发现这样做的任何方式,所以我修改了PerWebRequestLifestyleModule的来源,让我做什么我想要的。 不幸的是,似乎没有什么能使这种变化,不需要重新编译温莎 - 我希望我能做到这一点在可扩展的方式,所以我可以继续使用温莎的主要分布。
总之,使这项工作,我修改了GetScope
的功能PerWebRequestLifestyleModule
这样,如果它不是在运行的HttpContext(或如果HttpContext.Request抛出一个异常,就像它的Application_Start做),那么它会寻找一个范围从启动容器代替。 这让我用下面的代码使用我的容器中的Application_Start:
using (var scope = container.BeginScope())
{
// LifestylePerWebRequest components will now be scoped to this explicit scope instead
// _container.Resolve<...>()
}
有没有必要担心明确处置的事情,因为当Scope是他们将被安置。
我已经把完整代码下面的模块。 我不得不洗牌这一类,它的工作范围内围绕一对夫妇的其他东西,但它本质上是相同的。
public class PerWebRequestLifestyleModule : IHttpModule
{
private const string key = "castle.per-web-request-lifestyle-cache";
private static bool allowDefaultScopeOutOfHttpContext = true;
private static bool initialized;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
initialized = true;
context.EndRequest += Application_EndRequest;
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var scope = GetScope(application.Context, createIfNotPresent: false);
if (scope != null)
{
scope.Dispose();
}
}
private static bool IsRequestAvailable()
{
if (HttpContext.Current == null)
{
return false;
}
try
{
if (HttpContext.Current.Request == null)
{
return false;
}
return true;
}
catch (HttpException)
{
return false;
}
}
internal static ILifetimeScope GetScope()
{
var context = HttpContext.Current;
if (initialized)
{
return GetScope(context, createIfNotPresent: true);
}
else if (allowDefaultScopeOutOfHttpContext && !IsRequestAvailable())
{
// We're not running within a Http Request. If the option has been set to allow a normal scope to
// be used in this situation, we'll use that instead
ILifetimeScope scope = CallContextLifetimeScope.ObtainCurrentScope();
if (scope == null)
{
throw new InvalidOperationException("Not running within a Http Request, and no Scope was manually created. Either run from within a request, or call container.BeginScope()");
}
return scope;
}
else if (context == null)
{
throw new InvalidOperationException(
"HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net");
}
else
{
EnsureInitialized();
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 = HttpContext.Current;
if (context == null)
{
return null;
}
var scope = GetScope(context, createIfNotPresent: true);
if (scope != null)
{
context.Items.Remove(key);
}
return scope;
}
private static void EnsureInitialized()
{
if (initialized)
{
return;
}
var message = new StringBuilder();
message.AppendLine("Looks like you forgot to register the http module " + typeof(PerWebRequestLifestyleModule).FullName);
message.AppendLine("To fix this add");
message.AppendLine("<add name=\"PerRequestLifestyle\" type=\"Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor\" />");
message.AppendLine("to the <httpModules> section on your web.config.");
if (HttpRuntime.UsingIntegratedPipeline)
{
message.AppendLine(
"Windsor also detected you're running IIS in Integrated Pipeline mode. This means that you also need to add the module to the <modules> section under <system.webServer>.");
}
else
{
message.AppendLine(
"If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the <modules> section under <system.webServer>.");
}
#if !DOTNET35
message.AppendLine("Alternatively make sure you have " + PerWebRequestLifestyleModuleRegistration.MicrosoftWebInfrastructureDll +
" assembly in your GAC (it is installed by ASP.NET MVC3 or WebMatrix) and Windsor will be able to register the module automatically without having to add anything to the config file.");
#endif
throw new ComponentResolutionException(message.ToString());
}
private static ILifetimeScope GetScope(HttpContext context, bool createIfNotPresent)
{
var candidates = (ILifetimeScope)context.Items[key];
if (candidates == null && createIfNotPresent)
{
candidates = new DefaultLifetimeScope(new ScopeCache());
context.Items[key] = candidates;
}
return candidates;
}
}
好吧,我想出了一个非常干净的方式做到这一点!
首先,我们需要的实现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的替代品。
我们需要这种选择与城堡注册。 我这样做之前,我开始注册任何其他组件:
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());
}
我不知道什么幕后的发生.LifestylePerWebRequest()
; 但是这是我的场景“每个请求的上下文”做:
检查HttpContext
会话,如果存在拉从上下文.Items
。 如果它不存在拉从您的上下文System.Threading.Thread.CurrentContext
。
希望这可以帮助。