Есть много других подобных вопросов по SO об этом. К сожалению, многие, похоже, по очереди обманывают друг друга. Надеюсь, что этот поможет другим и поставит другие вопросы.
Мое требование к проекту заключается в том, чтобы загружать файлы 250 МБ через IIS в серверную службу WCF, размещенную в IIS. Я создал некоторые модульные тесты для бэкэнда WCF-сервиса, размещенного в IIS. Это:
1) Upload 1MB File
2) Upload 5MB File
3) Upload 10MB file
4) Upload 20MB File
5) Upload 200MB File
С самого начала, возможно, очевидно, что нам нужно использовать какую-то передачу потокового или пакетного файла. Я использовал этот образец.
В примере описывается метод, который использует объект .NET Stream. Побочным эффектом использования объекта потока является то, что вы должны использовать Контракты сообщений. Этого недостаточно, чтобы поместить Stream в список параметров вашей функции. Поэтому мы делаем это.
По умолчанию web.config для этой службы WCF довольно скудный. И ничего не работает:
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 файл теперь проходит без проблем.
Чтобы получить файлы размером более 4 МБ, вы должны настроить параметр в файле web.config в IIS (на стороне сервера вашей службы WCF) В этой статье от Microsoft объясняется, что это за настройка. Например, если вы установите ее на 8192, вы сможете загрузить файл 5 МБ, но не больше.
<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.
Объяснение этой проблемы описано очень хорошо, на этом плакате.
Подумайте об этом так. Файл 200 МБ никогда не делал его из клиента. Он должен быть полностью загружен клиентом, зашифрован и затем передан на сервер.
Когда вы используете 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.
(я не забыл обновить клиентский прокси)
И так, что там, где это для меня.... Помогите!