簡體   English   中英

Castle.Windsor生活方式取決於具體情況?

[英]Castle.Windsor lifestyle depending on context?

我有一個Web應用程序,其中許多組件使用.LifestylePerWebRequest()注冊,現在我決定實現Quartz.NET ,一個.NET作業調度庫,它在不同的線程中執行,而不是Request線程。

因此, HttpContext.Current產生null 到目前為止,我的服務,存儲庫和IDbConnection使用.LifestylePerWebRequest()實例化,因為它使得在請求結束時更容易處理它們。

現在我想在兩種情況下使用這些組件,在Web請求期間我希望它們不受影響,在非請求上下文中我希望它們使用不同的生活方式,我想我可以自己處理處理,但我該怎么辦關於它根據當前背景選擇組件的生活方式?

目前我注冊服務(例如),像這樣:

container.Register(
    AllTypes
        .FromAssemblyContaining<EmailService>()
        .Where(t => t.Name.EndsWith("Service"))
        .WithService.Select(IoC.SelectByInterfaceConvention)
        .LifestylePerWebRequest()
);

我想我應該使用某種擴展方法,但我只是看不到它..

您應該使用來自castleprojectcontrib的 Hybrid Lifestyle

混合生活方式實際上融合了兩種潛在的生活方式:主要生活方式和次要生活方式。 混合生活方式首先嘗試使用主要的生活方式; 如果由於某種原因它不可用,它會使用次要的生活方式。 這通常與PerWebRequest一起用作主要生活方式:如果HTTP上下文可用,則將其用作組件實例的范圍; 否則使用次要生活方式。

不要使用相同的組件。 事實上,在大多數情況下,我已經看到“后台處理”甚至沒有意義在網絡流程中開始。

根據評論進行闡述。

Web管道中的Shoehorning后台處理會影響您的體系結構,以便在EC2實例上節省一些$。 我強烈建議再考慮一下,但我離題了。

我的陳述仍然有效,即使你將兩個組件都放在Web過程中,它們是兩個不同的上下文中使用的兩個不同的組件,應該這樣對待。

我最近遇到了一個非常類似的問題 - 當HttpContext.Request尚不存在時,我希望能夠在Application啟動時根據我的容器運行初始化代碼。 我沒有找到任何方法,所以我修改了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<...>()

}

沒有必要擔心明確處理事物,因為它們將在范圍內被處置。

我已經為下面的模塊提供了完整的代碼。 我不得不在這個課程中改變其他一些東西以使它工作,但它基本上是一樣的。

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替代方案。

我們需要在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());
    }

我不知道幕后發生了什么.LifestylePerWebRequest() ; 但這就是我為“每個請求的上下文”場景所做的事情:

檢查HttpContext的會話,如果存在則從.Items提取上下文。 如果它不存在,請從System.Threading.Thread.CurrentContext提取您的上下文。

希望這可以幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM