WCF 支持上传、流式传输和缓冲两种方法。缓冲是默认模式,涉及缓冲整个文件并将其以一大块发送到服务器。这适用于中小型文件,但对于大文件来说往往太慢了。流式传输通过多个响应向服务器发送文件位,并具有许多好处,例如在流中断时能够恢复流。
显然,解决这个问题的适当方法是流式传输,但是,因为流式传输不是默认设置,我们需要做一些配置工作才能让 WCF 使用它。此答案特别适用于 WCF 服务,因为启用流式处理涉及修改 Web.config
因此,首先,假设您有一个接受流的公开方法,如下所示:
[OperationContract]
[WebInvoke(UriTemplate = "/Upload", Method = "POST")]
void Upload(Stream data);
我们需要告诉 WCF 为这个端点使用流式传输,为此我们需要一个允许流式传输的绑定,我们可以在 Web.config 中使用类似这样的东西:
<configuration>
...
<bindings>
<webHttpBinding>
<binding name="httpStreamingBinding" transferMode="Streamed" />
</webHttpBinding>
</bindings>
...
<services>
<service name="MyServiceNamespace.MyServiceName">
<endpoint address="" behaviorConfiguration="web" binding="webHttpBinding"
bindingConfiguration="httpStreamingBinding" name="UploadEndpoint"
contract="MyServiceNamespace.IMyServiceName" />
</service>
</services>
...
<behaviors>
...
<endpointBehaviors>
<behavior name="web">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</configuration>
这将创建一个端点,该端点指向包含 Upload 方法的类,并将其配置为使用流传输。但是我们仍然有一个问题,大文件传输需要时间并且Web服务器默认超时太低而无法传输大文件,我们需要修改更多的Web.config。
首先,我们需要像这样更改 httpStreamingBinding 的超时长度和最大接收消息大小(maxRecievedMessageSize 以字节为单位):
<binding name="httpStreamingBinding" maxReceivedMessageSize="4294967296"
transferMode="Streamed"
crossDomainScriptAccessEnabled="true"
openTimeout="00:01:00"
closeTimeout="00:01:00"
receiveTimeout="02:00:00"
sendTimeout="02:00:00"
/>
然后我们需要修改我们的 http 运行时以接受大文件,这里我选择了 4gb 作为最大值(maxRequestLength 以 kb 为单位):
<system.web>
...
<httpRuntime
executionTimeout="7200"
maxRequestLength="4194304" />
</system.web>
现在我们可以接收流数据,但还有更多工作要做。通常,大型 HTTP 上传是使用 multipart/form-data 内容类型完成的,这意味着您在流中收到的数据不仅是上传的文件,还有来自表单的附加数据。您可以自己手动解析这些数据,也可以使用现有的解析器。Lorenzo 在这个答案中提供了一个优秀的多部分数据解析器
我们还有另一个问题,使用 HTML 表单上传多部分数据很简单,但是如果我们想使用 javascript 怎么办?根据浏览器的不同,对上传流数据有不同级别的支持,但是jQuery 文件上传器插件为将文件上传到流数据服务提供了出色的支持。只需确保使用“files[]”作为多部分解析器的文件名,如下所示:
[OperationContract]
[WebInvoke(UriTemplate = "/Upload", Method = "POST")]
void Upload(Stream data)
{
var parser = new HttpMultipartParser(data, "files[]");
...
}
现在我们已经有了数据,我们需要一些方法来根据上传的文件类型来做一些逻辑。我通过更改基于 URL 参数的解析方法解决了这个问题,例如 /Upload/FormatOne 将使用 FormatOne 方法,而 /Upload/FormatTwo 将使用 FormatTwo。这是使用此方法完成的:
delegate void FileFormatHandler(Stream data);
[OperationContract]
[WebInvoke(UriTemplate = "/Upload/{fileType}", Method = "POST")]
void Upload(string fileType, Stream data)
{
var parser = new HttpMultipartParser(data, "files[]");
FileFormatHandler handler = selectHandler(fileType);
handler(data);
}
不幸的是,这种方法意味着该服务将不再使用基于 SOAP 的 WCF 调用机制,因为 Stream 应该是 SOAP 中的唯一参数,但是 Web 调用将不受此限制的影响。
我们现在有一个 WCF 服务,它使用流式接收大文件,并且能够根据用户使用的 URL 调用不同的解析方法。要上传数据,用户只需将一些信息发布到适当的 URL。例如,如果用户想要上传一个文件并让 MyFancyFormat 解析器对其进行解析,他们将 POST 到以下 URL:
http://myserver/MyService.svc/Upload/MyFancyFormat