2009-07-19 4 views
44

Несколько дней назад у меня была эта проблема с потоками ASP.Net. Я хотел иметь одноэлементный объект для каждого веб-запроса. Мне действительно нужно это для моей единицы работы. Я хотел бы создать экземпляр единицы работы для каждого веб-запроса, чтобы карта идентификации была действительной в рамках запроса. Таким образом, я мог бы использовать IoC, чтобы прозрачно вводить свои собственные классы IUnitOfWork в свои классы репозитория, и я мог бы использовать один и тот же экземпляр для запроса, а затем для обновления моих объектов.Singleton Per Call Context (Web Request) в Unity

Поскольку я использую Unity, я ошибочно использовал PerThreadLifeTimeManager. Вскоре я понял, что модель потоковой передачи ASP.Net не поддерживает то, что я хочу добиться. В основном это использует theadpool и перерабатывает потоки, и это означает, что я получаю один UnitOfWork в поток! Однако то, что я хотел, было одной единицей работы для каждого веб-запроса.

Немного поискового запроса дал мне this great post. Это было именно то, что я хотел; за исключением части единства, которая была довольно легко достижимой.

Это моя реализация для 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()); 

надеюсь, что это спасает кого-то немного времени.

+0

Хорошее решение. Если возможно, я рекомендую переименовать это в «CallContextLifetimeManager», поскольку веб-запросы, вероятно, являются лишь одним из потенциальных приложений. –

+0

Правда, я обновил текст и код, чтобы отразить это. Благодарю. –

+0

+1 Очень полезно. – MrDustpan

ответ

25

изящное решение, но каждый экземпляр LifetimeManager должен использовать уникальный ключ, а не константы:

private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid()); 

В противном случае, если у вас есть более чем один объект зарегистрирован PerCallContextLifeTimeManager, они делятся и тот же ключ для доступа CallContext, и вы не получите ожидаемого объекта обратно.

Также стоит реализации RemoveValue для обеспечения объектов очищаются:

public override void RemoveValue() 
{ 
    CallContext.FreeNamedDataSlot(_key); 
} 
+0

-1 CallContext воссоздается по каждому запросу. Ключ не должен быть уникальным между разными экземплярами вашего запроса singleton. –

+8

+1 На самом деле, это должно быть точно так же, как сказал шестидесятый. Если вы не назначаете уникальные ключи, все объекты регистрируются под одним ключом, и все становится беспорядочным. –

+0

-1 См. Ответ Стивена Роббинса и комментарий Мика Золту по вопросу: под загрузкой 'CallContext' не сохраняется для одного запроса. –

20

В то время как это прекрасно, вызывающее эта PerCallContextLifeTimeManager, я уверен, что это не «безопасные», чтобы считать ASP.Net По запросу 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() 
    { 
    } 
} 

Это, очевидно, означает, принимая зависимости от системы.Веб :-(

Гораздо больше информации об этом по адресу:

http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html

3

Спасибо за ваш вклад,

Но вопрос предлагаемой реализации имеет два недостатка, один из которых является серьезной ошибкой не как уже было сказано Стивен Роббинс в his answer и Михея Zoltu in a comment.

  1. контекст вызова нет t гарантируется сохранение asp.net для одного запроса. Под нагрузкой он может переключиться на другой, что приведет к разрыву предлагаемой реализации.
  2. Он не обрабатывает освобождение зависимостей в конце запроса.

В настоящее время пакет 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. Это зависит от порядка выполнения модуля.