Streaming in IIS Hosted WCF-Dienst zum Laufen bringen

Ich stecke hier irgendwie fest ...

Mein Ziel ist ganz einfach: Ich möchte einen von IIS gehosteten (und dann Windows Azure) WCF-Dienst bereitstellen, über den ich Dateien per Streaming hochladen und einige META-Daten zu der Datei hinzufügen kann, die ich hochladen möchte (Dateiname, MD5-Hash alle) das übliche Zeug ...) und um genaue Fortschrittsinformationen bezüglich des Uploads anzeigen zu können.

Zunächst habe ich eine abgeleitete Klasse erstelltStreamWithProgress was erbt vonDatenfluss, wo ich die überschrieben habeLesen Methode zum Auslösen eines Ereignisses bei jedem Lesevorgang, bei dem ich Fortschrittsinformationen weitergebe.

Zweitens habe ich einen WCF-Dienst mit einem erstelltMessageContract ( http://msdn.microsoft.com/en-us/library/ms730255.aspx ), um die META-Daten und das Stream-Objekt in einen einzigen SOAP-Umschlag zu packen. Dieser Service ist sehr einfach und bietet nur eine Methode zum Hochladen.

Ich habe alle Puffergrößen so eingestellt, dass große Datenmengen akzeptiert werden.

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 und

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

und die httpRuntime-Einstellungen wie folgt:

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

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

Die IIS \ ASP-Kompatibilitätseinstellungen lauten wie folgt:

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

WCF Streaming File Transfer unter .NET 4

Deaktivieren der Stapelverarbeitung wie folgt:

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

Ich habe eine erstelltSelbst gehosteter Service über die der Upload erfolgreich war. Dann habe ich es zu einem "upgegradet"IIS-gehosteter Dienst (auf meinem lokalen Rechner), der funktionierte. Dann habe ich eine erstelltlokal gehosteter Windows Azure-Dienst mit einer WCF-Webrolle, was hat funktioniert.

Der Haken dabei ist, dass in keinem der Fälle ein tatsächliches Streaming stattgefunden hat… Alle haben die Daten vor dem Senden gepuffert.

Ich bin auf dieses Problem gestoßen, als ich sah, dass mein Client den Fortschritt meldet, der Server jedoch erst mit dem Schreiben der Datei beginnt, nachdem die gesamte Datei gepuffert wurde.

Mein eigentlicher Code folgt.

Irgendwelche Ideen \ Hilfe? Alles wird sehr geschätzt ...

Vielen Dank!

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

Servicevertrag:

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

Tatsächlicher Service:

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

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

Client-Hauptcode:

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

Abgeleitete FileStream-Klasse (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>

- Aktualisierung: 20.04.2012

Nachdem ich einen Loopback-Adapter installiert hatte, verfolgte ich die Kommunikation mit RawCap und stellte fest, dass die Daten tatsächlich gestreamt werden, der IIS-Server jedoch alle Daten puffert, bevor ich die Webmethode aufrief!

Nach diesem Beitrag:

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

Es ist das ASP.Net-Verhalten, das WCF erbt ... In .Net 4.5 geht es jedoch um Fehlerbehebungen: |

Wenn jemand einen anderen Vorschlag hat, wird es großartig sein!

Vielen Dank!!

Antworten auf die Frage(2)

Ihre Antwort auf die Frage