将 MVC 视图渲染到 Parallel.ForEach 中的字符串时出现依赖注入问题

Dependency injection issue when rendering an MVC view to a string in Parallel.ForEach

我有一些代码可以将部分视图呈现为字符串:

public static string RenderPartialViewToString(ControllerContext context, string viewPath)
{
    var viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
    var view = viewEngineResult.View;
    using (var sw = new StringWriter())
    {
        var ctx = new ViewContext(context, 
                                  view, 
                                  context.Controller.ViewData,
                                  context.Controller.TempData, 
                                  sw);
        view.Render(ctx, sw);
        return sw.ToString();
    }
}

出于性能原因,此代码在 Parallel.ForEach 循环中被多次调用。它一直有效,直到我尝试为我们的控制器引入依赖注入。

当我将解析器设置为 AutoFac 的依赖解析器时....

IContainer container = IoC.BuildContainer();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

...我遇到异常...

The request lifetime scope cannot be created because the HttpContext is not available.

这个异常不会发生在每个视图上,只有当有两次调用获取同一个视图时才会发生。如果我将 Parallel.ForEach 更改为标准 ForEach.

,问题就会消失

我读过 controller context is not valid in a new thread 但在我引入 AutoFac 之前它并没有引起问题。

是否有解决方案可以让我保留 Parallel.ForEach?理想情况下,该解决方案将避免对遗留渲染代码进行大规模更改——也许是一些 AutoFac 配置?

堆栈跟踪:

at Autofac.Integration.Mvc.RequestLifetimeScopeProvider.GetLifetimeScope(Action`1 configurationAction) at Autofac.Integration.Mvc.AutofacDependencyResolver.get_RequestLifetimeScope() at Autofac.Integration.Mvc.AutofacDependencyResolver.GetService(Type serviceType) at System.Web.Mvc.BuildManagerViewEngine.DefaultViewPageActivator.Create(ControllerContext controllerContext, Type type) at System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) at AgentDesktop.HelperClasses.ViewHelper.RenderPartialViewToString(ControllerContext context, String viewPath) in C:\Users\colinm\source\repos\Git-SyntelateXA\AgentDesktop\HelperClasses\ViewHelper.cs:line 24

您的问题与 Autofac 没有直接关系,但 HttpContext 如何与 Parallel.ForEach 一起工作。

当您使用 InstancePerRequestAutofac 会将依赖关系绑定到当前的 HttpContext。如果你可以使用 InstancePerDependencyInstancePerLifetimeScope 你应该不会再有这个问题了。顺便说一句 InstancePerRequest 在最新版本的 Autofac 上被弃用了:https://autofac.readthedocs.io/en/latest/integration/aspnetcore.html#differences-from-asp-net-classic

通过使用 Parallel.ForEach,您正在创建许多线程。HttpContext.Current 与当前线程相关,新线程没有任何 httpcontext,这就是 autofac 抛出异常的原因。

HttpContext.Current 是可写的,所以你可以这样做

// /!\ AVOID THIS CODE 
var currentContext = HttpContext.Current;
Parallel.ForEach(xxx, o =>
{
    HttpContext.Current = currentContext;
    // do whatever you want
});

设置 HttpContext.current 对我来说听起来像是一个肮脏的黑客,我会避免这样做,但它可以帮助解决您的问题而无需重写所有内容。

另一种解决方案是设置用于创建并行任务的任务计划程序。通过使用 TaskScheduler.FromCurrentSynchronizationContext() .net 将创建一个线程,其中包含当前同步​​上下文的副本。

Parallel.ForEach(xxx, new ParallelOptions() { 
    TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() 
},  o =>
{
    // do whatever you want 
});

同样,此解决方案不是最佳解决方案,但可以帮助您解决问题而无需重写所有内容。