Blazor Server应用中向本地API传递可变数量参数实现报表下载的方案咨询
嘿,我完全懂你现在的困扰——要给报表API传四十多个参数,手动拼查询字符串不仅麻烦还容易出错,用POST的话又没法直接通过导航触发浏览器下载,全局单例还怕多用户并发时参数串掉。结合你的Blazor Server + FastReport场景,我给你几个实用的解决方案:
方案1:自动生成查询字符串,避免手动拼接
不用自己一个个拼参数,写个简单的扩展方法把参数对象自动转换成查询字符串,既干净又不容易出错。
首先,添加一个对象转查询字符串的扩展方法:
public static class ObjectExtensions { public static string ToQueryString(this object obj) { var properties = obj.GetType().GetProperties() .Where(p => p.GetValue(obj) != null) .Select(p => $"{Uri.EscapeDataString(p.Name)}={Uri.EscapeDataString(p.GetValue(obj)?.ToString() ?? "")}"); return string.Join("&", properties); } }
然后在你的Reporting.razor里定义参数对象,用这个方法生成查询字符串:
<div class="content-container"> <input type="text" @bind-value="@_startDate" /> <input type="text" @bind-value="@_endDate" /> <input type="number" @bind-value="@_userId" /> <button @onclick="DoSomething" type="submit">Download File</button> </div> @code { [Inject] private NavigationManager NavManager { get; set; } private string _startDate; private string _endDate; private int? _userId; private async Task DoSomething() { var reportParams = new { reportName = "report-2", StartDate = _startDate, EndDate = _endDate, UserId = _userId // 这里可以加所有需要的参数 }; var queryString = reportParams.ToQueryString(); NavManager.NavigateTo($"api/Report/{reportParams.reportName}?{queryString}", true); } }
API控制器里可以直接用模型绑定接收参数,不用手动从QueryString取:
[HttpGet("{reportName}")] public IActionResult GetReport(string reportName, [FromQuery] ReportParameters parameters) { // 直接用parameters.StartDate、parameters.EndDate等 string reportPath = $"pathtofilehere.frx"; // ... 剩下的FastReport逻辑不变 } // 定义参数模型 public class ReportParameters { public string StartDate { get; set; } public string EndDate { get; set; } public int? UserId { get; set; } // 所有40个参数都可以在这里定义 }
方案2:用临时缓存+唯一标识,避免大量参数传递
如果参数实在太多,或者有些参数是复杂类型,你可以把参数临时存在服务器缓存里,给每个请求生成一个唯一的GUID,只传这个GUID到API,API再从缓存里取参数。这样既不用传长串查询字符串,也不会有用户冲突的问题。
首先在Blazor页面里把参数存到IMemoryCache:
@inject IMemoryCache Cache @inject NavigationManager NavManager <div class="content-container"> <!-- 输入控件省略 --> <button @onclick="DoSomething" type="submit">Download File</button> </div> @code { private async Task DoSomething() { var reportParams = new { StartDate = _startDate, EndDate = _endDate, // 所有参数... }; var requestId = Guid.NewGuid().ToString(); // 设置5分钟过期,避免缓存堆积 Cache.Set(requestId, reportParams, TimeSpan.FromMinutes(5)); NavManager.NavigateTo($"api/Report/report-2?requestId={requestId}", true); } }
然后API控制器里从缓存取参数:
[ApiController] [Route("api/[controller]")] public class ReportController : ControllerBase { private readonly IMemoryCache _cache; public ReportController(IMemoryCache cache) { _cache = cache; } [HttpGet("{reportName}")] public IActionResult GetReport(string reportName, string requestId) { if (!_cache.TryGetValue(requestId, out ReportParameters parameters)) { return BadRequest("请求已过期或无效"); } // 使用parameters生成报表... // 可选:用完移除缓存,节省空间 _cache.Remove(requestId); // 剩下的FastReport逻辑不变 } }
方案3:用POST+JSInterop触发下载,支持复杂参数
如果你不想用GET,其实可以用POST传JSON参数,然后通过JSInterop把返回的流转换成Blob,触发浏览器下载。这样既可以传复杂的参数对象,也不用先把文件存在服务器上。
首先修改API为POST方法:
[HttpPost("{reportName}")] public IActionResult PostReport(string reportName, [FromBody] ReportParameters parameters) { // 用parameters生成报表,和原来的逻辑一样 string reportPath = $"pathtofilehere.frx"; WebReport web = new(); web.Report.Load(reportPath); web.Report.SetParameterValue("CONN", @"connstringhere"); // 这里用parameters里的值设置报表参数 web.Report.SetParameterValue("StartDate", parameters.StartDate); web.Report.SetParameterValue("EndDate", parameters.EndDate); web.Report.Prepare(); MemoryStream stream = new(); web.Report.Export(new FastReport.Export.PdfSimple.PDFSimpleExport(), stream); stream.Position = 0; return File(stream, "application/pdf", $"{reportName}.pdf"); }
然后在Blazor页面里用HttpClient发POST,再调用JS下载:
@inject HttpClient Http @inject IJSRuntime JSRuntime <div class="content-container"> <!-- 输入控件省略 --> <button @onclick="DoSomething" type="submit">Download File</button> </div> @code { private async Task DoSomething() { var reportParams = new { StartDate = _startDate, EndDate = _endDate, // 所有参数... }; var response = await Http.PostAsJsonAsync("api/Report/report-2", reportParams); response.EnsureSuccessStatusCode(); // 把流转成字节数组 var bytes = await response.Content.ReadAsByteArrayAsync(); // 调用JS触发下载 await JSRuntime.InvokeVoidAsync("downloadFile", $"report-2.pdf", bytes); } }
最后在_Host.cshtml里添加JS方法:
<script> function downloadFile(filename, byteArray) { const blob = new Blob([byteArray], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } </script>
关于FastReport的替代方案,如果你想试试免费的,QuestPDF(纯.NET开源)、iTextSharp(注意License合规)都是不错的选择,但FastReport本身已经很成熟,如果你已经用习惯了完全可以继续用。
备注:内容来源于stack exchange,提问作者user12571241

