ASP.NET Core Razor动态编译视图中嵌套Razor语法解析问题
解决方案:让动态Razor片段在编译阶段解析执行
你的核心问题是:存储在模型属性中的Razor语法字符串未被编译器解析,而是直接作为文本输出。要实现嵌套Razor语法的编译执行,同时避免每次请求解析,可以通过预编译动态Razor模板并缓存的方式解决,以下是具体方案:
1. 实现Razor模板编译服务
创建一个专门的服务,负责将字符串形式的Razor模板编译为可执行委托,并缓存编译结果,仅在模板内容变更时重新编译。
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Collections.Concurrent; public interface IDynamicRazorCompiler { Task<string> RenderAsync(string razorTemplate, ViewContext viewContext); } public class DynamicRazorCompiler : IDynamicRazorCompiler { private readonly RazorTemplateEngine _razorEngine; private readonly IServiceProvider _serviceProvider; private readonly ConcurrentDictionary<string, Func<ViewContext, Task<string>>> _compiledTemplates = new(); public DynamicRazorCompiler(RazorTemplateEngine razorEngine, IServiceProvider serviceProvider) { _razorEngine = razorEngine; _serviceProvider = serviceProvider; } public async Task<string> RenderAsync(string razorTemplate, ViewContext viewContext) { // 用模板内容生成唯一缓存键 var cacheKey = Convert.ToBase64String(System.Security.Cryptography.MD5.HashData(Encoding.UTF8.GetBytes(razorTemplate))); if (!_compiledTemplates.TryGetValue(cacheKey, out var renderFunc)) { // 解析Razor模板 var document = _razorEngine.ParseTemplate(razorTemplate); var codeDocument = _razorEngine.GenerateCode(document); // 配置编译所需的程序集引用 var references = new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(ViewContext).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Security.Claims.ClaimsPrincipal).Assembly.Location), MetadataReference.CreateFromFile(typeof(Microsoft.AspNetCore.Http.HttpContext).Assembly.Location), // 添加你的项目程序集引用 MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location) }; // 编译生成的C#代码 var compilation = CSharpCompilation.Create( $"DynamicRazorTemplate_{cacheKey}", new[] { codeDocument.GeneratedCode }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); // 输出编译结果到内存流 using var ms = new MemoryStream(); var emitResult = compilation.Emit(ms); if (!emitResult.Success) { var errors = string.Join(Environment.NewLine, emitResult.Diagnostics.Select(d => d.ToString())); throw new InvalidOperationException($"模板编译失败:{errors}"); } // 加载编译后的程序集 ms.Seek(0, SeekOrigin.Begin); var assembly = Assembly.Load(ms.ToArray()); var pageType = assembly.GetType("AspNetCore.GeneratedPages.DynamicTemplate"); // 创建渲染委托 renderFunc = async (context) => { var razorPage = (IRazorPage)Activator.CreateInstance(pageType); razorPage.PageContext = context.PageContext; razorPage.ViewContext = context; using var writer = new StringWriter(); context.Writer = writer; await razorPage.ExecuteAsync(); return writer.ToString(); }; _compiledTemplates.TryAdd(cacheKey, renderFunc); } return await renderFunc(viewContext); } }
2. 注册服务
在Program.cs中注册这个服务:
builder.Services.AddScoped<IDynamicRazorCompiler, DynamicRazorCompiler>(); // 注册RazorTemplateEngine实例 builder.Services.AddSingleton<RazorTemplateEngine>(sp => { var razorEngine = sp.GetRequiredService<RazorEngine>(); var options = sp.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value; return new RazorTemplateEngine(razorEngine, options); });
3. 在视图中使用
修改视图代码,注入服务并调用解析逻辑:
@inject IDynamicRazorCompiler _dynamicRazorCompiler <p>@if (!string.IsNullOrEmpty(Model.MyPropertyOnTheModel)) { @Html.Raw(await _dynamicRazorCompiler.RenderAsync(Model.MyPropertyOnTheModel, ViewContext)) }</p>
4. 安全注意事项
- 严格限制能编辑Razor模板内容的用户范围,杜绝代码注入风险。
- 可对模板内容做白名单过滤,仅允许访问安全的内置对象(如
@User.Identity.Name)。
替代方案:将模板作为独立视图存储
如果动态内容本身就是视图的一部分,可直接将带Razor语法的内容存储在数据库中作为完整视图片段,通过DbFileProvider加载时直接编译整个视图。这种方式更贴合你现有的编译机制,但仅适用于内容变更时允许重新编译视图的场景。
内容的提问来源于stack exchange,提问作者Mark Redfern
相关产品推荐
相关产品推荐

