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

如何用Blazor上传大文件至WebAPI并存储到Azure Blob Storage(SDK v12)

Blazor大文件上传至Azure Blob Storage(v12 SDK)解决方案

针对4GB大文件上传需求,采用**Azure Blob分块上传(Block Blob)**机制,实现前端分片、服务端直接转发流到Azure、无服务端内存占用的完整流程。

核心思路

利用Azure Block Blob的块上传特性:

  1. Blazor前端将大文件拆分为固定大小分片(如100MB)
  2. 逐个将分片和唯一块ID发送至后端API
  3. 后端API直接将请求体流上传至Azure Blob的对应块,不缓存分片数据
  4. 所有分片上传完成后,触发Azure端的块合并操作,生成单个完整Blob

1. Blazor前端代码

<input type="file" @onchange="HandleFileSelected" />
<button @onclick="StartUpload" disabled="@isUploading">开始上传</button>
<div>上传进度:@uploadProgress%</div>

@code {
    private IReadOnlyList<IBrowserFile> selectedFiles;
    private bool isUploading;
    private int uploadProgress;
    private const int BlockSize = 100 * 1024 * 1024; // 100MB分片(Azure单块最大支持100MB)
    private List<string> uploadedBlockIds = new();

    private void HandleFileSelected(InputFileChangeEventArgs e)
    {
        selectedFiles = e.GetMultipleFiles(1);
    }

    private async Task StartUpload()
    {
        if (selectedFiles?.FirstOrDefault() == null) return;
        
        isUploading = true;
        var targetFile = selectedFiles.First();
        var uniqueBlobName = $"{Guid.NewGuid()}{Path.GetExtension(targetFile.Name)}";
        long totalBytes = targetFile.Size;
        long uploadedBytes = 0;

        // 打开文件流,设置4GB最大限制
        using var fileStream = targetFile.OpenReadStream(maxAllowedSize: 4L * 1024 * 1024 * 1024);
        int blockIndex = 0;

        while (uploadedBytes < totalBytes)
        {
            int bytesToRead = (int)Math.Min(BlockSize, totalBytes - uploadedBytes);
            var chunkBuffer = new byte[bytesToRead];
            int actualReadBytes = await fileStream.ReadAsync(chunkBuffer, 0, bytesToRead);
            if (actualReadBytes == 0) break;

            // 生成Base64编码的块ID(Azure要求格式)
            var blockId = Convert.ToBase64String(BitConverter.GetBytes(blockIndex));
            uploadedBlockIds.Add(blockId);

            // 发送分片至API
            using var chunkContent = new ByteArrayContent(chunkBuffer, 0, actualReadBytes);
            var response = await Http.PostAsync(
                $"api/upload/chunk?blobName={uniqueBlobName}&blockId={blockId}", 
                chunkContent
            );
            response.EnsureSuccessStatusCode();

            uploadedBytes += actualReadBytes;
            uploadProgress = (int)((uploadedBytes * 100) / totalBytes);
            blockIndex++;
            StateHasChanged();
        }

        // 触发块合并
        var mergeResponse = await Http.PostAsync(
            $"api/upload/complete?blobName={uniqueBlobName}&blockIds={string.Join(",", uploadedBlockIds)}",
            null
        );
        mergeResponse.EnsureSuccessStatusCode();

        isUploading = false;
        uploadProgress = 100;
        StateHasChanged();
    }
}

2. 后端API代码

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/upload")]
public class UploadController : ControllerBase
{
    private readonly BlobServiceClient _blobServiceClient;
    private const string TargetContainer = "your-container-name"; // 替换为你的容器名

    public UploadController(BlobServiceClient blobServiceClient)
    {
        _blobServiceClient = blobServiceClient;
    }

    /// <summary>
    /// 上传单个分片至Azure Blob
    /// </summary>
    [HttpPost("chunk")]
    public async Task<IActionResult> UploadChunk([FromQuery] string blobName, [FromQuery] string blockId)
    {
        // 获取容器客户端(不存在则创建)
        var containerClient = _blobServiceClient.GetBlobContainerClient(TargetContainer);
        await containerClient.CreateIfNotExistsAsync(PublicAccessType.Blob);

        // 获取Blob客户端,直接转发请求体流到Azure
        var blobClient = containerClient.GetBlobClient(blobName);
        await blobClient.StageBlockAsync(blockId, Request.Body);

        return Ok();
    }

    /// <summary>
    /// 合并所有分片为完整Blob
    /// </summary>
    [HttpPost("complete")]
    public async Task<IActionResult> CompleteUpload([FromQuery] string blobName, [FromQuery] string blockIds)
    {
        var containerClient = _blobServiceClient.GetBlobContainerClient(TargetContainer);
        var blobClient = containerClient.GetBlobClient(blobName);

        // 拆分块ID列表,触发Azure端合并
        var blockIdList = blockIds.Split(',').ToList();
        await blobClient.CommitBlockListAsync(blockIdList);

        return Ok(new { BlobUrl = blobClient.Uri.AbsoluteUri });
    }
}

3. 服务配置

注册Azure Blob客户端(Program.cs)

// 从配置文件读取Azure Blob连接字符串
builder.Services.AddSingleton(sp => 
    new BlobServiceClient(builder.Configuration.GetConnectionString("AzureBlobStorage")));

配置Kestrel请求大小限制(Program.cs)

builder.Services.Configure<KestrelServerOptions>(options =>
{
    // 限制单个请求大小与分片大小一致
    options.Limits.MaxRequestBodySize = 100 * 1024 * 1024;
});

关键注意事项

  • 分片大小选择:推荐100MB,Azure标准Block Blob单块最大支持100MB,高级Block Blob可支持4GB单块
  • 块ID规范:必须为Base64编码,且所有块ID长度需一致(示例中用4字节索引转换,长度固定)
  • 断点续传扩展:可在上传前调用blobClient.GetBlockListAsync获取已上传块ID,跳过重复分片
  • 超时设置:需为API请求和Azure SDK操作配置合理超时时间

内容的提问来源于stack exchange,提问作者Hamlet Poghosyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 03:40:16