Since Windows Communication Foundation’s (WCF) introduction with the .NET Framework 3.0, developers have quickly realized the potential and power available with WCF while overall reducing the net amount of code one has to write and support to allow applications to communicate. Since connected systems are a strong driver to reduce costs and improve efficiencies with critical business systems; many organizations are making investments in service orientation. WCF introduces many standards compliant methods to ‘configure’ applications to communicate via protocols that, in the past, required developer expertise with specific API ‘stacks’ that provided the requisite service features. (E.g. ASP.NET web services (ASMX), Web Services Enhancements (WSE), System.Messaging (for MSMQ), Enterprise Services (for transactional behavior) and .NET Remoting.) WCF serves to break down the impendence mismatch across these various communication technologies to allow developers to work with a unified programming model (System.ServiceModel) that permits configuration-based service orientation simply by defining the communication characteristics in the client and server’s app.config file. With so many configuration options available to select from, it can be overwhelming to decide an appropriate binding configuration that provides the services and performance desired for specific application’s needs. This series will attempt to provide concrete, real-world examples of some of the advanced features and characteristics of WCF to make this selection process as easy as ordering off your favorite fast food menu.
This series assumes a basic understanding of WCF fundamentals and will provide specific usage scenarios for different WCF addresses, bindings and contracts. (Many articles already exist on this topic including one here. Additional information may be found here and in Justin Smith’s Inside Windows® Communication Foundation.)
Streaming data with WCF
There may be instances where you need to transfer multi-megabyte files or blocks of data from your service to a consumer. While you could have a method that returns a stream or byte array defined in your service contract, there are some potential memory implications this could introduce to your host process. When a block of memory is allocated within a managed process that exceeds 85,000 bytes in size, the CLR performs this allocation in a special region called the Large Object Heap. (LOH – Details can be found here.) Large, short-lived allocations in the LOH can lead to unexpected memory consumption of your host process since this heap is only collected during a Gen-2 garbage collection (that occurs less frequently). This consumption of memory may also lead to a premature Out-of-Memory (OOM) exception within your host process since the LOH is not currently compacted (as of the 2.0 CLR) if there is not a large enough contiguous free-region within the process’s memory space to fulfill the allocation request. These sorts of problems typically don’t arise until your place your WCF host under extreme load and sometimes, unfortunately, surface once the application is in production. (This further underscores the importance of executing appropriate test plans!) Luckily, there are a couple of options available to mitigate this problem. One option is to take advantage of the streaming option available with the BasicHttpBinding, NetTcpBinding, and NetNamedPipeBinding; while another is to allow consumers to request chunks of data that is smaller than the 85,000 LOH threshold. In this posting, we’ll look at both.
The solution contains 4 projects (See Figure 1) to demonstrate large-data transfers using WCF. The service contract is defined in the SharedInterface project that is referenced by both the client and the service implementation. The actual service implementation is in the ServiceImpl project and the client, capable of illustrating all 3 methods of requesting data, is in the FormsClient project. The server project is a simple console application you can use to run the solution outside of the IDE. (The implementation is separated from the sample host process so we can leverage the built-in WCF host within Visual Studio 2008.)
Figure 1 – Project Layout
To start with, we’ll define our service contract to have to two call-options. One returning a stream while the other returns chunks of data. The ‘chunking’ method is an alternative means of returning data to a consumer through a series of multiple calls where the consumer tracks its progress through the bytes of the file. This method returns a struct with the total file size as well as the requested byte region from the file and is declared in the SharedInterface.
public struct ChunkedDataResult
public byte ChunkedBlock;
public long StreamSize;
Then, we configure our bindings. In this example, I have defined 1 binding to stream the response defined as follows and another to use the (default) buffered transfermode. Both endpoints have relative addresses (Streaming and Buffered) that are rooted on the port and service name described by the baseaddress setting.
<binding name="StreamedBinding" messageEncoding="Mtom" transferMode="Streamed" />
<binding name="BufferedBinding" messageEncoding="Mtom" transferMode="Buffered" />
<endpoint binding="basicHttpBinding" bindingConfiguration="StreamedBinding"
<endpoint address ="Buffered"
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"
<add baseAddress="http://localhost:8500/WCFSamples" />
The key components of this binding is the transferMode option. For our streamed tests, we set it to StreadmedResponse. When using this transfermode (that, again, is available with the basicHttpBinding in addition to the TCP and NamedPipe bindings) within the context of a web application hosted in either IIS, there are a couple of considerations to keep in mind:
1) Visual Studio’s webdev server (Cassini) cannot handle streaming over HTTP. For testing, you need to host in a console application or through the WCF host utility. (As I have done in the sample).
2) IIS uses the ASP.NET maxRequestLength setting for the max length not the setting defined WCF setting. You will need to add this to your web.config to ensure IIS is able to return the data requested.
3) IIS cannot transfer more than 2GB of data. You'll need to self host to get around this if you need to send more data than that.
4) To avoid timeouts, you will need to increase the receive timeouts on the client (as I have done in the FormsClient’s configuration shown below. Note the extended recieveTimeouts in addition to both the increased maxBuffersize and maxRecievedMessageSize).
<binding name="WCFSamples_Buffered" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
maxBufferSize="2097152" maxBufferPoolSize="524288" maxReceivedMessageSize="2097152"
messageEncoding="Mtom" textEncoding="utf-8" transferMode="Buffered"
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="65536"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
Using the Debugging tools for Windows®; I collected some memory dumps of the host process to illustrate the allocations that occur when one of our images is sent to a consumer using the buffered transfer mode. (While collecting a memory dump is outside the scope of this article, the command I used to collect a dump is: adplus –quiet –hang –pn server.exe. Adplus is a VB Script that is located in the installation directory of the Debugging tools. ) Upon inspection (See Figure 2), we can clearly see the large byte-array allocations within the LOH.
Figure 2 – Allocations that occur when transferMode = Buffered
When we examine the LOH (See figure 3) when transferMode=Streaming (or StreamedResponse); we can see that there are no large byte array allocations that occur to ‘buffer’ the data for the client. (The object arrays are pinned regions of memory not related to the buffering of data from what I can tell.)
With our ‘chunked’ transfer mode, we also see that these LOH allocations disappear (See figure 4) and we instead see many smaller allocations that are slightly larger than our 65,535 byte block-size we defined in our server implementation. (The additional bytes are overhead with the WCF message.) These blocks are controlled by the following line in our service contract implementation. (StreamingImplementation.cs).
private const int BLOCKSIZE = 65535;
Figure 4 – Allocations that occur with our ‘chunking’ solution
While this article presented a configuration-based solution and an application-implemented solution; one could also leverage WCF’s many extensibility points to develop a channel that behaved similarly to ‘chunked’ receive method shown above. There’s a great starting point of such a solution here.
The code examples used for this solution can be downloaded from here.