2012-04-19 96 views
4

我有點困在這裏...獲取流媒體在IIS託管WCF服務工作

我的目標很簡單:我要揭露一個IIS託管(然後Windows Azure中)WCF服務,通過它我可以上傳文件,使用流媒體,以及添加一些關於我想要上傳的文件的META數據(文件名,MD5-hash所有常用的東西......),並能夠顯示有關上傳的準確進度信息。

首先我創建了一個派生類StreamWithProgress的FileStream,其中我已經覆蓋了方法引發一個事件與每個讀通過我通過進度信息繼承。

其次我創建使用MessageContracthttp://msdn.microsoft.com/en-us/library/ms730255.aspx)來包裝元數據的WCF服務和流對象到一個單一的SOAP信封。這項服務非常簡單,僅提供一種上傳方法。

我已經將所有的緩衝區大小,以接受大量的數據,按:

和的httpRuntime設置爲每:

的IIS \ ASP兼容性設置按:

和禁用配料按:

我創建了一個自託管服務通過它上傳成功。 然後我'升級'到IIS託管服務(在我的本地計算機上),它工作。 然後我創建了一個本地託管的Windows Azure服務,其中有一個工作的WCF卷軸,

儘管如此,沒有一個實例發生實際流式傳輸......所有這些實例都在發送數據之前對其進行了緩衝。

我遇到了這個問題,當我看到我的客戶端正在報告進度,但服務器在整個文件被緩衝之後纔開始寫入文件。

我的實際代碼如下。

任何想法\幫助? 任何將不勝感激...

謝謝!

服務器的web.config:

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

服務合同:

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); 
    } 
} 

實際的服務:

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(); 
     } 
    } 
} 

客戶端的app.config:

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

客戶端主代碼:

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()); 
     } 
    } 
} 

派生FileStream類(StreamWithProgress

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

- 更新:2012-04-20

後,我已經安裝了環 - 我使用RawCap跟蹤通信,並且看到數據實際上是流式傳輸的,但IIS服務器在調用之前緩存所有數據網絡方法!

根據這個帖子:

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

它是WCF繼承ASP.Net行爲......但是,他們在.NET 4.5談論修復此:|

如果有人有任何其他建議,它將是偉大的!

謝謝!

+0

如果您降低服務器端MaxBufferSize屬性,它會產生什麼影響嗎? – RichBower 2012-04-19 15:23:51

+0

嗨RichBower,我試過,謝謝...在你的帖子後,我去了所有的ReaderQuota和綁定設置...但無濟於事...... – mnemonic 2012-04-20 07:14:51

回答

3

經過一些嚴格的測試後,我看到數據實際上正在流式傳輸。我將[MessageContract]屬性應用到封裝器類 (根據http://msdn.microsoft.com/en-us/library/ms733742.aspx),並使我能夠發送一些關於該文件的額外元數據。使用 WireShark和RawCap很清楚,在讀取流時通過線路發送數據。

另一個困擾他的問題是,在實際調用上傳方法之前,正在流式傳輸的數據是緩衝的服務器端(使用IIS 7.5)! 這是一個值得關注的問題,但根據這個:http://social.msdn.microsoft.com/Forums/is/wcf/thread/cfe625b2-1890-471b-a4bd-94373daedd39, 修復應該在4.5版本的.Net

3

您正在使用Mtom和流式傳輸模式。這可能會導致問題。請嘗試刪除Mtom。其實,無論如何,Mtom是一個非常古老的標準。另外,在流式傳輸模式下使用SOAP服務時,我們只能有一個類型爲Stream的參數。我們不能使用像封裝器一樣的自定義類型。

構建文件上傳/下載服務的推薦解決方案是使用REST。在.NET平臺上構建REST服務的一種方法是使用ASP.NET Web API:http://www.asp.net/web-api。使用這個API,我們不需要處理流式傳輸模式。我們需要處理的是Range標題。這篇博文可能有幫助:http://blogs.msdn.com/b/codefx/archive/2012/02/23/more-about-rest-file-upload-download-service-with-asp-net-web-api-and-windows-phone-background-file-transfer.aspx。但是請注意,此API尚未發佈。如果您不想使用預發佈產品,則可以使用其他技術,例如,您可以將MVC控制器用作REST服務,或使用WCF REST服務,或者構建自定義http處理程序等。如果您想要要使用Stream,需要一個自定義流。我想建議您檢查http://blogs.msdn.com/b/james_osbornes_blog/archive/2011/06/10/streaming-with-wcf-part-1-custom-stream-implementation.aspx的樣本。

最好的問候,

Ming Xu。

+0

嗨,感謝您的答覆! 不幸的是使用REST不是我們的選擇,但感謝參考!我相信它最終會派上用場! :) – mnemonic 2012-04-24 14:05:41