Cómo hacer streaming en el servicio IIS Hosted WCF para trabajar

Estoy un poco atrapado aquí ...

Mi objetivo es bastante simple: quiero exponer un servicio WCF alojado en IIS (y luego Windows Azure) a través del cual puedo cargar archivos, usar transmisión y agregar algunos datos META sobre el archivo que quiero cargar (nombre de archivo, MD5-hash all las cosas habituales ...), y para poder mostrar información precisa sobre el progreso de la carga.

En primer lugar creé una clase derivada.StreamWithProgress que hereda deFileStream, donde he anulado elLeer Método para generar un evento con cada lectura a través del cual paso información de progreso.

En segundo lugar creé un servicio WCF usando unMessageContract ( http://msdn.microsoft.com/en-us/library/ms730255.aspx ) para envolver los datos META y el objeto de flujo en un único sobre SOAP. Este servicio es realmente simple, exponiendo un solo método de carga.

He establecido todos los tamaños de búfer para aceptar grandes cantidades de datos, según:

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

http://msdn.microsoft.com/en-us/library/ms733742.aspx y

http://msdn.microsoft.com/en-us/library/ms731325.aspx

y la configuración de httpRuntime según:

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

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

la configuración de compatibilidad IIS \ ASP según:

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

WCF Streaming File Transfer en .NET 4

Y deshabilitando los lotes según:

http://msdn.microsoft.com/en-us/library/system.servicemodel.icontextchannel.allowoutputbatching.aspx

He creado unservicio auto alojado a través del cual la carga tuvo éxito. Luego lo "actualicé" a unServicio alojado en IIS (en mi máquina local), que funcionó. Entonces creé unEl servicio de Windows Azure alojado localmente, con un rol web WCF, el cual funcionó.

Sin embargo, el problema es que en ninguno de los casos se realizó la transmisión real ... Todos ellos almacenaron en búfer los datos antes de enviarlos.

Me encontré con este problema, cuando vi que mi cliente estaba informando sobre el progreso, pero el servidor no comienza a escribir el archivo hasta después de que todo el archivo se almacenó en búfer.

Mi código actual sigue.

¿Alguna idea \ ayuda? Cualquier cosa será muy apreciada ...

¡Gracias!

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 servicios:

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

Servicio actual:

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

Clase FileStream derivada (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>

- Actualización: 2012-04-20

Después de que instalé un adaptador de bucle, rastreé las comunicaciones con RawCap y vi que los datos se transmiten en realidad, ¡pero que el servidor IIS almacena en un búfer todos los datos antes de invocar el método web!

Según este post:

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

Es el comportamiento de ASP.Net que WCF hereda ... Pero están hablando de soluciones para esto en .Net 4.5: |

Si alguien tiene alguna otra sugerencia será genial!

¡¡Gracias!!

Respuestas a la pregunta(2)

Su respuesta a la pregunta