能否通过Blazor Server应用实现浏览器文件直接上传至Azure Blob Storage?
Blazor Server 实现直接上传文件到Azure Blob Storage(带进度跟踪)
问题背景
我在Azure App Service上托管了Blazor Server应用,当前文件上传流程是先把用户本地文件传到Web应用服务器,再从服务器传到Azure Blob Storage,这种两步上传效率极低,文件要走两次网络。我希望实现用户浏览器直接上传到Blob Storage,同时借助Azure.Storage.Blobs的进度回调更新前端进度条。试过微软官方示例大多是两步上传,JS方案也没在Blazor里跑通,现有简化代码如下:
public static async Task UploadFileToAzureBlobStorage(IBrowserFile fileToUpload, Progress<long> progressHandler, Progress<long> copyProgressHandler) { // 创建带令牌的Blob URI Uri blobUri = new Uri($"https://storageAccountName.blob.core.windows.net/containerName/filename.txt?token"); // 从URI创建BlobClient BlobClient blobClient = new BlobClient(blobUri); // 拼接服务器本地文件路径 string filename = Path.Combine("folderName", "filename.txt"); // 上传操作 FileStream stream = new FileStream(filename, FileMode.Create); try { // 第一步:用户文件传到Web服务器 await fileToUpload.OpenReadStream(fileToUpload.Size).CopyToAsync(stream, copyProgressHandler); // 第二步:Web服务器文件传到Blob Storage stream.Position = 0; await Task.Run(() => blobClient.Upload(stream, progressHandler: progressHandler)); } catch (Exception ex) { // 错误处理 } // 释放流 await stream.DisposeAsync(); }
解决方案:直接上传+进度跟踪
核心思路是通过JS互操作调用Azure Storage JS SDK,让用户浏览器直接和Blob Storage交互,同时把上传进度回调到Blazor组件更新UI。
步骤1:后端生成带写入权限的SAS URI
保持你现有生成SAS URI的逻辑,确保令牌包含Write权限,且过期时间足够覆盖上传时长。示例代码:
public string GenerateBlobSasUri(string containerName, string blobName) { var blobServiceClient = new BlobServiceClient("你的存储账户连接字符串"); var containerClient = blobServiceClient.GetBlobContainerClient(containerName); var blobClient = containerClient.GetBlobClient(blobName); var sasBuilder = new BlobSasBuilder() { BlobContainerName = containerName, BlobName = blobName, ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(30), Protocol = SasProtocol.Https }; sasBuilder.SetPermissions(BlobSasPermissions.Write); return blobClient.GenerateSasUri(sasBuilder).ToString(); }
步骤2:Blazor组件实现文件选择与进度跟踪
创建上传组件,通过IJSRuntime调用JS上传逻辑,并接收进度回调:
@page "/direct-upload" @inject IJSRuntime JSRuntime <h3>直接上传到Blob Storage</h3> <input type="file" @onchange="OnFileSelected" /> <div class="progress-bar" style="width:@progressPercent%">@progressPercent%</div> @code { private IBrowserFile? _selectedFile; private int progressPercent; private async Task OnFileSelected(InputFileChangeEventArgs e) { _selectedFile = e.File; progressPercent = 0; // 获取后端生成的SAS URI var sasUri = await GenerateSasUriAsync("你的容器名", _selectedFile.Name); // 调用JS上传方法,传递文件、SAS URI和进度回调引用 await JSRuntime.InvokeVoidAsync( "blobUploader.upload", _selectedFile, sasUri, DotNetObjectReference.Create(this)); } [JSInvokable] public void UpdateUploadProgress(long bytesSent, long totalBytes) { progressPercent = (int)((double)bytesSent / totalBytes * 100); StateHasChanged(); } // 实际项目中替换为调用后端接口获取SAS URI private Task<string> GenerateSasUriAsync(string container, string blobName) { return Task.FromResult("生成的SAS URI"); } }
步骤3:编写JS上传逻辑
在wwwroot/js下创建blob-upload.js,封装Azure Storage SDK的上传逻辑:
window.blobUploader = { async upload(file, sasUri, dotNetRef) { // 从SAS URI初始化Blob服务客户端 const blobServiceClient = Azure.Storage.Blobs.BlobServiceClient.fromConnectionString(sasUri); const containerName = new URL(sasUri).pathname.split('/')[1]; const containerClient = blobServiceClient.getContainerClient(containerName); const blobClient = containerClient.getBlobClient(file.name); try { // 上传文件并跟踪进度 await blobClient.uploadBrowserData(file, { progress: (event) => { const bytesSent = event.loaded; const totalBytes = event.total; dotNetRef.invokeMethodAsync('UpdateUploadProgress', bytesSent, totalBytes); } }); alert('上传完成'); } catch (error) { console.error('上传失败:', error); alert('上传失败,请重试'); } finally { dotNetRef.dispose(); } } };
步骤4:引入依赖脚本
在wwwroot/index.html的body末尾添加Azure Storage JS SDK和自定义脚本:
<script src="https://aka.ms/azure-storage-blob-js"></script> <script src="js/blob-upload.js"></script>
关键说明
- 完全绕过App Service:文件从用户浏览器直接传到Blob Storage,避免了两次上传的带宽浪费和延迟
- 进度跟踪:通过JS的
progress回调,将实时进度传递给Blazor组件,更新UI - 权限安全:SAS URI仅授予临时写入权限,过期后自动失效,避免存储账户密钥泄露
- 内存管理:使用
DotNetObjectReference后要及时释放,防止Blazor Server内存泄漏
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

