Castle.Windsor образ жизни в зависимости от контекста?
У меня есть веб-приложение, где многие компоненты зарегистрированы с использованием .LifestylePerWebRequest()
Теперь я решил реализовать Quartz.NET, библиотеку планирования заданий.NET, которая выполняется в отдельных потоках, а не в потоке Request.
В качестве таких, HttpContext.Current
доходность null
, Мои сервисы, репозитории и IDbConnection
были созданы до сих пор с использованием .LifestylePerWebRequest()
потому что это облегчало их удаление, когда запросы заканчивались.
Теперь я хочу использовать эти компоненты в обоих сценариях, во время веб-запросов я хочу, чтобы они не затрагивались, а в контексте без запросов я хочу, чтобы они использовали другой образ жизни, я полагаю, что могу справиться с удалением самостоятельно, но как мне поступить об этом для выбора образа жизни для компонентов, основанных на текущем контексте?
В настоящее время я регистрирую услуги (например), например:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
Я полагаю, что я должен использовать какой-то метод расширения, но я просто не вижу его..
5 ответов
Вы должны использовать Hybrid Lifestyle от CastleProjectContrib.
Гибридный образ жизни - это тот, который фактически сочетает два основных образа жизни: основной образ жизни и вторичный образ жизни. Гибридный образ жизни сначала пытается использовать основной образ жизни; если он недоступен по какой-либо причине, он использует вторичный образ жизни. Это обычно используется с PerWebRequest в качестве основного образа жизни: если доступен HTTP-контекст, он используется как область действия для экземпляра компонента; в противном случае используется вторичный образ жизни.
Не используйте одни и те же компоненты. Фактически, в большинстве сценариев, которые я видел, "фоновая обработка" даже не имеет смысла участвовать в веб-процессе с самого начала.
Разработка на основе комментариев.
Обработка фоновой обработки в веб-конвейере ставит под угрозу вашу архитектуру, чтобы сэкономить несколько долларов на экземпляре EC2. Я настоятельно рекомендую подумать об этом еще раз, но я отступаю.
Мои утверждения остаются в силе, даже если вы помещаете оба компонента в веб-процесс, они представляют собой два разных компонента, используемых в двух разных контекстах, и должны рассматриваться как таковые.
Недавно у меня была очень похожая проблема - я хотел иметь возможность запускать код инициализации на основе моего контейнера при запуске приложения, когда HttpContext.Request еще не существует. Я не нашел никакого способа сделать это, поэтому я изменил источник PerWebRequestLifestyleModule, чтобы позволить мне делать то, что я хотел. К сожалению, казалось невозможным сделать это изменение без перекомпиляции Виндзора - я надеялся, что смогу сделать это расширяемым образом, чтобы я мог продолжать использовать основной дистрибутив Виндзора.
Во всяком случае, чтобы сделать эту работу, я изменил GetScope
функция PerWebRequestLifestyleModule
так что если он НЕ работает в HttpContext (или если HttpContext.Request выдает исключение, как это происходит в Application_Start), он будет искать Scope, запущенный из контейнера. Это позволяет мне использовать мой контейнер в 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
,
Надеюсь это поможет.