如何在Blazor的非RAZOR类CS文件中访问Startup注册的Scoped服务数据
问题根因
- Blazor Server 中
Scoped服务的生命周期与用户连接的 Circuit 绑定,同一个用户会话内会复用同一个服务实例;而 MVC/API 控制器的Scoped服务生命周期为单次 HTTP 请求,每次请求都会创建全新的实例,二者属于完全隔离的作用域,所以控制器中注入的DownloadingFile和 Razor 页面中注入的不是同一个对象,读取值自然为 null。手动实例化的对象更是没有经过赋值,为空属于正常现象。 - 如果是 Blazor WASM 项目,Scoped 服务完全运行在客户端浏览器中,服务端控制器不可能直接读取到客户端内存中的对象。
解决方案
方案1:不走控制器,直接用JS互操作在Blazor页面触发下载(最优解,无跨作用域问题)
无需额外开发控制器,直接通过JS在前端触发文件下载,适合文件体积不超过几百MB的场景:
- 在
wwwroot目录的公共JS文件中添加下载方法:
function triggerDownload(fileName, base64Content) { const aTag = document.createElement('a'); aTag.download = fileName; aTag.href = `data:application/octet-stream;base64,${base64Content}`; document.body.appendChild(aTag); aTag.click(); document.body.removeChild(aTag); }
- 在 Razor 页面中调用该方法:
@inject IJSRuntime JSRuntime @* 页面其他内容 *@ @code { [Inject] private DownloadingFile DownloadFileObj { get; set; } private async Task Download() { // 确认theFile已赋值 if(DownloadFileObj.theFile == null || DownloadFileObj.theFile.Length == 0) return; string base64Str = Convert.ToBase64String(DownloadFileObj.theFile); await JSRuntime.InvokeVoidAsync("triggerDownload", "自定义文件名.后缀", base64Str); } }
方案2:通过缓存中转数据(需要保留控制器的场景)
用内存缓存作为中间层存储文件字节数组,关联唯一ID传递给控制器,控制器通过ID读取缓存内容返回:
- 先注册内存缓存服务(Program.cs/Startup.cs):
services.AddMemoryCache();
- 在 Razor 页面生成带唯一ID的下载链接:
@inject IMemoryCache MemoryCache @* 页面其他内容 *@ @code { [Inject] private DownloadingFile DownloadFileObj { get; set; } private string DownloadLink { get; set; } private void GenerateDownloadLink() { // 赋值theFile后生成唯一ID string downloadId = Guid.NewGuid().ToString("N"); // 缓存有效期设为10分钟,足够完成下载 MemoryCache.Set(downloadId, DownloadFileObj.theFile, TimeSpan.FromMinutes(10)); DownloadLink = $"/Download/Download?downloadId={downloadId}"; } }
- 调整控制器逻辑,从缓存读取数据:
public class DownloadController : Controller { private readonly IMemoryCache _cache; public DownloadController(IMemoryCache cache) { _cache = cache; } public IActionResult Download(string downloadId) { if(!_cache.TryGetValue(downloadId, out byte[] fileBytes) || fileBytes == null) { return NotFound("下载链接已过期或无效"); } // 可选:下载完成后立即删除缓存 _cache.Remove(downloadId); return File(fileBytes, "application/octet-stream", "自定义文件名.后缀"); } }
如果是超大文件,可以将字节数组写入服务端临时目录,传递文件名/ID给控制器,控制器读取临时文件返回即可,注意定期清理临时目录的过期文件。
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

