使 IIS 托管的 WCF 服务中的流式处理正常工作

本文关键字:常工作 处理 工作 IIS WCF 服务 | 更新日期: 2023-09-27 18:31:41

我有点被困在这里...

我的目标很简单:我想公开一个IIS托管的(然后是Windows Azure)WCF服务,通过该服务,我可以使用流上传文件,并添加一些关于我要上传的文件的META数据(文件名,MD5哈希所有常见的东西......),并能够显示有关上传的准确进度信息。

首先,我创建了一个派生类 StreamWithProgress,它继承自 FileStream,其中我重写了 Read 方法,以便在每次读取时引发一个事件,通过该事件传递进度信息。

其次,我使用 MessageContract (http://msdn.microsoft.com/en-us/library/ms730255.aspx) 创建了一个 WCF 服务,将元数据和流对象包装到单个 SOAP 信封中。这项服务非常简单,只公开一种上传方法。

我已经将所有缓冲区大小设置为接受大量数据,如下所示:

  • 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

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

和 httpRuntime 设置如下:

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

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

IIS''ASP 兼容性设置如下:

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

  • .NET 4 上的 WCF 流式文件传输

并按照以下方式禁用批处理:

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

我创建了一个自托管服务,通过该服务上传成功。然后我将其"升级"为IIS托管服务(在我的本地计算机上),该服务有效。然后,我创建了一个本地托管的Windows Azure服务,其中包含WCF webrole,它起作用了。

但问题是,在所有情况下都没有发生实际的流媒体......他们都在发送数据之前缓冲了数据。

当我看到我的客户端正在报告进度时,我遇到了这个问题,但服务器直到整个文件被缓冲后才开始写入文件。

我的实际代码如下。

有什么想法''帮助吗?任何事情将不胜感激...

谢谢!

服务器网络配置:

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

客户端应用配置:

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

派生文件流类 (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 服务器在调用 Web 方法之前缓冲了所有数据!

根据这篇文章:

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

这是 WCF 继承 ASP.Net 行为...但是他们正在谈论在.Net 4.5中对此进行修复:|

如果有人有任何其他建议,那就太好了!

谢谢!!

使 IIS 托管的 WCF 服务中的流式处理正常工作

您正在将 Mtom 与流式传输模式一起使用。这可能会导致问题。请尝试删除 Mtom。实际上,无论如何,Mtom 是一个非常古老的标准。此外,当将 SOAP 服务与流传输模式一起使用时,我们只能有一个类型为 Stream 的参数。我们不能使用像封装器这样的自定义类型。

构建文件上传/下载服务的推荐解决方案是使用 REST。在.NET平台上构建REST服务的方法之一是使用 ASP.NET Web API:http://www.asp.net/web-api。使用此 API,我们不需要处理流式传输模式。我们需要处理的是范围标头。这篇博文可能会有所帮助: 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 处理程序等。如果要使用流,则需要自定义流。我想建议您检查 http://blogs.msdn.com/b/james_osbornes_blog/archive/2011/06/10/streaming-with-wcf-part-1-custom-stream-implementation.aspx 以获取样品。

此致敬意

徐明.

经过一些严格的测试,我发现数据实际上是流式传输的。我将 [MessageContract] 属性应用于封装器类(根据 http://msdn.microsoft.com/en-us/library/ms733742.aspx),这使我能够发送有关该文件的一些额外元数据。用很明显,WireShark和RawCap在读取Stream时通过网络发送数据。

另一个让他头疼的问题是,在实际调用上传方法之前,正在流式传输的数据在服务器端(使用 IIS 7.5)被缓冲!这有点令人担忧,但根据这个:http://social.msdn.microsoft.com/Forums/is/wcf/thread/cfe625b2-1890-471b-a4bd-94373daedd39,修复程序应该在 .Net 的 4.5 版本中