是否存在可磁盘缓存并复用的RazorLight ICachingProvider现成实现?
解决方案
一、实现跨进程复用的文件系统缓存提供器
由于MemoryCachingProvider是进程内缓存,多次调用exe会失效,需要自定义实现ICachingProvider,将模板编译产物(避免直接缓存Assembly)持久化到磁盘:
public class FileSystemCachingProvider : ICachingProvider { private readonly string _cacheDir; public FileSystemCachingProvider(string cacheDirectory) { _cacheDir = Path.GetFullPath(cacheDirectory); Directory.CreateDirectory(_cacheDir); } public bool Contains(string key) { var cachePath = GetSafeCachePath(key); return File.Exists(cachePath); } public void Insert(string key, ITemplatePage templatePage) { var cachePath = GetSafeCachePath(key); // 缓存Razor生成的C#代码而非Assembly,规避内存Assembly序列化问题 var cachedData = new CachedTemplate { GeneratedCode = templatePage.GeneratedCode, ModelTypeFullName = templatePage.ModelType.AssemblyQualifiedName }; File.WriteAllText(cachePath, JsonSerializer.Serialize(cachedData)); } public ITemplatePage Retrieve(string key) { var cachePath = GetSafeCachePath(key); if (!File.Exists(cachePath)) return null; var cachedData = JsonSerializer.Deserialize<CachedTemplate>(File.ReadAllText(cachePath)); var modelType = Type.GetType(cachedData.ModelTypeFullName); // 从缓存代码重新编译生成TemplatePage var compiler = new RazorTemplateCompiler(); var compilationResult = compiler.CompileSource(cachedData.GeneratedCode, modelType); return compilationResult.CompiledTemplate; } private string GetSafeCachePath(string key) { // 将模板key转为安全文件名(用哈希避免非法字符) var hash = SHA256.HashData(Encoding.UTF8.GetBytes(key)); var fileName = $"{Convert.ToHexString(hash)}.json"; return Path.Combine(_cacheDir, fileName); } private class CachedTemplate { public string GeneratedCode { get; set; } public string ModelTypeFullName { get; set; } } }
二、初始化RazorLight引擎
在exe工具中,使用自定义缓存提供器初始化引擎,确保多次调用复用缓存:
// 选择用户有权限的缓存目录,比如本地应用数据目录 var cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RazorTemplateCache"); var engine = new RazorLightEngineBuilder() .UseFileSystemProject("你的模板根目录") .UseCachingProvider(new FileSystemCachingProvider(cacheDir)) .Build();
三、逐个传入模型的调用流程
- 首次调用:传入模板路径和第一个模型数据,引擎编译模板后将生成代码写入磁盘缓存,渲染输出结果。
- 后续调用:传入同一模板路径和新模型数据,引擎从磁盘读取缓存代码,重新编译生成TemplatePage后渲染。
PowerShell调用示例:
# 第一次调用,传入模型1 & .\RazorRenderer.exe -Template "Templates/UserProfile.cshtml" -Model '{"Name":"Alice","Age":30}' # 后续调用,传入模型2 & .\RazorRenderer.exe -Template "Templates/UserProfile.cshtml" -Model '{"Name":"Bob","Age":25}'
四、内存Assembly的替代处理(可选)
若一定要缓存编译后的Assembly,可通过自定义编译服务将Assembly保存到磁盘,再在缓存提供器中复用:
public class FileSavingCompilationService : ICompilationService { private readonly ICompilationService _innerService; private readonly string _assemblyDir; public FileSavingCompilationService(ICompilationService innerService, string assemblyDirectory) { _innerService = innerService; _assemblyDir = assemblyDirectory; Directory.CreateDirectory(_assemblyDir); } public CompilationResult Compile(CompilationOptions options) { var result = _innerService.Compile(options); if (result.CompiledAssembly != null) { // 将内存Assembly写入磁盘 var assemblyPath = Path.Combine(_assemblyDir, $"{options.TemplateName}.dll"); using var stream = new FileStream(assemblyPath, FileMode.Create); result.CompiledAssembly.Save(stream); } return result; } }
初始化时替换编译服务:
var assemblyDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RazorTemplateAssemblies"); var engine = new RazorLightEngineBuilder() .UseFileSystemProject("你的模板根目录") .UseCompilationService(new FileSavingCompilationService(new DefaultCompilationService(), assemblyDir)) .UseCachingProvider(new FileSystemCachingProvider(cacheDir)) .Build();
关键注意事项
- 模板key必须固定(比如用模板绝对路径),确保多次调用匹配同一缓存项。
- 新增
-ClearCache参数,用于模板更新时清理旧缓存。 - 缓存目录需选择用户有读写权限的路径,避免权限问题。
内容的提问来源于stack exchange,提问作者mark
相关产品推荐
相关产品推荐

