Autofac - 无法创建请求生命周期范围,因为HttpContext不可用 - 由于异步代码?

时间:2014-02-15 23:00:13

标签: c# asp.net-mvc asp.net-web-api autofac dependency-resolver

简短问题:Same as this unanswered problem

长问题:

我刚从MVC 4 + Web Api解决方案中移植了一些代码,该解决方案使用Autofac进入我的新解决方案,该解决方案也使用Autofac但仅使用Web Api 2(没有MVC 5.1项目,只是一个web api)。

在我以前的解决方案中,我有MVC4和Web Api所以我有2个Bootstrapper.cs文件,每个文件一个。我复制了新项目的Web Api引导程序。

现在我在新解决方案中还有2个需要提取依赖项的项目。让我们假设我必须使用DependencyResolver.Current.GetService<T>(),尽管它是反模式。

首先,在我将MVC依赖关系解析器设置为同一个容器之前,这个工作无效:

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天内以赏金奖励现有答案

3 个答案:

答案 0 :(得分:24)

更新2014年11月20日:自此问题发布以来Autofac.Mvc5版本中,AutofacDependencyResolver.Current的实施已更新,无需HttpContext 1}}。如果您遇到此问题并找到了此答案,可以通过更新到更高版本的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应用程序或者总是存在Web上下文的东西。

以下是我推荐的内容:

  • 如果您需要使用网络环境之外的组件并且您使用的是WebApi,请使用Autofac.Integration.WebApi.AutofacWebApiDependencyResolver
  • 如果您在WCF中,请使用标准AutofacHostFactory.Container和主机工厂实现来解决依赖关系。 (WCF对其单身主机潜力等有点奇怪,所以“每个请求”并不那么简单。)
  • 如果您需要某些技术“不可知”,请考虑使用Autofac的CommonServiceLocator实现。它不会创建请求生命周期,但它可以解决一些问题。

如果你把这些事情做好,不要试图在原生境外使用各种解决方案,那么你就不应该遇到问题。

可以在服务注册中相当安全地使用InstancePerApiRequestInstancePerHttpRequest这两种扩展都使用相同的生命周期范围标记,因此即使一个案例中的基础生命周期范围基于HttpContext而另一个基于IDependencyScope,也可以类似地处理MVC Web请求和Web API请求的概念。因此,您可以假设在应用/应用类型之间共享注册模块,它应该做正确的事情。

如果您需要原始的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;

这将为您节省很多麻烦。

答案 1 :(得分:3)

我的假设:

  1. 您正在从MVC项目的单独Thread / AppDomain中运行工作流项目。
  2. IUserRepo依赖于HttpContext
  3. 如果我的假设正确,Workflow项目将不知道HttpContext.Current

    WindowsWorkflow项目一直在运行(如果我理解正确 - 实际上并没有使用这种技术)。 MVC基于HTTP请求的位置。只有在有请求进入时才会填充HttpContext.Current。如果没有请求 - 此变量为空。如果没有请求会发生什么,但Workflow实例正在尝试访问HttpContext?正确 - 空引用异常。或者在您的情况下依赖性解析例外。

    您需要做什么:

    1. 将容器注册分隔为模块 - 所有域类的域模块。然后是MVC模块:用于所有MVC细节,例如User.CurrentHttpContext.Current。和Workflow模块(如果需要)以及所有特定于Workflow的实现。
    2. 在工作流初始化时,使用域和工作流模块创建autofac容器,排除MVC依赖项。对于MVC容器 - 在没有工作流模块的情况下创建它。
    3. 对于IUserRepo创建不依赖于HttpContext的实现。这可能是最有问题的。
    4. 我在Azure中执行了类似的Quartz.Net执行。请参阅我的博客文章:http://tech.trailmax.info/2013/07/quartz-net-in-azure-with-autofac-smoothness/。这篇文章不会直接帮助你,但解释了我分割autofac模块的原因。

      根据评论进行更新:WebApi在此澄清了很多内容。 WebApi请求不会通过与MVC请求相同的管道。 WebApi控制器无权访问HttpContext。见this answer

      现在,根据您在wepApi控制器中的操作,您可能希望更改IUserRepo实现,以便能够同时使用MVC和WebApi。

答案 2 :(得分:0)

我们目前处于这样一种情况:我们的测试遭受了缺少的httpcontext&#39;因版本限制而无法使用上述优秀建议。

我们解决这个问题的方法是创建一个&#39; mock&#39;我们的测试设置中的http上下文: 见:Mock HttpContext.Current in Test Init Method