65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (63投票s)

2007年9月6日

CPOL

5分钟阅读

viewsIcon

535403

downloadIcon

14631

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

Screenshot - WCF_FileTransfer_Progress.png

引言

本文探讨了使用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类中的filenamelength带有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次后抛出超时异常。
© . All rights reserved.