0

我们有一个 Web 服务,它需要为客户端提供上传大文件的能力,然后由客户端选择的可用机制之一对其进行解析。该服务的典型用法将涉及上传一个大文件并指定要使用的解析机制,然后服务器将解析上传的数据并将其存储在数据库中。

例如,我们有许多包含有用信息的 excel 文件。不幸的是,有多种不同的格式,我们需要能够解析它们。我们当前的解决方案要求当用户上传一个 excel 文件时,他们还要从预定义的解析方法列表中进行选择。

目前,该服务提供了一种具有以下签名的方法:

[OperationContract, WebInvoke(UriTemplate = "/DoUpload/{fileType}")]
void DoUpload(string fileType, Stream fileData);

我们需要某种方式来允许基于 Web 的客户端(利用 jQuery)调用 DoUpload,使得 fileType 是客户端选择的字符串,而 fileData 是从客户端上传的文件。

理想情况下,这种机制还允许我们公开来自同一服务的有效文件类型列表。

其他信息:

  • 将来需要添加其他方法,包括其他文件类型
  • 该解决方案应为服务器利用 C# 技术,但不一定需要是 WCF
  • 上传的文件存储在服务器上,处理后丢弃。
4

1 回答 1

1

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
于 2012-12-19T01:13:52.690 回答