Parallel.ForEach中渲染MVC视图为字符串的AutoFac依赖注入问题
问题根源
这个错误是Autofac MVC集成的默认设计导致的:
- Autofac默认将请求生命周期Scope绑定到
HttpContext.Current,而Parallel.ForEach使用的是独立的线程池线程,这些线程默认没有关联当前请求的HttpContext - 当视图渲染触发
DefaultViewPageActivator创建视图实例时,会调用Autofac的依赖解析器获取服务,Autofac找不到绑定HttpContext的请求Scope就抛出异常 - 此前没有使用Autofac时,MVC默认的视图激活器直接用
Activator.CreateInstance创建视图,不会走依赖解析,所以没有这个问题
方案1:替换视图激活器(改动最小,无需修改Autofac配置)
仅需要修改RenderPartialViewToString方法,指定使用原生的视图激活器,跳过Autofac的依赖解析,适合视图里没有使用依赖注入的场景:
public static string RenderPartialViewToString(ControllerContext context, string viewPath) { var viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath); var view = viewEngineResult.View; using (var sw = new StringWriter()) { // 新增:自定义视图激活器,直接用Activator创建视图,不走Autofac解析 var activator = new DefaultViewPageActivator(new NonAutofacResolver()); var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw); // 替换当前视图上下文的视图激活器 ctx.ViewPageActivator = activator; view.Render(ctx, sw); return sw.ToString(); } } // 新增:空的依赖解析器,不会调用Autofac internal class NonAutofacResolver : IDependencyResolver { public object GetService(Type serviceType) => Activator.CreateInstance(serviceType); public IEnumerable<object> GetServices(Type serviceType) => Enumerable.Empty<object>(); }
这个方案不需要修改任何其他业务代码或者Autofac配置,完全兼容现有Parallel.ForEach逻辑。
方案2:传递HttpContext到并行线程(适合视图需要依赖注入的场景)
如果视图里确实用到了依赖注入,只需要在调用Parallel.ForEach之前捕获当前的HttpContext,在每个并行任务里赋值给当前线程的HttpContext.Current即可:
// 调用Parallel之前先捕获当前HttpContext var currentContext = HttpContext.Current; Parallel.ForEach(items, item => { // 给当前并行线程赋值HttpContext HttpContext.Current = currentContext; try { // 原有渲染逻辑 var html = RenderPartialViewToString(controllerContext, viewPath); // 其他业务逻辑 } finally { // 线程执行完清空,避免线程复用导致的上下文污染 HttpContext.Current = null; } });
这个方案不需要修改渲染方法和Autofac配置,仅需要调整Parallel.ForEach的调用代码。
方案3:修改Autofac配置(全局生效,适合所有场景)
如果不想改业务代码,可以替换Autofac默认的请求生命周期Scope提供器,当没有HttpContext时自动fallback到根Scope或者自定义Scope:
// 注册Autofac依赖解析器的时候指定自定义的Scope提供器 IContainer container = IoC.BuildContainer(); var lifetimeScopeProvider = new FallbackRequestLifetimeScopeProvider(container); DependencyResolver.SetResolver(new AutofacDependencyResolver(container, lifetimeScopeProvider)); // 自定义Scope提供器实现 internal class FallbackRequestLifetimeScopeProvider : ILifetimeScopeProvider { private readonly ILifetimeScope _rootScope; private readonly RequestLifetimeScopeProvider _innerProvider; public FallbackRequestLifetimeScopeProvider(ILifetimeScope rootScope) { _rootScope = rootScope; _innerProvider = new RequestLifetimeScopeProvider(rootScope); } public ILifetimeScope GetLifetimeScope(Action<ContainerBuilder> configurationAction) { if (HttpContext.Current == null) { // 没有HttpContext时直接返回根Scope(或者你可以自定义创建子Scope) return _rootScope; } return _innerProvider.GetLifetimeScope(configurationAction); } public void EndLifetimeScope() { if (HttpContext.Current != null) { _innerProvider.EndLifetimeScope(); } } public ILifetimeScope ApplicationContainer => _rootScope; }
注意:该方案如果用到InstancePerRequest的服务,需要将其注册改为InstancePerDependency或者InstancePerLifetimeScope,避免生命周期不匹配的问题。
这个方案完全不需要修改业务代码,仅调整Autofac初始化逻辑即可。
内容的提问来源于stack exchange,提问作者Colin
相关产品推荐
相关产品推荐

