难以使用WCF上传大文件

时间:2011-11-13 17:09:39

标签: c# wcf iis

关于此,还有许多其他类似的问题。不幸的是,许多人似乎在某些方面相互欺骗。我希望这个会帮助别人并解决其他问题。

我的项目要求是通过IIS将250MB文件上传到IIS中托管的后端WCF服务。我为IIS中托管的后端WCF服务创建了一些单元测试。他们是:

1) Upload 1MB File
2) Upload 5MB File
3) Upload 10MB file
4) Upload 20MB File
5) Upload 200MB File

马上就可以了,我们需要使用某种流媒体或分块文件传输。 I used this sample

该示例描述了一种使用.NET Stream对象的方法。使用流对象的一个​​副作用是,您必须使用Message contract。将Stream放在函数的参数列表中是不够的。所以我们这样做。

默认情况下,此WCF服务的web.config非常精简。没有任何作用:

System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.

经过大量的搜索和实验,很明显BasicHttpBinding与Stream对象和MessageContract的这种组合不兼容。 我们必须切换到WSHttpBinding

为此,服务器的web.config在以下部分稍微复杂一些:

<system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior>
                    <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
                    <serviceMetadata httpGetEnabled="true"/>
                    <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
                    <serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="true"/>
                </behavior>
                <behavior name="FileServiceBehavior">
                    <serviceMetadata httpGetEnabled="true"/>
                    <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                    <serviceThrottling maxConcurrentCalls="500" maxConcurrentSessions="500" maxConcurrentInstances="500"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
        <bindings>
            <wsHttpBinding>
                <binding name="FileServiceBinding" closeTimeout="10:01:00"
                  maxBufferPoolSize="104857600"
                  maxReceivedMessageSize="104857600" openTimeout="10:01:00"
                  receiveTimeout="10:10:00" sendTimeout="10:01:00"
                  messageEncoding="Mtom">
                    <readerQuotas maxDepth="104857600" maxStringContentLength="104857600"
                                  maxArrayLength="104857600" maxBytesPerRead="104857600"
                                  maxNameTableCharCount="104857600" />
                </binding>
            </wsHttpBinding>
        </bindings>
        <services>
            <service behaviorConfiguration="FileServiceBehavior" name="OMS.Service.FileService">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="FileServiceBinding" contract="OMS.Service.IFileService"></endpoint>
            </service>
        </services>
    </system.serviceModel>

没有任何工作可言,1MB文件现在正在通过,没有任何问题。

要获取大于4MB的文件,必须调整IIS中的web.config中的设置(WCF服务的服务器端)This article from Microsoft explains what that setting is.例如,如果将其设置为8192,然后你就可以上传5MB文件,但不能上传任何更大的文件。

<httpRuntime maxRequestLength="8192" />

我设置了一些淫秽的测试 - 2147483647. 前4个文件通过此门。

由于下一个原因,200MB没有机会进入这个大门:

System.InsufficientMemoryException: Failed to allocate a managed memory buffer of 279620368 bytes. The amount of available memory may be low. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.

很好地描述了这个问题的解释,by this poster

这样想。 200MB的文件永远不会脱离客户端。它必须由客户端完全加载,加密然后传输到服务器。

当您使用Visual Studio 2010为服务生成代理类时,它会将一些内容放入您的app.config中。对我来说,它看起来像这样:

<binding 
 name="Binding_IFileService" closeTimeout="00:01:00"
 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
 bypassProxyOnLocal="false" transactionFlow="false" 
 hostNameComparisonMode="StrongWildcard"
 maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Mtom"
 textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
     maxBytesPerRead="4096" maxNameTableCharCount="16384" />
    <reliableSession ordered="true" inactivityTimeout="00:10:00"
         enabled="false" />
    <security mode="Message">
        <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
        <message clientCredentialType="Windows" negotiateServiceCredential="true" />
    </security>
</binding>

密钥是安全模式。默认设置为“message”。该值由服务器上设置的任何值拾取。默认情况下,您的服务器正在使用消息级别安全性。

如果你试图在服务器上强制它像这样:

 <security mode="None">

您收到此错误:

System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:8080/oms/FileService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.

(我确实记得更新客户端代理)

所以,这就是它代表我的地方......帮助!

3 个答案:

答案 0 :(得分:2)

除非您实现流式传输,否则WCF通常不能很好地处理大型文件传输,实际上可以使用BasicHttpBindings来实现。

对于我的项目,我有一个自定义主机工厂,可以创建服务主机:

protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
    ServiceHost host = new ServiceHost(serviceType, baseAddresses);

    ContractDescription contract = ContractDescription.GetContract(serviceType);

    BasicHttpBinding binding = new BasicHttpBinding();
    binding.OpenTimeout = TimeSpan.FromMinutes(1);
    binding.ReceiveTimeout = TimeSpan.FromMinutes(1);
    binding.SendTimeout = TimeSpan.FromHours(1);
    binding.TransferMode = TransferMode.StreamedResponse;
    binding.MessageEncoding = WSMessageEncoding.Mtom;

    ServiceEndpoint streaming = new ServiceEndpoint(contract, binding, new EndpointAddress(baseAddresses[0] + "/STREAMING"));

    host.AddServiceEndpoint(streaming);

    return host;
}

您需要在您的情况下使用StreamedRequest。

StreamingService的实现:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single, AddressFilterMode = AddressFilterMode.Any)]
public class FileStreamingService : IFileStreamingV1
{
    Stream IFileStreamingV1.GetFileStream(string downloadFileLocation)
    {
        if (!File.Exists(downloadFileLocation))
        {
            throw new FaultException("The file could not be found");
        }

        FileStream stream = File.OpenRead(downloadFileLocation);
        return stream;
    }
}

我没有在服务本身中指定最大缓冲区大小,但在我的情况下,客户端应用程序将下载限制为每秒5mb。在您的情况下,服务本身将需要设置油门行为。这并没有解决你如何告诉服务文件中有多少字节才能正确传输它的问题,但它应该给你一个开始。

您还应该注意在主机配置中使用MTOM。 MTOM旨在帮助传输大型文件(对于小型传输而言不是那么好)。

我没有客户端行为的示例,但您的服务应该从上传流的缓冲区中读取字节数并将它们保存到文件,直到没有任何内容流式传输。虽然内存很便宜但我不建议在文件的内存副本中存储,特别是在200mb。

您还应该知道,根据您的网络托管平台(IIS,Apache等),您可能还会受限于在给定时间可以传输的数据量。但是,配置更改通常可以解决任何托管问题。

我希望这会有所帮助。

答案 1 :(得分:0)

尼斯详细问题:)

您的上一条错误消息是找不到404文件。通常这是文件丢失或网站关闭。检查这一点只是为了排除这一点。

  • 尝试并浏览到您的svc文件
  • 测试它是否仍适用于较小的文件。

根据上次更改,您已关闭基于邮件的加密。此更改需要在客户端和服务器上完成。如果一方正在加密而另一方不期望该消息被加密,则会感到困惑。

答案 2 :(得分:0)

可以压缩文件吗?我知道这不是一个解决方案,但如果您知道将支持的最大大小,它会有所帮助。

如果不是唯一的方法是流式传输文件,或分成几部分。