Подтвердить что ты не робот

Стиль Castle.Windsor в зависимости от контекста?

У меня есть веб-приложение, где многие компоненты зарегистрированы с помощью .LifestylePerWebRequest(), теперь я решил реализовать Quartz.NET, библиотеку планирования заданий .NET, которая выполняется в отдельных потоках и а не поток запроса.

Таким образом, HttpContext.Current дает null. Мои сервисы, репозитории и IDbConnection были установлены до сих пор с помощью .LifestylePerWebRequest(), поскольку он упростил их удаление, когда закончились запросы.

Теперь я хочу использовать эти компоненты в обоих сценариях, во время веб-запросов я хочу, чтобы они остались незатронутыми, а в контекстах без запроса я хочу, чтобы они использовали другой образ жизни, я полагаю, что я могу справиться с самим распоряжением, но как должен ли я использовать его для выбора стиля жизни для компонентов на основе текущего контекста?

В настоящее время я регистрирую службы (например), например:

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

Я полагаю, что должен использовать какой-то метод расширения, но я просто не вижу его.

4b9b3361

Ответ 1

Вам следует использовать Hybrid Lifestyle от castleprojectcontrib.

Гибкий образ жизни - это тот, который фактически смешивает два основных образа жизни: основной образ жизни и вторичный образ жизни. Гибридный образ жизни сначала пытается использовать основной образ жизни; если он по какой-то причине недоступен, он использует вторичный образ жизни. Это обычно используется с PerWebRequest в качестве основного образа жизни: если контекст HTTP доступен, он используется как область экземпляра компонента; в противном случае используется вторичный образ жизни.

Ответ 2

Не используйте те же компоненты. Фактически, в большинстве сценариев я видел, что "фоновая обработка" даже не имеет смысла быть в веб-процессе для начала.

Разработка на основе комментариев.

Фоновая обработка изображений в веб-конвейере ставит под угрозу вашу архитектуру, чтобы сэкономить несколько $на экземпляре EC2. Я бы настоятельно рекомендовал подумать об этом снова, но я отвлекся.

Мои утверждения все еще стоят, даже если вы добавляете оба компонента в веб-процесс, они представляют собой два разных компонента, используемых в двух разных контекстах, и их следует рассматривать как таковые.

Ответ 3

Недавно у меня была очень похожая проблема - я хотел иметь возможность запускать код инициализации, основанный на моем контейнере, в запуске приложения, когда HttpContext.Request еще не существует. Я не нашел никакого способа сделать это, поэтому я изменил источник PerWebRequestLifestyleModule, чтобы позволить мне делать то, что я хотел. К сожалению, не удалось сделать это изменение без перекомпиляции Windsor - я надеялся, что смогу сделать это растяжимым способом, чтобы я мог продолжать использовать основное распределение Windsor.

В любом случае, чтобы выполнить эту работу, я изменил функцию 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 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;
    }
}

Ответ 4

Хорошо, я придумал очень чистый способ сделать это!

Прежде всего нам понадобится реализация IHandlerSelector, это может выбрать обработчик, основанный на нашем мнении по этому вопросу, или оставаться нейтральным (возвращая null, что означает "нет мнения" ).

/// <summary>
/// Emits an opinion about a component 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());
    }

Ответ 5

Я не знаю, что происходит за кулисами в .LifestylePerWebRequest(); но это то, что я делаю для сценариев "Контекст на запрос":

Отметьте HttpContext для сеанса и, если существует, вытащите контекст из .Items. Если он не существует, вытащите свой контекст из System.Threading.Thread.CurrentContext.

Надеюсь, что это поможет.