如何在ASP.NET生命周期内为Razor视图一次性附加编译时数据?
你的需求很明确——要在应用生命周期内一次性给每个Razor视图绑定Webpack生成的带哈希的脚本/样式,既不想硬编码,也不想每次请求都去读取数据源。下面几个方案完全符合你的要求,都是在编译或启动阶段完成绑定,运行时直接取用静态数据:
方案一:利用Razor编译时代码生成器(ASP.NET Core)
这个方案是从根源上解决问题,直接在Razor视图编译的过程中,把Webpack资源路径注入到视图类的静态属性里,相当于编译时就"写死"到视图代码中,但又不需要你手动硬编码:
准备Webpack Manifest
首先让Webpack生成manifest.json文件,把每个视图对应的资源路径(带哈希)记录下来,比如:{ "Views/Home/Index": "/js/home-index.abc123.js", "Views/Account/Login": "/js/login.def456.js" }这里的键要和视图的完整路径对应,方便后续匹配。
自定义Razor代码生成器
创建一个继承自DefaultRazorCSharpCodeGenerator的类,重写GenerateView方法,在生成视图类代码时,从manifest里读取当前视图对应的资源路径,生成静态属性:public class AssetInjectingRazorGenerator : DefaultRazorCSharpCodeGenerator { private readonly IWebHostEnvironment _env; private readonly Dictionary<string, string> _assetManifest; public AssetInjectingRazorGenerator( RazorCodeGeneratorContext context, IWebHostEnvironment env) : base(context) { _env = env; // 应用启动时一次性读取manifest var manifestPath = Path.Combine(_env.ContentRootPath, "wwwroot", "manifest.json"); _assetManifest = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(manifestPath)); } public override void GenerateView() { base.GenerateView(); // 获取当前视图的路径(比如"Views/Home/Index") var viewPath = Context.FilePath.Substring(Context.FilePath.IndexOf("Views/")).Replace(".cshtml", ""); if (_assetManifest.TryGetValue(viewPath, out var scriptPath)) { // 给视图类添加静态属性 Context.CodeWriter.WriteLine($"public static string ScriptPath = \"{scriptPath}\";"); } } }注册自定义生成器
在Program.cs里替换默认的Razor代码生成器:builder.Services.AddRazorPages() .AddRazorRuntimeCompilation(options => { options.CodeGenerators.Add((context) => new AssetInjectingRazorGenerator(context, builder.Environment)); });在视图中使用
编译完成后,视图类会自动拥有ScriptPath静态属性,直接在视图里用:<script src="@this.ScriptPath"></script>这个属性是编译时确定的,运行时完全没有额外开销,和硬编码效果一样,但能自动同步Webpack的哈希变更。
方案二:启动时加载Manifest + 静态类映射
如果觉得编译生成器有点复杂,可以用更简单的静态类方案,在应用启动时一次性读取所有资源映射,然后通过静态类提供给视图:
创建静态资源类
定义一个静态类,每个视图对应一个属性:public static class ViewAssets { public static string HomeIndexScript { get; set; } public static string AccountLoginScript { get; set; } // 其他视图的属性... }启动时加载并赋值
在Program.cs的应用启动阶段(比如builder.Build()之后),读取Webpack的manifest并给静态属性赋值:var app = builder.Build(); var env = app.Services.GetRequiredService<IWebHostEnvironment>(); var manifestPath = Path.Combine(env.ContentRootPath, "wwwroot", "manifest.json"); var assetManifest = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(manifestPath)); ViewAssets.HomeIndexScript = assetManifest["Views/Home/Index"]; ViewAssets.AccountLoginScript = assetManifest["Views/Account/Login"]; // 其他视图的赋值...在视图中使用
先在_ViewImports.cshtml里导入静态类的命名空间:@using YourAppNamespace.ViewAssets然后在对应视图里直接用:
<script src="@ViewAssets.HomeIndexScript"></script>这个方案也是一次性加载,运行时直接取用静态属性,完全没有重复操作的开销。
额外说明
- 不管用哪个方案,当Webpack更新资源哈希后,只需要重新编译视图(方案一)或者重启应用(方案二)就能自动更新资源路径,和硬编码的更新流程一致。
- 如果用ASP.NET Core的Razor运行时编译,方案一在修改manifest后,视图会自动重新编译,不需要重启应用,非常方便开发阶段使用。
内容的提问来源于stack exchange,提问作者lockedscope

