Blazor Server应用实现本地拖拽文件预览:无需上传至服务器
解决FluentInputFile上传前客户端预览问题(避免大文件来回传输)
你的核心问题是当前实现需要先把文件上传到服务器再返回字节生成预览URL,大文件场景下会浪费带宽和服务器资源,同时担心直接绑定JS事件会破坏FluentInputFile原有逻辑。下面提供一种安全的实现方式:
方案思路
通过addEventListener绑定客户端change事件,在用户选择文件后直接在前端生成Blob URL用于预览,不覆盖FluentInputFile原有事件处理器,同时保留原有的上传流程。
1. 修改Blazor组件代码
添加预览区域和组件引用,用于绑定客户端事件:
<FluentInputFile @ref="videoInputFile" Id="video-upload" Mode="InputFileMode.SaveToTemporaryFolder" Accept="video/*,image/*" MaximumFileSize="@(2000*1024*1024)" @bind-ProgressPercent="@videoProgress" OnCompleted="@OnVideoCompletedAsync" Style="width:100%; height: 300px;"> <ChildContent> <FluentIcon Value="@(new Icons.Regular.Size16.ArrowUpload())"></FluentIcon> <span style="font-family: monospace; margin-left: 10px;">上传视频/图片</span> </ChildContent> </FluentInputFile> <!-- 预览区域 --> @if (!string.IsNullOrEmpty(previewUrl)) { @if (previewUrl.Contains("video/")) { <video controls src="@previewUrl" style="max-width:100%; margin-top:1rem; max-height:400px;"></video> } else { <img src="@previewUrl" style="max-width:100%; margin-top:1rem;" alt="文件预览" /> } }
2. 更新C#逻辑
添加组件引用、预览URL字段,绑定客户端事件并处理预览更新:
int videoProgress = 0; List<FluentInputFileEventArgs> videos = default!; FileInfo? video; string? videoUrl; string? previewUrl; private FluentInputFile? videoInputFile; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && videoInputFile != null) { // 绑定客户端预览事件,不会覆盖原有事件 await JSRuntime.InvokeVoidAsync("bindFilePreview", videoInputFile.Id, DotNetObjectReference.Create(this)); } await base.OnAfterRenderAsync(firstRender); } [JSInvokable] public void UpdatePreview(string url, string fileType) { // 释放旧的预览URL,避免内存泄漏 if (!string.IsNullOrEmpty(previewUrl)) { _ = JSRuntime.InvokeVoidAsync("revokeObjectURL", previewUrl); } previewUrl = url; StateHasChanged(); } private async Task OnVideoCompletedAsync(IEnumerable<FluentInputFileEventArgs> _videos) { videos = _videos.ToList(); Console.WriteLine($"[EVENT] 文件上传完成 : {videos.Count()}"); videos.ForEach(x => Console.WriteLine($"文件名: {x.Name}, 大小: {x.Size/1000000f} MB")); video = videos[0].LocalFile; videoUrl = await embedFiles(video, video.Extension switch { ".mp4" => "video/mp4", ".jpg" or ".jpeg" => "image/jpeg", ".png" => "image/png", _ => "application/octet-stream" }); videoProgress = 0; StateHasChanged(); } private async Task<string?> embedFiles(FileInfo? fileInfo, string format) { if(fileInfo == null) { return null; } byte[] bytes = new byte[fileInfo.Length]; using(FileStream fileStream = fileInfo.OpenRead()) { await fileStream.ReadAsync(bytes); } string url = await JSRuntime.InvokeAsync<string>("bytesToDataURL", bytes, format); Console.WriteLine($"[INFO] 文件URL : {url}"); return url; } // 组件销毁时释放预览URL public void Dispose() { if (!string.IsNullOrEmpty(previewUrl)) { _ = JSRuntime.InvokeVoidAsync("revokeObjectURL", previewUrl); } }
3. 客户端JS代码
使用addEventListener绑定事件,确保不破坏原有逻辑:
function bindFilePreview(elementId, dotNetRef) { const input = document.getElementById(elementId); if (!input) return; // 用addEventListener添加事件,不会覆盖组件原有绑定的事件 input.addEventListener('change', (e) => { const file = e.target.files?.[0]; if (!file) return; // 直接在客户端生成Blob URL用于预览 const previewUrl = URL.createObjectURL(file); dotNetRef.invokeMethodAsync('UpdatePreview', previewUrl, file.type); }); } async function bytesToDataURL(bytes, format) { const blob = new Blob([new Uint8Array(bytes)], { type: format }); let url = URL.createObjectURL(blob); console.log(url); return url; } function revokeObjectURL(url) { URL.revokeObjectURL(url); }
方案优势
- 不破坏原有逻辑:使用
addEventListener而非直接覆盖onchange,不会影响FluentInputFile自带的上传事件处理器 - 节省资源:预览完全在客户端完成,大文件无需先上传到服务器再返回
- 预览与上传分离:预览过程不干扰原有上传流程,用户体验更流畅
内容的提问来源于stack exchange,提问作者Ajaykrishnan R
相关产品推荐
相关产品推荐

