如何在Blazor Server中实现基于当前页面内容的动态文件下载功能
Blazor Server 动态文件下载实现方案
以下两种实现方案均无需单独部署WebAPI项目,可直接运行在单Blazor Server实例中,完美兼容Azure Web App部署,不会出现多进程调试/运行异常问题。
方案一:集成Razor Pages端点(推荐,实现简单)
1. 项目配置
首先在现有Blazor Server项目的Program.cs中开启Razor Pages支持:
// 服务注册阶段添加 builder.Services.AddRazorPages(); // 中间件配置阶段添加,放在MapBlazorHub之前 app.MapRazorPages();
2. 创建下载Razor页面
新增Razor Page(如Pages/DownloadReport.cshtml),对应的PageModel实现逻辑如下:
public class DownloadReportModel : PageModel { // 注入你自己的报表生成服务、分布式缓存 private readonly IReportGenerator _reportGenerator; private readonly IDistributedCache _cache; public DownloadReportModel(IReportGenerator reportGenerator, IDistributedCache cache) { _reportGenerator = reportGenerator; _cache = cache; } public async Task<IActionResult> OnGetAsync(string cacheKey) { // 从缓存读取Blazor端传入的筛选参数 var filterJson = await _cache.GetStringAsync(cacheKey); if (string.IsNullOrEmpty(filterJson)) return NotFound("参数已过期"); var filterParams = JsonSerializer.Deserialize<ReportFilterParams>(filterJson); // 生成报表字节内容 byte[] reportBytes = await _reportGenerator.GenerateAsync(filterParams); // 返回文件结果,第二个参数对应MIME类型,第三个为下载默认文件名 return File(reportBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"报表_{DateTime.Now:yyyyMMddHHmmss}.xlsx"); } }
如果你的筛选参数很少(比如只有2-3个简单字段),可以直接用查询字符串传递,无需缓存,直接在OnGet方法中接收对应参数即可。
3. Blazor端触发下载
Blazor组件中,首先在wwwroot/js/common.js中加入极简的下载触发JS:
function triggerDownload(downloadUrl) { const tempLink = document.createElement('a'); tempLink.href = downloadUrl; tempLink.target = '_blank'; document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); }
在_Layout.cshtml中引入该JS文件,然后在报表页面的组件中实现下载逻辑:
@inject IJSRuntime JSRuntime @inject IDistributedCache Cache <!-- 页面其他内容 --> <button @onclick="HandleDownloadReport">下载当前报表</button> @code { // 当前页面用户选择的筛选条件 private ReportFilterParams _currentFilter = new(); private async Task HandleDownloadReport() { // 生成唯一缓存键,将筛选参数存入缓存,设置1分钟过期 var cacheKey = Guid.NewGuid().ToString("N"); await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(_currentFilter), new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1) }); // 调用JS触发下载 await JSRuntime.InvokeVoidAsync("triggerDownload", $"/DownloadReport?cacheKey={cacheKey}"); } }
方案二:集成内置API控制器
Blazor Server项目本身支持直接添加API控制器,无需创建单独的WebAPI项目:
1. 项目配置
在Program.cs中开启控制器支持:
// 服务注册阶段添加 builder.Services.AddControllers(); // 中间件配置阶段添加,放在MapBlazorHub之前 app.MapControllers();
2. 新增报表下载控制器
[ApiController] [Route("api/report")] public class ReportController : ControllerBase { private readonly IReportGenerator _reportGenerator; public ReportController(IReportGenerator reportGenerator) { _reportGenerator = reportGenerator; } [HttpPost("download")] public async Task<IActionResult> Download([FromBody] ReportFilterParams filterParams) { byte[] reportBytes = await _reportGenerator.GenerateAsync(filterParams); return File(reportBytes, "application/pdf", $"报表_{DateTime.Now:yyyyMMddHHmmss}.pdf"); } }
3. Blazor端调用接口下载
添加JS方法:
async function postDownload(url, params) { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }); const blob = await res.blob(); const downloadUrl = URL.createObjectURL(blob); const tempLink = document.createElement('a'); tempLink.href = downloadUrl; tempLink.download = '报表.pdf'; tempLink.click(); URL.revokeObjectURL(downloadUrl); }
Blazor端调用:
private async Task HandleDownloadReport() { await JSRuntime.InvokeVoidAsync("postDownload", "/api/report/download", _currentFilter); }
注意事项
- 两种方案都属于单项目实现,所有服务、配置可以和Blazor Server共享,部署到Azure Web App时只需要发布一个包,没有多项目部署的问题
- 不需要单独创建WebAPI项目,避免了多进程调试、跨域、部署复杂度高的问题
- 如果报表文件体积较大,建议在生成文件时使用流式返回,减少内存占用
内容的提问来源于stack exchange,提问作者Tessaract
相关产品推荐
相关产品推荐

