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

WCF流传输:通过HTTP上传/下载文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (58投票s)

2011年3月9日

CPOL

3分钟阅读

viewsIcon

614420

downloadIcon

34388

一个通过Web服务器从浏览器向WCF服务上传/下载大型文件的示例。

Wordley_ScreenShot.jpg

本文目的

我尝试过通过HTTP向WCF服务上传/下载大型文件,但是遇到了一些问题,我无法上传超过45KB的文件。我搜索了整个网络,但没有找到任何现成的示例代码/解决方案。通过参考网络和MSDN上的解释,我尝试了许多配置设置组合,最终成功地传输了大型文件(我在IE6上测试了高达1GB的文件)。

我想分享我的经验,以便支持其他人在这方面的努力,并邀请开发者社区提出评论。

解释

要使用“WCF服务+HTTP”传输大型文件,我们可以使用以下类型的绑定

  1. wsHttpBinding
  2. basicHttpBinding

在wsHttpBinding中,我们可以将transfermode属性设置为Buffered,但是对于大型文件使用这种方法有一个缺点,因为它需要在上传/下载之前将整个文件放入内存中,Web客户端和WCF服务主机都需要一个大的缓冲区。但是,这种方法对于安全传输小型文件非常有用。

在basicHTTPBinding中,我们可以将transfermode设置为Streamed,以便可以将文件分成块进行传输。我们必须确保额外的安全机制来传输这些块。本文档中未解释安全机制。

实现:WCF服务

创建一个新的“WCF服务”项目。创建一个名为TransferService的新服务。现在我们将看到一个接口文件“ITransferService”和一个类文件TransferService.csITransferService应该有两个方法,一个用于上传,一个用于下载。

WCF服务示例接口代码
[ServiceContract]
public interface ITransferService
{
    [OperationContract]
    RemoteFileInfo DownloadFile(DownloadRequest request);
 
    [OperationContract]
     void UploadFile(RemoteFileInfo request); 
}
[MessageContract]
public class DownloadRequest
{
    [MessageBodyMember]
    public string FileName;
}

[MessageContract]
public class RemoteFileInfo : IDisposable
{
    [MessageHeader(MustUnderstand = true)]
    public string FileName;

    [MessageHeader(MustUnderstand = true)]
    public long Length;

    [MessageBodyMember(Order = 1)]
    public System.IO.Stream FileByteStream;

    public void Dispose()
    { 
        if (FileByteStream != null)
        {
            FileByteStream.Close();
            FileByteStream = null;
        }
    }   
}
WCF服务示例接口实现代码
public RemoteFileInfo DownloadFile(DownloadRequest request)
{
    RemoteFileInfo result = new RemoteFileInfo();
    try
    {
        string filePath = System.IO.Path.Combine(@"c:\Uploadfiles", request.FileName);
        System.IO.FileInfo fileInfo = new System.IO.FileInfo(filePath); 

        // check if exists
        if (!fileInfo.Exists)
            throw new System.IO.FileNotFoundException("File not found", 
                                                      request.FileName);

        // open stream
        System.IO.FileStream stream = new System.IO.FileStream(filePath, 
                  System.IO.FileMode.Open, System.IO.FileAccess.Read);

            // return result 
            result.FileName = request.FileName;
            result.Length = fileInfo.Length;
            result.FileByteStream = stream;
        }
        catch (Exception ex)
        {

        }
        return result; 
    }
    public void UploadFile(RemoteFileInfo request)
    {
        FileStream targetStream = null;
        Stream sourceStream =  request.FileByteStream;

        string uploadFolder = @"C:\upload\";
         
        string filePath = Path.Combine(uploadFolder, request.FileName);

        using (targetStream = new FileStream(filePath, FileMode.Create, 
                              FileAccess.Write, FileShare.None))
        {
            //read from the input stream in 65000 byte chunks
            
            const int bufferLen = 65000;
            byte[] buffer = new byte[bufferLen];
            int count = 0;
            while ((count = sourceStream.Read(buffer, 0, bufferLen)) > 0)
            {
                // save to output stream
                targetStream.Write(buffer, 0, count);
            }
            targetStream.Close();
            sourceStream.Close();
        }

    }

WCF服务的“Web.Config”设置

以下设置对于传输大型数据至关重要

  • ReaderQuotas:我们必须设置最大大小(这取决于我们的具体需求)。在这里,我设置了最大值,根据MSDN/网络文档,我们可以传输高达2GB的数据。
  • <binding name="TransferService"
           maxReceivedMessageSize="2147483647"
           maxBufferSize="2147483647" transferMode="Streamed" >
                    
    <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" 
         maxArrayLength="2147483647" maxBytesPerRead="2147483647" 
         maxNameTableCharCount="2147483647"/>

    关于我的经验:我仅使用上述设置,并从浏览器上的WCF服务收到了“400错误请求”错误。

    此错误可能由于多种原因造成:原因可能是Web服务器和WCF服务之间的配置不匹配,或者在WCF服务中引发异常等。

  • BindingConfiguration:此属性在端点部分中默认不可用。当我添加此属性时,“400错误请求”异常消失了,上传和下载执行良好。
  • <endpoint address="" binding="basicHttpBinding" 
              bindingConfiguration="TransferService" 
              contract ="ITransferService">
    </endpoint>

    除了上述设置外,还应在web.config文件中添加HttpRuntime设置,如下所示

    <httpRuntime maxRequestLength="2097151" //Maxvalue
         useFullyQualifiedRedirectUrl="true"
         executionTimeout="14400"   />  //can be configured as per the requirement.

实现:Web服务器

创建一个新的“网站”项目。在网页上放置一个链接按钮和一个文件上传控件。创建另一个按钮来上传文件。在“服务引用”中添加WCF服务的引用,使用合适的名称,当前为FileTransferServiceReference

添加服务引用后,需要在web.config中进行以下更改

<binding name="BasicHttpBinding_ITransferService" closeTimeout="04:01:00"
     openTimeout="04:01:00" receiveTimeout="04:10:00" sendTimeout="04:01:00"
     allowCookies="false" bypassProxyOnLocal="false" 
     hostNameComparisonMode="StrongWildcard"
     maxBufferSize="2147483647" maxBufferPoolSize="2147483647" 
     maxReceivedMessageSize="2147483647"
     messageEncoding="Text" textEncoding="utf-8" 
     transferMode="Streamed"
     useDefaultWebProxy="true">
  <readerQuotas maxDepth="128" 
      maxStringContentLength="2147483647" maxArrayLength="2147483647"
      maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
  <security mode="None">
      <transport clientCredentialType="None" 
              proxyCredentialType="None" realm="" />
      <message clientCredentialType="UserName" algorithmSuite="Default" />
  </security>
</binding>

增加readerQuotas属性的值,并增加超时设置。

页面的代码隐藏如下

protected void LinkButton1_Click(object sender, EventArgs e)
{
    try
    {
        FileTransferServiceReference.ITransferService 
                    clientDownload = new TransferServiceClient();
        FileTransferServiceReference.DownloadRequest requestData = new DownloadRequest();

        FileTransferServiceReference.RemoteFileInfo fileInfo = new RemoteFileInfo();
        requestData.FileName = "codebase.zip";
 
        fileInfo = clientDownload.DownloadFile(requestData);

        Response.BufferOutput = false;   // to prevent buffering 
        byte[] buffer = new byte[6500];
        int bytesRead = 0;

        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.ClearHeaders();
        HttpContext.Current.Response.ContentType = "application/octet-stream";
        HttpContext.Current.Response.AddHeader("Content-Disposition", 
                    "attachment; filename=" + requestData.FileName);

        bytesRead = fileInfo.FileByteStream.Read(buffer, 0, buffer.Length);

        while (bytesRead > 0)
        {
            // Verify that the client is connected.
            if (Response.IsClientConnected)
            {

                Response.OutputStream.Write(buffer, 0, bytesRead);
                // Flush the data to the HTML output.
                Response.Flush();

                buffer = new byte[6500];
                bytesRead = fileInfo.FileByteStream.Read(buffer, 0, buffer.Length);
 
            }
            else
            {
                bytesRead = -1;
            }
         }
    }
    catch (Exception ex)
    {
        // Trap the error, if any.
        System.Web.HttpContext.Current.Response.Write("Error : " + ex.Message);
    }
    finally
    {
        Response.Flush();
        Response.Close();
        Response.End();
        System.Web.HttpContext.Current.Response.Close();
    }
}

protected void Button1_Click(object sender, EventArgs e)
{ 
    if (FileUpload1.HasFile)
    {
        System.IO.FileInfo fileInfo = 
               new System.IO.FileInfo(FileUpload1.PostedFile.FileName);
        FileTransferServiceReference.ITransferService clientUpload = 
               new FileTransferServiceReference.TransferServiceClient();
        FileTransferServiceReference.RemoteFileInfo 
               uploadRequestInfo = new RemoteFileInfo();

        using (System.IO.FileStream stream = 
               new System.IO.FileStream(FileUpload1.PostedFile.FileName, 
               System.IO.FileMode.Open, System.IO.FileAccess.Read))
        {
            uploadRequestInfo.FileName = FileUpload1.FileName;
            uploadRequestInfo.Length = fileInfo.Length;
            uploadRequestInfo.FileByteStream = stream;
            clientUpload.UploadFile(uploadRequestInfo);
            //clientUpload.UploadFile(stream);
        }
    }
}

现在构建两个IDE项目并执行Web服务器项目。但这仍然只适用于小于50KB的文件!如果选择上传大型文件,您将看到一个空白网页。

现在,您需要在system.web配置文件部分的httpRuntime部分中添加另一个属性MaxRequestLength,如下所示

<httpRuntime maxRequestLength="2097150"/>

就是这样!现在可以从IE6浏览器通过HTTP向WCF服务下载和上传大型文件(我测试了高达~1GB的文件)。

© . All rights reserved.