使用WCF上传/下载文件时的进度指示






4.92/5 (63投票s)
本文探讨了使用Windows Communication Foundation实现带进度指示(进度条功能)的上传和下载功能。

引言
本文探讨了使用Windows Communication Foundation实现带进度指示(进度条功能)的上传和下载功能。对于此示例,您需要Visual Studio 2008。
示例代码包含三个项目,捆绑在一个解决方案中。以下是对这些项目的简要描述。随附的示例代码有C#和VB.NET版本(VB.NET版本由Project Time and Cost的Lee Galloway转换)。
FileService
这是主要的服务器项目。
服务器契约
File Server项目包含FileTransferServiceContract.cs文件,其中包含IFileTransferService
接口。此接口描述了我们服务器提供的操作。此代码文件中不执行实际工作,仅描述提供的操作。如果您之前处理过面向服务的应用程序,您会知道这项工作足够重要,值得为此创建一个单独的文件。以下是我们文件传输服务的两个操作:
-
DownloadFile 服务器方法
接受一个
DownloadRequest
实例,其中包含客户端要下载的文件的名称。它返回一个在同一代码文件中定义的RemoteFileInfo
实例。RemoteFileInfo
包含要下载的文件名、文件流和文件的大小(字节)。此RemoteFileInfo
类的实例将由客户端用于下载文件。您注意到RemoteFileInfo
类中的filename
和length
带有MessageHeader
属性。这是因为当消息契约包含stream
时,它可能是契约中唯一的正文成员。 -
UploadFile 服务器方法
接受一个
RemoteFileInfo
消息契约的实例。这与DownloadFile
中使用的相同,只是在这种情况下,不需要length
属性。
CS
[ServiceContract()]
public interface IFileTransferService
{
[OperationContract()]
void UploadFile(RemoteFileInfo request);
[OperationContract]
RemoteFileInfo DownloadFile(DownloadRequest request);
}
public class RemoteFileInfo
{
[MessageHeader(MustUnderstand = true)]
public string FileName;
[MessageHeader(MustUnderstand = true)]
public long Length;
[MessageBodyMember(Order = 1)]
public System.IO.Stream FileByteStream;
}
VB
<ServiceContract()> _
<servicecontract() />Public Interface IFileTransferService
<OperationContract()> _
Sub UploadFile(ByVal request As RemoteFileInfo)
<OperationContract()> _
Function DownloadFile(ByVal request As DownloadRequest) As RemoteFileInfo
End Interface
<MessageContract()> _
Public Class RemoteFileInfo
Implements IDisposable
<OperationContract()> _
Public FileName As String
<OperationContract()> _
Public Length As Long
<MessageBodyMember(Order:=1)> _
Public FileByteStream As System.IO.Stream
End Class
服务器实现
File Server还包含FileTransferService.cs代码文件,其中包含契约的实现,即实际执行所有工作的代码。显然,包含的类实现了IFileTransferService
类,构成了服务契约。如果您之前在.NET中处理过流,您会发现处理上传或下载的流和相关信息的代码非常简单。如果您不熟悉.NET流,请在Google上搜索快速介绍。
请注意,由于文件的实际下载是在DownloadFile
方法执行完成后(即客户端获取此方法返回的RemoteFileInfo
实例后)开始的,服务器必须在客户端完成过程后关闭已打开的流。Buddhike提出了一种优雅的方法。为此,RemoteFileInfo
契约实现了IDisposable
接口,并在相应的Dispose
方法中处理流。如果不这样做,流将保持锁定状态,相应的文件也将被锁定,无法写入。
ConsoleHost
FileService
是一个类库,因此它不能作为窗口进程启动。因此,它需要另一个可执行文件进程来托管它。几种类型的进程可以托管WCF服务,例如.NET可执行文件、IIS进程、Windows Activation Services(Vista的新功能)等等。我们的示例使用.NET可执行文件来托管我们的服务。因此,ConsoleHost
是一个控制台应用程序,它正好执行此操作。它引用了FileService
项目。但是,它与我们服务所做的业务(即传输文件)没有任何关系。实际上,即使我们的服务被设计为托管一家在线杂货店,您在Program.cs中找到的代码也是相同的。快速查看此代码文件,了解我们的服务如何启动和关闭。
CS
static void Main(string[] args)
{
ServiceHost myServiceHost = new ServiceHost
(typeof(FileService.FileTransferService));
myServiceHost.Open();
Console.WriteLine("This is the SERVER console");
Console.WriteLine("Service Started!");
foreach (Uri address in myServiceHost.BaseAddresses)
Console.WriteLine("Listening on " + address.ToString());
Console.WriteLine("Click any key to close...");
Console.ReadKey();
myServiceHost.Close();
}
VB
Public Shared Sub Main()
Dim myServiceHost As New ServiceHost(_
GetType(FileService.FileTransferService))
myServiceHost.Open()
Console.WriteLine("This is the SERVER console")
Console.WriteLine("Service Started!")
For Each address As Uri In myServiceHost.BaseAddresses
Console.WriteLine("Listening on " + address.ToString())
Next
Console.WriteLine("Click any key to close...")
Console.ReadKey()
myServiceHost.Close()
End Sub
ConsoleHost
的配置是最重要的!它分为三个部分,配置我们的服务将如何运行以及如何向外界公开。本文的目的不是详细描述WCF服务的配置方式,请参考MSDN上的WCF参考获取更多信息。我们服务配置中值得注意的是,它使用MTOM
作为消息编码和流作为传输模式。另请参阅maxReceivedMessageSize
属性。这定义了我们服务传输的消息的最大大小。由于我们正在传输文件,我们希望此属性有一个较大的值。
XML
<binding name ="FileTransferServicesBinding"
transferMode="Streamed"
messageEncoding="Mtom"
maxReceivedMessageSize="10067108864" >
</binding>
客户端
Client
项目是我们服务的一个示例消费者。您会注意到Client
项目包含一个名为Service References的文件夹。此文件夹包含Visual Studio通过右键单击Client
项目根目录并选择“添加服务引用”自动创建的一堆文件。此文件夹中的文件是我们文件传输服务在客户端的代理。Client
使用这些文件向服务器发送请求,从而隐藏了Web服务和SOAP协议的复杂性。
同样,如果您之前处理过流,您会发现TestForm文件中的内容非常简单,除了一个小部分,这也是上传与下载实现进度指示的区别。下载时,客户端控制整个过程。您可以在TestForm.cs中看到,下载是通过一个循环实现的,该循环逐块读取服务器流。因此,客户端知道已读取服务器流的哪个部分以及剩余多少。上传时,该循环位于服务器端。为了让客户端知道服务器读取了多少字节,它使用了StreamWithProgress
类,该类继承了System.IO.Stream
。该类的实例被传递给服务器,而不是原始文件流。由于此类重写了流的默认Read
方法(请参阅下面的代码),它可以向客户端报告上传过程的进度!
CS
public override
int Read(byte[] buffer, int offset,
int count)
{
int result = file.Read(buffer, offset, count);
bytesRead += result;
if (ProgressChanged != null)
ProgressChanged(this, new ProgressChangedEventArgs(bytesRead, length));
return result;
}
VB
Public Overloads Overrides Function Read(ByVal buffer As Byte(), _
ByVal offset As Integer, ByVal count As Integer) As Integer
Dim result As Integer = file.Read(buffer, offset, count)
bytesRead += result
RaiseEvent ProgressChanged(Me, New ProgressChangedEventArgs(_
bytesRead, m_length))
Return result
End Function
历史
- 更新于 2007/09/09:Buddhike提出了一个更优雅的服务器实现方法。
- 更新于 2007/10/24:代码现在也提供VB.NET版本。由Project Time and Cost的Lee Galloway转换。
- 更新于 2009/02/03
- 将项目升级到Visual Studio 2008(目标框架仍为.NET Framework 2.0)。
- 在客户端正确关闭读取流以完成下载。未能这样做会导致客户端在下载2到3次后抛出超时异常。