17

简短的问题:与这个未回答的问题相同

长问题:

我刚刚将一些代码从使用 Autofac 的 MVC 4 + Web Api 解决方案移植到我的新解决方案中,该解决方案也使用 Autofac 但仅使用 Web Api 2(没有 MVC 5.1 项目,只是一个 Web 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 天内以赏金奖励现有答案

4

4 回答 4

28

更新 2014 年 11 月 20 日:在发布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这样动态包装/代理依赖解析器以便对其进行检测的工具。这就是为什么你不能只是 cast DependencyResolver.Current as AutofacDependencyResolver

几乎所有使用Autofac.Integration.Mvc.AutofacDependencyResolverrequires的东西HttpContext

这就是为什么您不断收到此错误的原因。如果您没有注册的依赖项并不重要InstancePerHttpRequest-AutofacDependencyResolver仍然需要 Web 上下文。

我猜您拥有的另一个不存在问题的工作流应用程序是 MVC 应用程序或始终存在 Web 上下文的东西。

这是我的建议:

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

如果您保持这些事情正确并且不尝试在其原生栖息地之外使用各种解析器,那么您就不应该遇到问题。

可以在服务注册中相当安全地使用InstancePerApiRequestInstancePerHttpRequest互换。这两个扩展都使用相同的生命周期范围标记,因此即使底层生命周期范围在一种情况下是基于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;

这将为您节省很多麻烦。

于 2014-02-17T16:31:48.343 回答
3

我的假设:

  1. 您正在与 MVC 项目分开的 Thread/AppDomain 中运行工作流项目。
  2. IUserRepo依赖于 HttpContext

如果我的假设正确,Workflow 项目将不知道HttpContext.Current.

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

你需要做什么:

  1. 将容器注册分离到模块中 - 所有域类的域模块。然后是 MVC 模块:适用于所有 MVC 细节,例如User.CurrentHttpContext.Current. 以及带有所有工作流特定实现的工作流模块(如果需要)。
  2. 在工作流初始化时,创建带有域和工作流模块的 autofac 容器,排除 MVC 依赖项。对于 MVC 容器 - 在没有工作流模块的情况下创建它。
  3. 用于IUserRepo创建不依赖于 HttpContext 的实现。这可能是最有问题的做法。

我在 Azure 中为 Quartz.Net 执行做了类似的事情。请参阅我的博客文章:http ://tech.trailmax.info/2013/07/quartz-net-in-azure-with-autofac-smoothness/ 。这篇文章不会直接帮助你,但会解释我拆分 autofac 模块的原因。

根据评论更新: WebApi 在这里澄清了很多事情。WebApi 请求不会通过与 MVC 请求相同的管道。并且 WebApi 控制器无权访问 HttpContext。看到这个答案

现在,根据您在 wepApi 控制器中所做的事情,您可能希望更改IUserRepo实现以同时使用 MVC 和 WebApi。

于 2014-02-16T00:02:23.270 回答
0

We're currently in a situation where we have tests that suffer from the 'missing httpcontext' issue but cannot yet use the excellent recommendations above due to version constraints.

The way we solved it was to create a 'mock' http context in our test setup: see: Mock HttpContext.Current in Test Init Method

于 2015-09-04T16:21:26.733 回答
0

For me to resolve this issue I click on the same button another few tries, which is usually no more than 4. If this is you you could put a message for the user to do so.

于 2021-10-20T18:28:05.100 回答