RazorLight+Mjml.Net环境下Component.InvokeAsync加载分部视图空引用报错
问题分析
空引用异常的核心原因是RazorLight作为轻量级模板引擎,默认没有集成ASP.NET Core ViewComponent的完整服务栈,你注入的IViewComponentHelper实例并未被正确初始化(内部依赖如ViewComponentDescriptorProvider、IViewComponentInvokerFactory等缺失),导致调用Component.InvokeAsync时触发空引用。
解决步骤
1. 注册ViewComponent必要服务到HostApplicationBuilder
在构建Host时,需添加MVC核心服务并显式注册ViewComponent相关依赖,无需引入完整MVC中间件:
var builder = Host.CreateApplicationBuilder(args); // 添加MVC核心服务及ViewComponent支持 builder.Services.AddMvcCore() .AddViewComponents(); // 注册RazorLight服务(根据现有配置调整模板路径) builder.Services.AddRazorLight(() => new RazorLightEngineBuilder() .UseFileSystemProject(Path.Combine(Directory.GetCurrentDirectory(), "Templates")) .UseMemoryCachingProvider() .Build()); // 注册HttpContextAccessor(后续初始化ViewComponent上下文需要) builder.Services.AddHttpContextAccessor(); // 注册Mjml渲染服务 builder.Services.AddSingleton<IMjmlRenderer, MjmlRenderer>();
2. 手动初始化IViewComponentHelper上下文
RazorLight不会自动初始化ViewComponentHelper的上下文,需在模板渲染前手动设置:
public class EmailTemplateService { private readonly IRazorLightEngine _razorEngine; private readonly IViewComponentHelper _viewComponentHelper; private readonly IHttpContextAccessor _httpContextAccessor; public EmailTemplateService(IRazorLightEngine razorEngine, IViewComponentHelper viewComponentHelper, IHttpContextAccessor httpContextAccessor) { _razorEngine = razorEngine; _viewComponentHelper = viewComponentHelper; _httpContextAccessor = httpContextAccessor; } public async Task<string> RenderAndConvertEmailAsync<T>(string templateName, T model) { // 初始化ViewComponentHelper的上下文环境 var httpContext = _httpContextAccessor.HttpContext ?? new DefaultHttpContext(); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); ((IViewContextAware)_viewComponentHelper).Contextualize(new ViewContext( actionContext, new FakePlaceholderView(), new ViewDataDictionary<T>(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model }, new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>()), TextWriter.Null, new HtmlHelperOptions())); // 渲染Razor模板 var razorOutput = await _razorEngine.CompileRenderAsync(templateName, model); // 转换为MJML最终HTML var mjmlResult = await new MjmlRenderer().RenderAsync(razorOutput); return mjmlResult.Html; } // 空视图类,用于ViewContext占位 private class FakePlaceholderView : IView { public string Path => string.Empty; public Task RenderAsync(ViewContext context) => Task.CompletedTask; } }
3. 确保ViewComponent视图文件路径正确
- 你的
DayPillsViewComponent对应的视图文件需放在Templates/Components/DayPills/Default.cshtml(RazorLight默认的ViewComponent视图查找路径) - 确认.csproj中已配置复制视图文件到输出目录:
<ItemGroup> <Content Update="Templates\**\*.cshtml"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup>
4. 主模板中调用ViewComponent的正确写法
主模板中直接使用Component.InvokeAsync即可,无需额外注入IViewComponentHelper:
@model YourNamespace.EmailContentModel <mjml> <mj-body> <mj-section> <mj-column> @await Component.InvokeAsync("DayPills", new { Pills = Model.DailyMedications }) </mj-column> </mj-section> </mj-body> </mjml>
关键注意事项
- 无需注册完整的
AddMvc(),AddMvcCore().AddViewComponents()已足够满足ViewComponent需求,避免冗余依赖 - 若ViewComponent内部需要依赖其他服务,需确保这些服务已注册到DI容器中
- 模板路径需与RazorLight配置的文件系统路径完全匹配
内容的提问来源于stack exchange,提问作者wetsbou
相关产品推荐
相关产品推荐

