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