Контекст Singleton Per Call (веб-запрос) в Unity
Несколько дней назад у меня возникла проблема с многопоточностью ASP.Net. Я хотел иметь одноэлементный объект для каждого веб-запроса. Мне действительно нужно это для моей единицы работы. Я хотел создать единицу работы для каждого веб-запроса, чтобы идентификационная карта действовала на протяжении всего запроса. Таким образом, я мог бы использовать IoC, чтобы прозрачно внедрить свой собственный IUnitOfWork в мои классы репозитория, и я мог бы использовать тот же экземпляр для запроса, а затем обновления своих сущностей.
Так как я использую Unity, я по ошибке использовал PerThreadLifeTimeManager. Вскоре я понял, что модель потоков ASP.Net не поддерживает то, чего я хочу достичь. В основном он использует пул потоков и перерабатывает потоки, а это значит, что я получаю один UnitOfWork на поток !! Однако мне нужно было одну единицу работы на веб-запрос.
Немного погугливости дал мне этот замечательный пост. Это было именно то, что я хотел; за исключением части единства, которой было довольно легко достичь.
Это моя реализация PerCallContextLifeTimeManager для единства:
public class PerCallContextLifeTimeManager : LifetimeManager
{
private const string Key = "SingletonPerCallContext";
public override object GetValue()
{
return CallContext.GetData(Key);
}
public override void SetValue(object newValue)
{
CallContext.SetData(Key, newValue);
}
public override void RemoveValue()
{
}
}
И, конечно, я использую это, чтобы зарегистрировать свою единицу работы с кодом, подобным следующему:
unityContainer
.RegisterType<IUnitOfWork, MyDataContext>(
new PerCallContextLifeTimeManager(),
new InjectionConstructor());
Надеюсь, это сэкономит кому-то немного времени.
Ответы
Ответ 1
Аккуратное решение, но каждый экземпляр LifetimeManager должен использовать уникальный ключ, а не константу:
private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid());
В противном случае, если у вас есть несколько объектов, зарегистрированных в PerCallContextLifeTimeManager, они используют один и тот же ключ для доступа к CallContext, и вы не вернете ожидаемый объект.
Также стоит реализовать RemoveValue для обеспечения очистки объектов:
public override void RemoveValue()
{
CallContext.FreeNamedDataSlot(_key);
}
Ответ 2
В то время как это нормально вызывает этот PerCallContextLifeTimeManager, я уверен, что это не "безопасно", чтобы считаться ASP.Net Per-request LifeTimeManager.
Если ASP.Net выполняет сворачивание потоков, единственное, что передается через новый поток через CallContext, это текущий HttpContext - все, что вы храните в CallContext, исчезнет. Это означает, что при большой нагрузке вышеприведенный код может иметь непреднамеренные результаты - и я полагаю, что было бы настоящей болью отслеживать, почему!
Единственный "безопасный" способ сделать это с помощью HttpContext.Current.Items или сделать что-то вроде:
public class PerCallContextOrRequestLifeTimeManager : LifetimeManager
{
private string _key = string.Format("PerCallContextOrRequestLifeTimeManager_{0}", Guid.NewGuid());
public override object GetValue()
{
if(HttpContext.Current != null)
return GetFromHttpContext();
else
return GetFromCallContext();
}
public override void SetValue(object newValue)
{
if(HttpContext.Current != null)
return SetInHttpContext();
else
return SetInCallContext();
}
public override void RemoveValue()
{
}
}
Это, очевидно, означает получение зависимостей от System.Web: - (
Гораздо больше информации об этом можно найти по адресу:
http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html
Ответ 3
Спасибо за ваш вклад,
Но у предложенного вопроса реализации есть два недостатка, один из которых является серьезной ошибкой, о которой уже говорил Стивен Роббинс в своем ответе и Мика Золту в комментарии.
- Не гарантируется, что контекст вызова будет сохранен Asp.Net для одного запроса. Под нагрузкой он может переключиться на другой, что приведет к сбою предлагаемой реализации.
- Он не обрабатывает освобождение зависимостей в конце запроса.
В настоящее время пакет Unity.Mvc Nuget предоставляет PerRequestLifetimeManager
для выполнения работы. Не забудьте зарегистрировать связанный с ним UnityPerRequestHttpModule
в коде начальной загрузки, иначе освобождение зависимостей также не будет обрабатываться.
Использование начальной загрузки
DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
или в web.config system.webServer/modules
<add name="UnityPerRequestHttpModule" type="Microsoft.Practices.Unity.Mvc.UnityPerRequestHttpModule, Microsoft.Practices.Unity.Mvc" preCondition="managedHandler" />
Похоже, его текущая реализация также подходит для веб-форм. И это даже не зависит от MVC. К сожалению, его сборка происходит из-за некоторых других классов, которые он содержит.
Помните, что если вы используете какой-либо пользовательский модуль http, используя ваши разрешенные зависимости, они могут быть уже размещены в модуле EndRequest
. Это зависит от порядка выполнения модуля.