C# 使用WCF上载大文件有困难

C# 使用WCF上载大文件有困难,c#,wcf,iis,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 很明显,我们需要使用某种流式或分块

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

我的项目要求是通过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
很明显,我们需要使用某种流式或分块文件传输

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

默认情况下,此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与流对象和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>
对这个问题的解释描述得很好

这样想吧。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">
(我记得更新了客户端代理)

所以,这就是它对我的意义。。。。救命啊非常详细的问题:)

上一条错误消息是找不到404文件。通常情况下,这是因为文件丢失或站点关闭。看看这个,排除这个可能性

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

根据上次更改,您已关闭基于消息的加密。此更改需要在客户端和服务器上进行。如果一方正在加密,而另一方不希望消息被加密,那么它会变得混乱。

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


如果不是唯一的解决方法,那就是流式传输文件,或者将文件分成若干部分。

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旨在帮助传输大文件(对于小文件传输来说不是那么好)

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

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


我希望这会有所帮助。

您没有答案,但流媒体可以与BasicHttpBinding和消息契约一起工作。在那个阶段没有看到你的.config,我无法告诉你是什么导致了你的400…这不是一个真正的答案,但当我在绑定和端点配置方面遇到问题时,我会使用VS2010中的一个工具来帮助减少XML的混乱。转到“工具”->“WCF服务配置编辑器”。您可以使用它来编辑客户端和主机配置。正如在回答中已经提到的,两者都需要匹配,否则您将得到端点未找到错误或安全错误。顺便说一句,好详细的问题!也没有回答。如何为NetCPBinding做到这一点?我不想问一个愚蠢的问题。。。有什么想法吗?没有。在我的例子中,我碰巧知道这些文件的大小大约为300MB。但假设它被压缩到50MB的文件。还是太大了。
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.
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;
}
[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;
    }
}