将 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
一起工作。
当您使用 InstancePerRequest
时 Autofac 会将依赖关系绑定到当前的 HttpContext
。如果你可以使用 InstancePerDependency
或 InstancePerLifetimeScope
你应该不会再有这个问题了。顺便说一句 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
});
同样,此解决方案不是最佳解决方案,但可以帮助您解决问题而无需重写所有内容。
我有一些代码可以将部分视图呈现为字符串:
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
一起工作。
当您使用 InstancePerRequest
时 Autofac 会将依赖关系绑定到当前的 HttpContext
。如果你可以使用 InstancePerDependency
或 InstancePerLifetimeScope
你应该不会再有这个问题了。顺便说一句 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
});
同样,此解决方案不是最佳解决方案,但可以帮助您解决问题而无需重写所有内容。