I have some code that renders a partial view to a string:
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();
}
}
For perfomance reasons this code is called multiple times inside a Parallel.ForEach
loop. And it works until I try to introduce dependency injection for our controllers.
When I set the resolver to AutoFac's dependency resolver....
IContainer container = IoC.BuildContainer();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
...I get an exception...
The request lifetime scope cannot be created because the HttpContext is not available.
This exception doesn't occur for every view, only when there are two calls to get the same view. And the problem goes away if I change the Parallel.ForEach
to a standard ForEach
.
I have read that the controller context is not valid in a new thread but it didn't cause an issue until I introduced AutoFac.
Is there a solution to this that lets me keep the Parallel.ForEach
? Ideally the solution will avoid making wholesale changes to the legacy rendering code - perhaps some AutoFac configuration?
Stack trace:
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
Your issue is not directly related to Autofac but how HttpContext
works with Parallel.ForEach
.
When you use InstancePerRequest
Autofac will tie the dependency to the current HttpContext
. If you can use InstancePerDependency
or InstancePerLifetimeScope
you should not have this issue anymore. By the way InstancePerRequest
is deprecated on the latest version of Autofac : https://autofac.readthedocs.io/en/latest/integration/aspnetcore.html#differences-from-asp-net-classic
By using Parallel.ForEach
you are creating many threads.HttpContext.Current
is related to the current thread, the new thread doesn't have any httpcontext that's why autofac throws exception.
HttpContext.Current
is writable so you can do something like this
// /!\ AVOID THIS CODE
var currentContext = HttpContext.Current;
Parallel.ForEach(xxx, o =>
{
HttpContext.Current = currentContext;
// do whatever you want
});
Settings HttpContext.current
sound like a dirty hack to me and I would avoid doing this but it can help fix your issue without rewriting everything.
Another solution would be setting the task scheduler used to create the parallel tasks. By using TaskScheduler.FromCurrentSynchronizationContext()
.net will create a thread with a copy of the current synchronization context.
Parallel.ForEach(xxx, new ParallelOptions() {
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
}, o =>
{
// do whatever you want
});
Again, this solution is not the best solution but could help you fix your issue without rewriting everything.