能否实现文件上传代理控制器?Blazor Wasm跨域文件传输优化
Blazor WASM 文件上传/下载的流式代理解决方案
上传流式代理实现
核心思路是让ASP.NET Core控制器直接流式接收请求体,并即时转发到外部云API,全程不将文件保存到服务器磁盘或内存,彻底避免两次上传的问题。
控制器代码示例
[ApiController] [Route("api/proxy")] public class FileProxyController : ControllerBase { private readonly HttpClient _httpClient; public FileProxyController(HttpClient httpClient) { _httpClient = httpClient; } [HttpPost("upload")] public async Task<IActionResult> ForwardUpload() { // 直接读取Blazor请求的原始流 using var requestStream = Request.Body; // 构建转发到云API的请求,用流内容直接传递 var cloudRequest = new HttpRequestMessage(HttpMethod.Post, "https://your-cloud-api.com/upload") { Content = new StreamContent(requestStream) }; // 复制Blazor请求中的自定义头到云请求(按需过滤不需要的头) foreach (var header in Request.Headers) { if (!cloudRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray())) { cloudRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); } } // 即时转发并直接返回云API的响应 var cloudResponse = await _httpClient.SendAsync(cloudRequest, HttpCompletionOption.ResponseHeadersRead); return new HttpResponseMessageResult(cloudResponse); } } // 自定义结果类,直接透传第三方API的响应内容和头信息 public class HttpResponseMessageResult : IActionResult { private readonly HttpResponseMessage _responseMessage; public HttpResponseMessageResult(HttpResponseMessage responseMessage) { _responseMessage = responseMessage; } public async Task ExecuteResultAsync(ActionContext context) { var response = context.HttpContext.Response; response.StatusCode = (int)_responseMessage.StatusCode; // 复制响应头 foreach (var header in _responseMessage.Headers) { response.Headers.TryAdd(header.Key, new Microsoft.Extensions.Primitives.StringValues(header.Value.ToArray())); } foreach (var header in _responseMessage.Content.Headers) { response.Headers.TryAdd(header.Key, new Microsoft.Extensions.Primitives.StringValues(header.Value.ToArray())); } // 流式传递响应体 using var stream = await _responseMessage.Content.ReadAsStreamAsync(); await stream.CopyToAsync(response.Body); await response.Body.FlushAsync(); } }
Blazor WASM端上传代码
<input type="file" @ref="fileInput" /> <button @onclick="UploadFile">上传文件</button> @code { private ElementReference fileInput; private async Task UploadFile() { var files = await fileInput.GetFilesAsync(); if (files.Count == 0) return; var file = files[0]; using var content = new MultipartFormDataContent(); content.Add(new StreamContent(file.OpenReadStream()), "file", file.Name); // 添加自定义请求头 content.Headers.Add("X-Custom-Auth", "your-cloud-token"); var response = await HttpClient.PostAsync("/api/proxy/upload", content); response.EnsureSuccessStatusCode(); } }
下载流式代理实现
通过控制器直接从云API拉取文件流,即时返回给Blazor WASM,实现类似视频流的即时传输效果,无需服务器缓存完整文件。
控制器代码示例
[HttpGet("download/{fileId}")] public async Task<IActionResult> ForwardDownload(string fileId) { // 构建云API下载请求 var cloudRequest = new HttpRequestMessage(HttpMethod.Get, $"https://your-cloud-api.com/download/{fileId}"); // 添加云API所需的自定义头 cloudRequest.Headers.Add("X-Custom-Auth", "your-cloud-token"); // 发送请求并获取响应流(不等全部内容下载完成) var cloudResponse = await _httpClient.SendAsync(cloudRequest, HttpCompletionOption.ResponseHeadersRead); if (!cloudResponse.IsSuccessStatusCode) { return StatusCode((int)cloudResponse.StatusCode); } // 从云响应中解析内容类型和文件名(根据云API返回格式调整) var contentType = cloudResponse.Content.Headers.ContentType?.MediaType ?? "application/octet-stream"; var fileName = cloudResponse.Content.Headers.ContentDisposition?.FileNameStar ?? cloudResponse.Content.Headers.ContentDisposition?.FileName ?? $"file_{fileId}"; // 直接将云流返回给Blazor var stream = await cloudResponse.Content.ReadAsStreamAsync(); return File(stream, contentType, fileName); }
Blazor WASM端下载代码
<button @onclick="DownloadFile">下载文件</button> @code { private async Task DownloadFile() { var fileId = "target-file-id"; var response = await HttpClient.GetAsync($"/api/proxy/download/{fileId}", HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); var contentType = response.Content.Headers.ContentType?.MediaType ?? "application/octet-stream"; var fileName = response.Content.Headers.ContentDisposition?.FileNameStar ?? response.Content.Headers.ContentDisposition?.FileName ?? $"file_{fileId}"; // 通过JS将流保存到本地 var stream = await response.Content.ReadAsStreamAsync(); await JS.InvokeVoidAsync("saveAsFile", fileName, stream, contentType); } } // 配套JS代码(wwwroot/js/file-utils.js) function saveAsFile(fileName, byteArray, contentType) { const blob = new Blob([byteArray], { type: contentType }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }
关键注意事项
- 流式处理开关:必须使用
HttpCompletionOption.ResponseHeadersRead,确保客户端和服务器在获取响应头后立即开始流传输,无需等待完整响应。 - 头信息过滤:转发时注意过滤掉ASP.NET Core自动添加的非必要头(如
Host),避免与云API的头规则冲突。 - 性能优化:全程使用流传递,避免将文件加载到服务器内存,大幅降低大文件传输时的服务器资源占用。
- 错误透传:直接将云API的错误状态码返回给Blazor,方便前端统一处理上传/下载失败场景。
内容的提问来源于stack exchange,提问作者Igor Markiv
相关产品推荐
相关产品推荐

