基于 WCF 实现 Silverlight 文件分片上传与进度追踪
在 Silverlight 应用中处理文件上传时,直接使用普通的 HTTP 请求可能会受到诸多限制。通过 WCF (Windows Communication Foundation) 服务来实现文件分片上传,不仅可以突破单次传输的大小限制,还能轻松实现上传进度的精确追踪。本文将详细讲解如何利用 WCF 结合流式分块读取技术,在 Silverlight 中实现高效、低内存占用的文件上传功能。
服务端 WCF 接口实现
首先,在 ASP.NET 宿主项目中添加一个"启用 Silverlight 的 WCF 服务"。为了避免大文件传输导致的内存溢出,服务端接口设计为接收文件分块(Chunk),并根据参数决定是创建新文件还是追加到现有文件。
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class FileTransferService
{
[OperationContract]
public void UploadChunk(string targetFileName, byte[] chunkData, bool isAppendMode)
{
// 定义上传目录,此处使用 App_Data 避免直接暴露在 Web 根目录
string uploadDirectory = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/Uploads");
if (!System.IO.Directory.Exists(uploadDirectory))
{
System.IO.Directory.CreateDirectory(uploadDirectory);
}
string fullPath = System.IO.Path.Combine(uploadDirectory, targetFileName);
System.IO.FileMode fileMode = isAppendMode ? System.IO.FileMode.Append : System.IO.FileMode.Create;
// 将接收到的数据块写入文件
using (System.IO.FileStream fs = new System.IO.FileStream(fullPath, fileMode, System.IO.FileAccess.Write))
{
fs.Write(chunkData, 0, chunkData.Length);
}
}
}
客户端服务引用配置
在 Silverlight 项目中,右键点击项目节点并选择"添加服务引用"。在弹出的对话框中点击"发现"按钮,Visual Studio 会自动查找当前解决方案中的 WCF 服务。选中刚才创建的 FileTransferService,命名空间可保持默认或自定义,点击确定完成引用。注意:如果在发现服务时出现错误,请确保先按 F5 编译并运行一次宿主 Web 项目。
客户端 UI 设计
为了直观展示上传状态,我们在 XAML 中放置一个触发按钮和一个用于显示进度信息的文本块。
<UserControl x:Class="SilverlightUploader.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Margin="20">
<Button x:Name="BtnSelectAndUpload" Content="选择并上传文件" Width="160" Height="40" Click="BtnSelectAndUpload_Click"/>
<TextBlock x:Name="TxtProgress" Margin="0,15,0,0" Text="准备就绪" FontSize="14"/>
</StackPanel>
</UserControl>
客户端业务逻辑与流式分块读取
原生的实现方式往往会将整个文件读取到内存中的集合里,这在处理大文件时极易引发 OutOfMemoryException。以下重构后的逻辑采用 Stream 游标机制,每次仅从磁盘读取固定大小的字节块(如 4KB)进行异步发送,大幅降低了客户端的内存压力。
public partial class MainPage : UserControl
{
private const int ChunkSize = 4096; // 每次上传 4KB
private FileUploadState _uploadState;
public MainPage()
{
InitializeComponent();
}
private void BtnSelectAndUpload_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog
{
Filter = "所有文件 (*.*)|*.*",
Multiselect = false
};
if (dialog.ShowDialog() == true)
{
FileInfo selectedFile = dialog.File;
_uploadState = new FileUploadState
{
FileName = selectedFile.Name,
TotalBytes = selectedFile.Length,
UploadedBytes = 0,
FileStream = selectedFile.OpenRead()
};
// 开始上传第一个数据块
SendNextChunk(false);
}
}
private void SendNextChunk(bool isAppend)
{
byte[] buffer = new byte[ChunkSize];
int bytesRead = _uploadState.FileStream.Read(buffer, 0, ChunkSize);
_uploadState.LastChunkSize = bytesRead;
// 如果读取字节数为 0,说明文件已传输完毕
if (bytesRead == 0)
{
_uploadState.FileStream.Close();
TxtProgress.Text = "上传完成!";
BtnSelectAndUpload.IsEnabled = true;
return;
}
// 截取实际读取到的字节数组
byte[] chunkToSend = new byte[bytesRead];
Array.Copy(buffer, chunkToSend, bytesRead);
var client = new FileTransferServiceClient();
client.UploadChunkCompleted += Client_UploadChunkCompleted;
BtnSelectAndUpload.IsEnabled = false;
client.UploadChunkAsync(_uploadState.FileName, chunkToSend, isAppend, _uploadState);
}
private void Client_UploadChunkCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
TxtProgress.Text = "上传出错: " + e.Error.Message;
_uploadState.FileStream.Close();
BtnSelectAndUpload.IsEnabled = true;
return;
}
_uploadState = e.UserState as FileUploadState;
_uploadState.UploadedBytes += _uploadState.LastChunkSize;
// 计算并更新进度显示
double progress = (_uploadState.UploadedBytes * 100.0) / _uploadState.TotalBytes;
TxtProgress.Text = $"进度: {progress:F2}% ({_uploadState.UploadedBytes} / {_uploadState.TotalBytes} 字节)";
// 继续递归上传下一个数据块
SendNextChunk(true);
}
}
/// <summary>
/// 维护单次上传任务的状态上下文
/// </summary>
public class FileUploadState
{
public string FileName { get; set; }
public long TotalBytes { get; set; }
public long UploadedBytes { get; set; }
public System.IO.Stream FileStream { get; set; }
public int LastChunkSize { get; set; }
}