Marc
Marc

Reputation: 13194

Uploading files above 50KB via WCF service fails

I am trying to get a file upload/download via a WCF service working. The transfer protocol is HTTP. I have set the binding up to user Streaming as transfer mode and have tried for days now to get it working properly, without success. I have managed to get it working for small files. When uploading large files, the file is created on the server, a number of bytes are written and then the transaction fails.

The service is hosted in a Windows Azure WebRole environment scaled to a degree which must definitely be sufficient for the task.

When inspecting the trace log with (e2e-file) with SvcTraceViewer.exe, the problem seems to be this:

An exception has been thrown when reading the stream.

with the call stack:

System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Read(Byte[] buffer, Int32 offset, Int32 count)
DataService.TransferService.UploadFile(RemoteFileInfo request)
SyncInvokeUploadImage(Object , Object[] , Object[] )
System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

The service contract looks like this:

[ServiceContract]
public interface ITransferService
{
    [OperationContract]
    RemoteFileInfo DownloadImage(DownloadRequest request);

    [OperationContract(IsOneWay = true)]
    void UploadImage(RemoteFileInfo request);
}

[MessageContract]
public class DownloadRequest
{
    [MessageBodyMember]
    public string FileName;
}

[MessageContract]
public class RemoteFileInfo : IDisposable
{
    [MessageHeader(MustUnderstand = true)] 
    public string FileName;

    [MessageHeader(MustUnderstand = true)] 
    public long Length;

    [MessageBodyMember(Order = 1)] 
    public System.IO.Stream FileByteStream;

    public void Dispose()
    {
        if (FileByteStream != null)
        {
            FileByteStream.Close();
            FileByteStream = null;
        }
    }
}

Here is the service implementation:

public class TransferService : ITransferService
{
    public RemoteFileInfo DownloadImage(DownloadRequest request)
    {
        return DownloadFile(request);            
    }

    public void UploadImage(RemoteFileInfo request)
    {            
        UploadFile(request);
    }

    public RemoteFileInfo DownloadFile(DownloadRequest request)
    {
        var result = new RemoteFileInfo();
        try
        {
            var filePath = Path.Combine(RoleEnvironment.GetLocalResource("TempStorage").RootPath, request.FileName);
            var fileInfo = new FileInfo(filePath);

            if (!fileInfo.Exists)
                throw new FileNotFoundException("File not found", request.FileName);

            var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

            result.FileName = request.FileName;
            result.Length = fileInfo.Length;
            result.FileByteStream = stream;
        }
        catch (Exception ex)
        {
        }
        return result;
    }

    public void UploadFile(RemoteFileInfo request)
    {
        FileStream targetStream;
        var sourceStream = request.FileByteStream;

        var filePath = Path.Combine(RoleEnvironment.GetLocalResource("TempStorage").RootPath, request.FileName);

        using (targetStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            const int bufferLen = 65000;
            var buffer = new byte[bufferLen];
            var count = 0;
            while ((count = sourceStream.Read(buffer, 0, bufferLen)) > 0)
            {
                targetStream.Write(buffer, 0, count);
            }
            targetStream.Close();
            sourceStream.Close();
        }
    }
}

I've set the web.config of the WCF service up like this to enable streaming, increase the relevant size limitations (or at least the ones I know of) and the timeouts:

<?xml version="1.0"?>
<configuration>
  <system.diagnostics>
    <trace autoflush="true">
        <listeners>
          <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
            name="AzureDiagnostics">
            <filter type="" />
          </add>
        </listeners>
    </trace>
    <sources>
      <source name="System.ServiceModel"
              switchValue="Information, ActivityTracing"
              propagateActivity="true">
        <listeners>
          <add name="sdt"
              type="System.Diagnostics.XmlWriterTraceListener"
              initializeData= "log.e2e" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>
  <system.web>
    <customErrors mode="Off" />
    <compilation debug="true" targetFramework="4.0" />
    <httpRuntime maxRequestLength="2097151" useFullyQualifiedRedirectUrl="true" executionTimeout="14400"   />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

    <bindings>
      <basicHttpBinding>
        <binding closeTimeout="00:01:00" 
                 openTimeout="00:01:00" 
                 receiveTimeout="00:10:00"
                 sendTimeout="00:10:00" 
                 maxReceivedMessageSize="2147483647"
                 maxBufferSize="2147483647" 
                 transferMode="Streamed" >
          <readerQuotas maxDepth="2147483647" 
                        maxStringContentLength="2147483647"
                        maxArrayLength="2147483647"
                        maxBytesPerRead="2147483647"
                        maxNameTableCharCount="2147483647"/>
        </binding>
      </basicHttpBinding>
    </bindings>

  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>

The client side is a WPF application. The calling code is this:

private void UploadImage(byte[] data, Guid guid)
{
    using (var transferService = new TransferServiceClient())
    using (var stream = new MemoryStream(data))
    {
        var uploadRequestInfo = new RemoteFileInfo();
        uploadRequestInfo.FileName = guid.ToString();
        uploadRequestInfo.Length = data.Length;
        uploadRequestInfo.FileByteStream = stream;
        transferService.UploadImage(uploadRequestInfo);
    }
}

private byte[] DownloadImage(Guid guid)
{
    using (var transferService = new TransferServiceClient())
    {
        try
        {
            var request = new DownloadRequest(guid.ToString());
            var iconFile = transferService.DownloadImage(request);
            var data = ByteArrayOperations.FromStream(iconFile.FileByteStream);
            return data;
        }
        catch (Exception)
        {
            return null;
        }
    }
}

and - finally - the client side app.config:

<?xml version="1.0"?>
<configuration>

  <configSections>      

  <system.web>
    <httpRuntime maxRequestLength="2097150"/>
  </system.web>

  <system.serviceModel>
    <bindings>
      <basicHttpBinding>           
        <binding name="BasicHttpBinding_ITransferService" 
                 closeTimeout="04:01:00"
                 openTimeout="04:01:00" 
                 receiveTimeout="04:10:00" 
                 sendTimeout="04:01:00"
                 allowCookies="false" 
                 bypassProxyOnLocal="false"
                 hostNameComparisonMode="StrongWildcard"
                 maxBufferSize="2147483647" 
                 maxBufferPoolSize="2147483647"
                 maxReceivedMessageSize="2147483647"
                 messageEncoding="Text" 
                 textEncoding="utf-8"
                 transferMode="Streamed"
                 useDefaultWebProxy="true">
          <readerQuotas maxDepth="128"
                        maxStringContentLength="2147483647" 
                        maxArrayLength="2147483647"
                        maxBytesPerRead="2147483647" 
                        maxNameTableCharCount="2147483647" />
          <security mode="None">
            <transport clientCredentialType="None"
                    proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>

      <endpoint address="xxx/TransferService.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ITransferService"
                contract="Transfer.ITransferService" name="BasicHttpBinding_ITransferService" />
    </client>
  </system.serviceModel>
</configuration>

Note that out of desparation I've added this section:

<system.web>
  <httpRuntime maxRequestLength="2097150"/>
</system.web>

without any effect.

In the error log there is one more exception being thrown directly after starting up the service. I neither know what it means nor whether it is related to the problem at all:

System.ServiceModel.ProtocolException

Content Type multipart/related; type="application/xop+xml";start="<http://tempuri.org/0&gt;";boundary="uuid:b230e809-500b-4217-a08e-32ff49e13bac+id=5";start-info="text/xml" was sent to a service expecting text/xml; charset=utf-8. The client and service bindings may be mismatched.

I've spent days getting this to work and really don't know what else I can try. I would be really really happy if somebody could have a look at the configs and maybe give a hint what else to try. So, the question is:

What could be the cause for the transfer to fail and what can I do to solve this?

I would also be happy for further advice on

What to do to find the root problem here, if it cannot be identified based on the information given in the question?

Appendix: I don't think the client side exception is of any use but I would like to include it in the question to make it easier for others with the same problem to find the answers to come (hopefully):

A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll

Additional information: An existing connection was forcibly closed by the remote host

Upvotes: 1

Views: 479

Answers (1)

Derek W
Derek W

Reputation: 10026

I do not see where the service binding is being specified in the serivce config file. I suspect that the configuration values are not being read and this to be the cause of why.

So I propose specifying the name of the binding in the service config file as BasicHttpBinding_ITransferService (same name used in the client config) and then adding the following under the system.serviceModel node of the configuration file:

<services>
  <service name="TransferService" >
    <endpoint 
        binding="basicHttpBinding"
        bindingConfiguration="BasicHttpBinding_ITransferService"
        contract="Transfer.ITransferService" />
  </service>
</services>

Upvotes: 1

Related Questions