Castle.Windsor生活方式取决于具体情况?

时间:2012-08-08 22:06:59

标签: c# asp.net-mvc-3 inversion-of-control castle-windsor quartz.net

我有一个使用.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()
);

我想我应该使用某种扩展方法,但我只是看不到它..

5 个答案:

答案 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.RegisterIRegistration.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

中提取您的上下文

希望这有帮助。