26

を使用して多くのコンポーネントが登録されているWebアプリケーションがあります。ここで、要求スレッドではなく、別々のスレッドで実行される.NETジョブスケジューリングライブラリであるQuartz.NET.LifestylePerWebRequest()を実装することにしました。

そのため、HttpContext.Currentを生成しnullます。私のサービス、リポジトリ、およびは、リクエストが終了したときにそれらを簡単に破棄できるようになったためIDbConnection、これまで使用してインスタンス化されていました。.LifestylePerWebRequest()

これらのコンポーネントを両方のシナリオで使用したいのですが、Webリクエスト中は影響を受けないようにし、リクエスト以外のコンテキストでは別のライフスタイルを使用したいので、自分で処理できると思いますが、どうすればよいですか?現在の状況に基づいてコンポーネントのライフスタイルを選択するためにそれについて?

現在、私は次のようなサービスを登録しています(たとえば)。

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

ある種の拡張方法を使用する必要があると思いますが、表示されません。

4

5 に答える 5

24

CastleprojectcontribHybridLifestyleを使用する必要があります。

ハイブリッドライフスタイルとは、メインライフスタイルとセカンダリライフスタイルの2つの基本的なライフスタイルを実際にブレンドしたものです。ハイブリッドライフスタイルは、最初にメインライフスタイルを使用しようとします。なんらかの理由で利用できない場合は、二次的なライフスタイルを使用します。これは通常、主なライフスタイルとしてPerWebRequestで使用されます。HTTPコンテキストが使用可能な場合は、コンポーネントインスタンスのスコープとして使用されます。それ以外の場合は、二次的なライフスタイルが使用されます。

于 2012-08-09T08:35:56.493 に答える
6

同じコンポーネントを使用しないでください。実際、ほとんどのシナリオで、「バックグラウンド処理」が最初からWebプロセスに含まれることすら意味がないことを確認しました。

コメントに基づいて詳しく説明します。

Webパイプラインでのバックグラウンド処理のシューホーニングは、EC2インスタンスで数ドルを節約するためにアーキテクチャを危険にさらしています。これについてもう一度考えることを強くお勧めしますが、私は逸脱します。

両方のコンポーネントをWebプロセスに配置している場合でも、これらは2つの異なるコンテキストで使用される2つの異なるコンポーネントであり、そのように扱う必要があります。

于 2012-08-09T20:33:19.257 に答える
4

最近、非常によく似た問題が発生しました。HttpContext.Requestがまだ存在しない場合に、アプリケーションの起動時にコンテナーに基づいて初期化コードを実行できるようにしたかったのです。それを行う方法が見つからなかったので、PerWebRequestLifestyleModuleのソースを変更して、やりたいことができるようにしました。残念ながら、Windsorを再コンパイルせずにこの変更を行うことは不可能であるように思われました。拡張可能な方法で変更できるので、Windsorのメインディストリビューションを引き続き使用できることを望んでいました。

とにかく、これを機能させるために、のGetScope関数を変更PerWebRequestLifestyleModuleして、HttpContextで実行されていない場合(または、Application_Startの場合のようにHttpContext.Requestが例外をスローした場合)、コンテナーから開始されたスコープを検索するようにしました。代わりは。これにより、次のコードを使用して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;
    }
}
于 2012-08-08T22:26:46.047 に答える
3

わかりました、これを行うための非常にクリーンな方法を見つけました!

まず、の実装が必要です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ハンドラーがちょうど2人いて、そのうちの1人がライフスタイルを持っている場合にのみ意見を述べます。もう1つは、おそらく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());
    }
于 2012-08-09T01:01:46.057 に答える
1

の舞台裏で何が起こっているのかわかりません.LifestylePerWebRequest()。しかし、これは「リクエストごとのコンテキスト」シナリオで私が行うことです。

セッションを確認HttpContextし、存在する場合はからコンテキストをプルします.Items。存在しない場合は、からコンテキストをプルしますSystem.Threading.Thread.CurrentContext

お役に立てれば。

于 2012-08-08T22:17:10.143 に答える