Синглтон на контекст вызова (веб-запрос) в Unity - PullRequest
45 голосов
/ 20 июля 2009

Несколько дней назад у меня была эта проблема с многопоточностью ASP.Net. Я хотел иметь одноэлементный объект для каждого веб-запроса. Мне действительно нужно это для моей единицы работы. Я хотел создать единицу работы для каждого веб-запроса, чтобы идентификационная карта действовала на протяжении всего запроса. Таким образом, я мог бы использовать IoC, чтобы прозрачно внедрить свой собственный IUnitOfWork в мои классы репозитория, и я мог бы использовать тот же экземпляр для запроса, а затем обновления своих сущностей.

Поскольку я использую Unity, я по ошибке использовал PerThreadLifeTimeManager. Вскоре я понял, что модель потоков ASP.Net не поддерживает то, что я хочу достичь. В основном он использует theadpool и перерабатывает потоки, а это значит, что я получаю один 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());

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

Ответы [ 3 ]

25 голосов
/ 10 июня 2010

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

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

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

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

public override void RemoveValue()
{
     CallContext.FreeNamedDataSlot(_key);
}
21 голосов
/ 15 января 2011

Хотя это нормально, вызывая этот 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

4 голосов
/ 03 апреля 2015

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...