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

Blazor Server大文件分片上传至CDN时文件损坏问题求助

Blazor Server分片上传文件内容损坏问题修复方案

核心问题定位

原代码的关键错误在于未正确处理ReadAsync的返回值:Stream.ReadAsync并不保证会读取到请求的全部字节数,返回值是实际读取的字节数。原代码直接使用整个预分配的chunk数组,导致当实际读取字节数小于chunkSize时,数组末尾的无效字节(默认是0)会被一并上传,最终合并后的文件包含冗余数据,导致内容损坏。

修复步骤及优化点

1. 正确处理流读取的实际字节数

修改分片读取逻辑,只使用实际读取到的字节部分:

var chunk = new byte[chunkSize];
var bytesRead = await inStream.ReadAsync(chunk, 0, (int)chunkSize);
if (bytesRead == 0)
{
    _uploading = false;
    break;
}
// 仅使用实际读取的字节创建内存流
using var chunkStream = new MemoryStream(chunk, 0, bytesRead);
var fileContent = new StreamContent(chunkStream);

2. 增加上传响应校验

每次分片上传后校验响应状态,避免失败的分片被忽略:

var response = await Http.SendAsync(request);
response.EnsureSuccessStatusCode(); // 上传失败时抛出异常

3. 优化Blazor Server流读取配置

Blazor Server中InputFile.OpenReadStream的参数建议设置为文件实际大小,而非long.MaxValue,减少不必要的内存开销:

using (var inStream = file.OpenReadStream(file.Size))

4. 完善Content-Type设置

为分片内容设置正确的ContentType,避免CDN处理异常:

fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);

5. 确保资源正确释放

使用using语句管理StreamContent和MemoryStream,避免资源泄漏:

using var chunkStream = new MemoryStream(chunk, 0, bytesRead);
using var fileContent = new StreamContent(chunkStream);

修改后的完整代码示例

private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
    const long CHUNKSIZE = 1024 * 400; // 可根据实际场景调整

    var file = e.File;
    long uploadedBytes = 0;
    long totalBytes = file.Size;
    long percent = 0;
    int fragment = 0;
    long chunkSize;

    using (var inStream = file.OpenReadStream(file.Size))
    {
        blockView = false;
        model.Url = string.Empty;
        _uploading = true;
        var contentType = Path.GetExtension(file.Name);            

        while (_uploading)
        {
            chunkSize = uploadedBytes + CHUNKSIZE > totalBytes 
                ? totalBytes - uploadedBytes 
                : CHUNKSIZE;

            var chunk = new byte[chunkSize];
            var bytesRead = await inStream.ReadAsync(chunk, 0, (int)chunkSize);
            
            if (bytesRead == 0)
            {
                _uploading = false;
                break;
            }

            using var chunkStream = new MemoryStream(chunk, 0, bytesRead);
            using var fileContent = new StreamContent(chunkStream);
            fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);

            var request = new HttpRequestMessage(HttpMethod.Put, $"https://{cdn_url}/{model.Files.Name}/{(fragment++).ToString("D8")}");
            request.Headers.Add("X-Auth-Token", model.Files.Token);
            request.Content = fileContent;

            var response = await Http.SendAsync(request);
            response.EnsureSuccessStatusCode();

            // 更新进度与UI
            uploadedBytes += bytesRead;
            percent = uploadedBytes * 100 / totalBytes;
            echo = $"上传 {percent}%  {(uploadedBytes / 1e+6).ToString("F2")} MB / {(totalBytes / 1e+6).ToString("F2")} MB";
            _uploading = percent < 100;

            Console.WriteLine($"分片 {fragment} 上传字节数: {bytesRead}");
            await InvokeAsync(StateHasChanged);
        }

        // 发起分片合并请求
        var requestItog = new HttpRequestMessage(HttpMethod.Put, $"https://{cdn_url}/{model.Files.Name + contentType}");
        requestItog.Headers.Add("X-Auth-Token", model.Files.Token);
        requestItog.Headers.Add("X-Object-Manifest", $"{Container_Name}/{model.Files.Name}/");

        var responseItog = await Http.SendAsync(requestItog);
        responseItog.EnsureSuccessStatusCode();

        model.Url = model.Files.Name + contentType;
        await AddName.InvokeAsync(model.Files.Name + contentType);
    }
}

额外检查点

  • 确认CDN的X-Object-Manifest合并规则:确保CDN会按分片文件名的字典序(00000000、00000001...)正确合并所有分片。
  • Blazor Server Hub配置:已设置MaximumReceiveMessageSize为100MB,满足大文件分片传输的SignalR限制,无需调整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 01:55:58