You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否实现文件上传代理控制器?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:32:53