Obtendo o streaming no serviço WCF hospedado no IIS para funcionar

Eu estou meio preso aqui ...

Meu objetivo é bastante simples: Desejo expor um serviço WCF hospedado no IIS (e, em seguida, Windows Azure) através do qual posso carregar arquivos, usando streaming e adicionar alguns dados META sobre o arquivo que deseja carregar (nome do arquivo, MD5-hash todos as coisas usuais ...), e para poder exibir informações precisas sobre o progresso do upload.

Primeiro de tudo eu criei uma classe derivadaStreamWithProgress que herda deFileStream, onde eu anulei oLer método para levantar um evento com cada leitura através da qual eu passo informações de progresso.

Em segundo lugar eu criei um serviço WCF usando umMessageContract ( http://msdn.microsoft.com/pt-br/library/ms730255.aspx ) para envolver os dados META e o objeto stream em um único envelope SOAP. Este serviço é realmente simples, expondo apenas um único método para upload.

Eu configurei todos os tamanhos de buffer para aceitar grandes quantidades de dados, conforme:

http://smehrozalam.wordpress.com/2009/01/29/retrieving-huge-amount-of-data-from-wcf-service-in-silverlight-application/

http://msdn.microsoft.com/pt-br/library/ms733742.aspx e

http://msdn.microsoft.com/pt-br/library/ms731325.aspx

e as configurações httpRuntime como por:

http://msdn.microsoft.com/en-us/library/e1f13641(v=vs.71).aspx e

http://kjellsj.blogspot.com/2007/02/wcf-streaming-upload-files-over-http.html

as configurações de compatibilidade IIS \ ASP conforme:

http://weblogs.asp.net/jclarknet/archive/2008/02/14/wcf-streaming-issue-under-iis.aspx e

Transferência de arquivos em fluxo contínuo do WCF no .NET 4

E desativando os lotes conforme:

http://msdn.microsoft.com/pt-br/library/system.servicemodel.icontextchannel.allowoutputbatching.aspx

Eu criei umserviço auto hospedado através do qual o upload foi bem-sucedido. Então eu atualizei para umServiço hospedado pelo IIS (na minha máquina local), que funcionou. Então eu criei umserviço do Windows Azure hospedado localmente, com uma função Web do WCF, que funcionou.

O problema é que em nenhuma das instâncias a transmissão real ocorreu ... Todos eles armazenaram os dados antes de enviá-los.

Eu me deparei com esse problema, quando vi que meu cliente estava relatando o progresso, mas o servidor não começou a gravar o arquivo até que todo o arquivo fosse armazenado em buffer.

Meu código atual segue.

Alguma idéia \ ajuda? Qualquer coisa será muito apreciada…

Obrigado!

Servidor web.config:

<code><?xml version="1.0"?>
<configuration>
    <system.serviceModel>

        <bindings>
            <basicHttpBinding>
                <binding name="uploadBasicHttpBinding" 
                 maxReceivedMessageSize="2147483647" 
                 transferMode="Streamed" 
                 messageEncoding="Mtom"
                 maxBufferPoolSize="2147483647"
                 maxBufferSize="2147483647">
                 <readerQuotas maxArrayLength="2147483647" 
                                maxBytesPerRead="2147483647" 
                                maxDepth="2147483647" 
                                maxNameTableCharCount="2147483647" 
                                maxStringContentLength="2147483647"/>
                </binding>
            </basicHttpBinding>
        </bindings>

            <behaviors>
                <serviceBehaviors>
                    <behavior name="defaultBehavior">
                        <serviceMetadata httpGetEnabled="true"/>
                        <serviceDebug includeExceptionDetailInFaults="false"/>
                        <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
                    </behavior>
                </serviceBehaviors>
            </behaviors>

        <!-- Add this for BufferOutput setting -->
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true"/>

        <services>
            <service name="WcfService1.Service1" behaviorConfiguration="defaultBehavior">           
                <endpoint binding="basicHttpBinding" contract="WcfService1.IService1" bindingConfiguration="uploadBasicHttpBinding"/>
                <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
            </service>
        </services>

    </system.serviceModel>

    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
    </system.webServer>

    <system.web>
        <compilation debug="true"/>
    <httpRuntime maxRequestLength="2147483647" />
    </system.web>

</configuration>
</code>

Contrato de serviço:

<code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.IO;

namespace WcfService1
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract(IsOneWay=true)]
        void UploadStream(Encapsulator data);
    }
}
</code>

Serviço real:

<code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

using System.IO;
using System.Web;
using System.ServiceModel.Activation;

namespace WcfService1
{
    [MessageContract]
    public class Encapsulator
    {
        [MessageHeader(MustUnderstand = true)]
        public string fileName;
        [MessageBodyMember(Order = 1)]
        public Stream requestStream;
    }

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class Service1 : IService1
    {
        public Service1()
        {
            HttpContext context = HttpContext.Current;

            if (context != null)
            {
                context.Response.BufferOutput = false;
            }
        }

        public void UploadStream(Encapsulator data)
        {
            const int BUFFER_SIZE = 1024;

            int bytesRead = 0;

            byte[] dataRead = new byte[BUFFER_SIZE];

            string filePath = Path.Combine(@"C:\MiscTestFolder", data.fileName);

            string logPath = Path.Combine(@"C:\MiscTestFolder", string.Concat(data.fileName, ".log"));

            bytesRead = data.requestStream.Read(dataRead, 0, BUFFER_SIZE);

            StreamWriter logStreamWriter = new StreamWriter(logPath);

            using (System.IO.FileStream fileStream = new System.IO.FileStream(filePath, FileMode.Create))
            {
                while (bytesRead > 0)
                {
                    fileStream.Write(dataRead, 0, bytesRead);
                    fileStream.Flush();

                    logStreamWriter.WriteLine("Flushed {0} bytes", bytesRead.ToString());
                    logStreamWriter.Flush();

                    bytesRead = data.requestStream.Read(dataRead, 0, BUFFER_SIZE);
                }

                fileStream.Close();
            }

            logStreamWriter.Close();
        }
    }
}
</code>

Cliente app.config:

<code><?xml version="1.0"?>
<configuration>
    <system.serviceModel>

        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IService1" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Mtom" textEncoding="utf-8" transferMode="Streamed"
                    useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>

        <client>
            <endpoint address="http://localhost/WcfService1/Service1.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
                contract="UploadService.IService1" name="BasicHttpBinding_IService1" />
        </client>

    </system.serviceModel>

    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    </startup>
</configuration>
</code>

Código principal do cliente:

<code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using CustomFileUploaderTester.UploadService;
using System.ServiceModel;
using System.IO;

namespace CustomFileUploaderTester
{
    class Program
    {
        private static long bytesRead = 0;

        static void Main(string[] args)
        {
            Service1Client client = new Service1Client();

            using (StreamWithProgress fstream = new StreamWithProgress(@"C:\BladieBla\someFile.wmv", FileMode.Open))
            {
                client.InnerChannel.AllowOutputBatching = false;

                fstream.ProgressChange += new EventHandler<StreamReadProgress>(fstream_ProgressChange);

                client.UploadStream("someFile.wmv", fstream);

                fstream.Close();
            }

            Console.ReadKey();
        }

        static void fstream_ProgressChange(object sender, StreamReadProgress e)
        {
            bytesRead += e.BytesRead;

            Console.WriteLine(bytesRead.ToString());
        }
    }
}
</code>

Classe derivada de FileStream (StreamWithProgress)

<code>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace CustomFileUploaderTester
{
    public class StreamReadProgress : EventArgs
    {
        #region Public Properties

        public long BytesRead
        {
            get;
            set;
        }

        public long Length
        {
            get;
            set;
        }

        #endregion

        #region Constructor

        public StreamReadProgress(long bytesRead, long fileLength)
            : base()
        {
            this.BytesRead = bytesRead;

            this.Length = fileLength;
        }

        #endregion
    }

    public sealed class StreamWithProgress : FileStream
    {
        #region Public Events

        public event EventHandler<StreamReadProgress> ProgressChange;

        #endregion

        #region Constructor

        public StreamWithProgress(string filePath, FileMode fileMode)
            : base(filePath, fileMode)
        {
        }

        #endregion

        #region Overrides

        public override int Read(byte[] array, int offset, int count)
        {
            int bytesRead = base.Read(array, offset, count);

            this.RaiseProgressChanged(bytesRead);

            return bytesRead;
        }

        #endregion

        #region Private Worker Methods

        private void RaiseProgressChanged(long bytesRead)
        {
            EventHandler<StreamReadProgress> progressChange = this.ProgressChange;

            if (progressChange != null)
            {
                progressChange(this, new StreamReadProgress(bytesRead, this.Length));
            }
        }


        #endregion
    }
}
</code>

- Atualização: 2012-04-20

Depois de instalar um adaptador de loopback, rastreei as comunicações com o RawCap e vi que os dados são realmente transmitidos, mas o servidor IIS está armazenando em buffer todos os dados antes de invocar o método da web!

De acordo com este post:

http://social.msdn.microsoft.com/Forums/is/wcf/thread/cfe625b2-1890-471b-a4bd-94373daedd39

é comportamento ASP.Net que WCF herda ... Mas eles estão falando sobre correções para isso no .Net 4.5: |

Se alguém tiver alguma outra sugestão, será ótimo!

Obrigado!!

questionAnswers(2)

yourAnswerToTheQuestion