WCF流传输:通过HTTP上传/下载文件
一个通过Web服务器从浏览器向WCF服务上传/下载大型文件的示例。
本文目的
我尝试过通过HTTP向WCF服务上传/下载大型文件,但是遇到了一些问题,我无法上传超过45KB的文件。我搜索了整个网络,但没有找到任何现成的示例代码/解决方案。通过参考网络和MSDN上的解释,我尝试了许多配置设置组合,最终成功地传输了大型文件(我在IE6上测试了高达1GB的文件)。
我想分享我的经验,以便支持其他人在这方面的努力,并邀请开发者社区提出评论。
解释
要使用“WCF服务+HTTP”传输大型文件,我们可以使用以下类型的绑定
- wsHttpBinding
- basicHttpBinding
在wsHttpBinding中,我们可以将transfermode
属性设置为Buffered
,但是对于大型文件使用这种方法有一个缺点,因为它需要在上传/下载之前将整个文件放入内存中,Web客户端和WCF服务主机都需要一个大的缓冲区。但是,这种方法对于安全传输小型文件非常有用。
在basicHTTPBinding中,我们可以将transfermode
设置为Streamed
,以便可以将文件分成块进行传输。我们必须确保额外的安全机制来传输这些块。本文档中未解释安全机制。
实现:WCF服务
创建一个新的“WCF服务”项目。创建一个名为TransferService的新服务。现在我们将看到一个接口文件“ITransferService”和一个类文件TransferService.cs。ITransferService
应该有两个方法,一个用于上传,一个用于下载。
[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;
}
}
}
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的文件)。