c# - 难以使用 WCF 上传大文件

标签 c# wcf iis

SO 上还有许多其他类似的问题。不幸的是,许多人似乎在某些方面互相欺骗。我希望这篇文章能帮助其他人并解决其他问题。

我的项目要求是通过 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

马上,很明显我们需要使用某种流式传输或分 block 文件传输。 I used this sample .

该示例描述了一种使用 .NET Stream 对象的方法。使用流对象的一个​​副作用是您必须使用消息契约。将 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(WCF 服务的服务器端)的 web.config 中调整设置 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>

关键是安全模式。默认设置为“消息”。该值由服务器上设置的任何内容获取。默认情况下,您的服务器使用消息级安全。

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

 <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.

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

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

最佳答案

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;
    }
}

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

您还应该注意 MTOM 的使用在主机配置中。 MTOM 旨在帮助传输大文件(不适合小文件传输)。

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

您还应注意,根据您的 Web 托管平台(IIS、Apache 等),您可能还会限制在给定时间可以传输的数据量。但是,更改配置通常可以解决任何托管问题。

希望对您有所帮助。

关于c# - 难以使用 WCF 上传大文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8113211/

相关文章:

c# - Entity Framework 中的反向属性和外键有什么区别?

c# - WPF 控件初始化的顺序 - IsEnabled

IIS 7.5 静态内容压缩(不一致)

c# - C#从基类访问派生类的属性

c# - Telerik Reporting 在本地生成 pdf,但不在 Azure 上生成

wcf - 502 Bad gateway WCF 没有响应

wcf - WCF 服务与 MVC WebAPI 的性能问题

wcf - 此 BasicHttpBinding 的 CustomBinding?

c# - 如何确保使用 ASP.NET MVC5 和 IIS 7.5 express 刷新 CSS 文件

svn - SVN分支和“IIS Url Already Mapped to Different Location”