Autofac - область ожидания запроса не может быть создана, потому что HttpContext недоступен - из-за асинхронного кода?

Короткий вопрос: То же, что и эта неотвеченная проблема

Долгосрочный вопрос:

Я просто портировал некоторый код из решения MVC 4 + Web Api, которое использовало Autofac в моем новом решении, которое также использует Autofac, но только с Web Api 2 (без проекта MVC 5.1, просто веб-api).

В моем предыдущем решении у меня были MVC4 и Web Api, поэтому у меня было 2 файла Bootstrapper.cs, по одному для каждого. Я скопировал только загрузочный скрипт Web Api для нового проекта.

Теперь у меня есть еще 2 проекта в новом решении, которое нужно вытащить зависимость. Давайте просто предположим, что я должен использовать DependencyResolver.Current.GetService<T>(), несмотря на то, что это анти-шаблон.

Сначала это не работало, пока я не установил MVC Dependency Resolver в тот же контейнер:

GlobalConfiguration.Configuration.DependencyResolver = 
     new AutofacWebApiDependencyResolver(container);

//I had to pull in Autofac.Mvc and Mvc 5.1 integration but this line fixed it
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

Странная часть заключается в том, что она только фиксирует ее в ОДНОМ из этих проектов! Здесь ситуация:

 Solution.Web project
      Bootstrapper.cs that registers both dependency resolvers for web api and mvc.

 Solution.ClassLib project
      var userRepo = DependencyResolver.Current.GetService<IUserRepo>(); //Good! :)

 Solution.WindowsWorkflow project
      var userRepo = DependencyResolver.Current.GetService<IUserRepo>(); //Throws exception :(

Исключение составляет:   Область действия запроса не может быть создана, потому что HttpContext недоступен.

Теперь, прежде чем мы начнем обвинять рабочий процесс, просто знаю, что у меня была эта точная настройка, работающая отлично в другом решении, рабочий процесс смог использовать DependencyResolver просто отлично. Поэтому я подозреваю, что это связано с использованием более новой версии Autofac и тем фактом, что рабочий процесс выполняется асинхронно (как и вопрос, связанный с асинхронным кодом)

Я попытался переключить весь регистрационный код на использование InstancePerLifetimeScope() вместо InstancePerHttpRequest() и попытаться создать область действия:

using (var c= AutofacDependencyResolver.Current
                     .ApplicationContainer.BeginLifetimeScope("AutofacWebRequest"))
{
   var userRepo = DependencyResolver.Current.GetServices<IUserRepo>();
}

Но это не изменило исключение. Прерывая код еще ниже, точным виновником:

var adr = AutofacDependencyResolver.Current; //Throws that exception 

На самом деле нужно пройти мимо этого, потратив слишком много времени. Наградит существующий ответ щедростью за 2 дня

Ответ 1

ОБНОВЛЕНИЕ 20 ноября 2014 года: В версиях Autofac.Mvc5, поскольку этот вопрос был выпущен, реализация AutofacDependencyResolver.Current была обновлена, чтобы удалить необходимость HttpContext. Если вы столкнулись с этой проблемой и нашли этот ответ, , вы можете легко решить проблему, обновив ее до более поздней версии Autofac.Mvc5. Тем не менее, я оставлю исходный ответ неповрежденным для людей, чтобы понять, почему у исходного вопросника возникли проблемы.

Оригинальный ответ следует:


AutofacDependencyResolver.Current требуется HttpContext.

Просматривая код, AutofacDependencyResolver.Current выглядит следующим образом:

public static AutofacDependencyResolver Current
{
  get
  {
    return DependencyResolver.Current.GetService<AutofacDependencyResolver>();
  }
}

И, конечно, если текущий преобразователь зависимостей - это AutofacDependencyResolver, то он попытается сделать разрешение...

public object GetService(Type serviceType)
{
  return RequestLifetimeScope.ResolveOptional(serviceType);
}

Что получает область жизни от RequestLifetimeScopeProvider...

public ILifetimeScope GetLifetimeScope(Action<ContainerBuilder> configurationAction)
{
  if (HttpContext.Current == null)
  {
    throw new InvalidOperationException("...");
  }

  // ...and your code is probably dying right there so I won't
  // include the rest of the source.
}

Он должен работать так, чтобы поддерживать такие инструменты, как Glimpse, которые динамически обертывают/проксируют зависимый преобразователь, чтобы измерить его. Вот почему вы не можете просто бросить DependencyResolver.Current as AutofacDependencyResolver.

Довольно многое, что использует Autofac.Integration.Mvc.AutofacDependencyResolver, требует HttpContext.

Вот почему вы продолжаете получать эту ошибку. Неважно, если у вас нет зависимостей, зарегистрированных InstancePerHttpRequest - AutofacDependencyResolver, все равно потребуется веб-контекст.

Я предполагаю, что другое приложение рабочего процесса, которое у вас было, где это не было проблемой, - это приложение MVC или что-то еще, где всегда был веб-контекст.

Здесь я бы рекомендовал:

  • Если вам нужно использовать компоненты вне веб-контекста, и вы находитесь в WebApi, используйте Autofac.Integration.WebApi.AutofacWebApiDependencyResolver.
  • Если вы используете WCF, используйте стандартную реализацию AutofacHostFactory.Container и этот хост factory для разрешения зависимостей. (WCF немного странно с его потенциалом хоста singleton и т.д., Поэтому "на запрос" не так просто.)
  • Если вам нужно что-то "агностическое" для технологии, рассмотрите реализацию CommonServiceLocator для Autofac. Он не создает время жизни запроса, но может решить некоторые проблемы.

Если вы держите эти вещи прямо и не пытаетесь использовать различные резольверы вне их родных сред обитания, то вы не должны сталкиваться с проблемами.

Вы можете довольно безопасно использовать InstancePerApiRequest и InstancePerHttpRequest взаимозаменяемые в регистрации услуг. Оба этих расширения используют один и тот же тег области видимости в течение времени, поэтому понятие веб-запроса MVC и запрос веб-API могут быть рассмотрены аналогично, даже если базовая шкала времени жизни в одном случае основана на HttpContext, а другая основана на IDependencyScope. Таким образом, вы можете гипотетически делиться регистрационным модулем между типами приложений и приложений, и он должен поступать правильно.

Если вам нужен оригинальный контейнер Autofac, сохраните свою собственную ссылку на него. Вместо того, чтобы предположить, что Autofac каким-то образом вернет этот контейнер, вам может потребоваться сохранить ссылку на ваш контейнер приложения, если вам нужно Получите это позже по любой причине.

public static class ApplicationContainer
{
  public static IContainer Container { get; set; }
}

// And then when you build your resolvers...
var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver =
  new AutofacWebApiDependencyResolver(container);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
ApplicationContainer.Container = container;

Это спасет вас от неприятностей по дороге.

Ответ 2

Мои предположения:

  • Вы запускаете проект рабочего процесса в отдельном проекте Thread/AppDomain из проекта MVC.
  • IUserRepo зависит от HttpContext

Если мое предположение правильное, проект Workflow не имеет понятия о HttpContext.Current.

Проект WindowsWorkflow работает все время (если я его правильно понимаю - на самом деле не работал с этой технологией). Где MVC основан на HTTP-запросах. HttpContext.Current заполняется только при поступлении запроса. Если запрос отсутствует - эта переменная имеет значение null. Что произойдет, если запрос отсутствует, но экземпляр Workflow пытается получить доступ к HttpContext? Правильно - исключение для ссылки. Или в вашем случае исключение разрешения зависимостей.

Что вам нужно сделать:

  • Раздельная регистрация контейнеров в модули - модуль домена для всех ваших классов домена. Затем модуль MVC: для всех ваших спецификаций MVC, например User.Current или HttpContext.Current. И модуль Workflow (если требуется) со всеми конкретными реализациями Workflow.
  • При инициализации Workflow создайте контейнер autofac с модулями домена и рабочего процесса, исключите зависимости MVC. Для контейнера MVC - создайте его без модуля рабочего процесса.
  • Для IUserRepo создать реализацию, не зависящую от HttpContext. Вероятно, это будет наиболее проблематично.

Я сделал что-то подобное для выполнения Quartz.Net в Azure. См. Мою статью в блоге об этом: http://tech.trailmax.info/2013/07/quartz-net-in-azure-with-autofac-smoothness/. Этот пост не поможет вам напрямую, но объясняет мои рассуждения о разделении модулей autofac.

Обновление в соответствии с комментарием: WebApi многое объясняет здесь. Запрос WebApi не проходит через тот же конвейер, что и ваши запросы MVC. А контроллеры WebApi не имеют доступа к HttpContext. См. этот ответ.

Теперь, в зависимости от того, что вы делаете в своем контроллере wepApi, вы можете изменить реализацию IUserRepo, чтобы иметь возможность работать как с MVC, так и с WebApi.

Ответ 3

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

То, как мы решили это, заключалось в создании "mock" http-контекста в нашей тестовой установке: см. Mock HttpContext.Current в методе тестирования Init