You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 10:08:11