Streaming Web Content

How do I deliver content from a WCF service as part of a web page?

Web page content in this case typically refers to HTML, images, or other data that is directly consumed by the web browser rather than an application running in the web browser. There are a few things you need to do to make your web service serve up content in a way that's indistinguishable from an ordinary web server. I'll serve up a static image at a fixed location for this example but you can get as fancy as you'd like.

The first thing you need is the right contract. The initial page load is ordinarily retrieved using the HTTP GET verb rather than the HTTP POST verb assumed by web services. I'll set that up as part of my contract using the WebGet attribute to set the verb and a URI template to set the address.

 [ServiceContract]
public interface IService
{
   [OperationContract]
   [WebGet(UriTemplate = "/image")]
   Stream GetImage();
}

The second thing you need is the right content type. Although web browsers can try to autodetect content, you should specify the content type if it is known. This allows the web browser to process the content correctly inline.

 public class Service : IService
{
   public Stream GetImage()
   {
      WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
      return new FileStream("c:\\test.jpg", FileMode.Open, FileAccess.Read);
   }
}

Finally, you may notice that while I've done everything needed in the service implementation to enable streaming, content can only be streamed if the binding supports this as well. When using WebServiceHost, the default bindings do not support streamed content. This may be hard to spot because the typical files are small and a test program running on the same machine completes the transfers before streaming would make a difference.

I've wrapped the service implementation in this example to intentionally slow down the transfer to make the difference more apparent. The following code demonstrates enabling streaming on the binding. You can change the transfer mode back to Buffered to observe the difference. Streaming requires support in the receiving application as well to make a difference. Using a large, progressive encoded image will demonstrate this.

 using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Threading;

public class SlowStream : Stream
{
   Stream innerStream;

   public SlowStream(Stream innerStream)
   {
      this.innerStream = innerStream;
   }

   public override bool CanRead
   {
      get { return innerStream.CanRead; }
   }

   public override bool CanSeek
   {
      get { return false; }
   }

   public override bool CanWrite
   {
      get { return false; }
   }

   public override void Flush()
   {
      throw new NotImplementedException();
   }

   public override long Length
   {
      get { return innerStream.Length; }
   }

   public override long Position
   {
      get
      {
         return innerStream.Position;
      }
      set
      {
         throw new NotImplementedException();
      }
   }

   public override int Read(byte[] buffer, int offset, int count)
   {
      Thread.Sleep(100);
      return innerStream.Read(buffer, offset, count > 1024 ? 1024 : count);
   }

   public override long Seek(long offset, SeekOrigin origin)
   {
      throw new NotImplementedException();
   }

   public override void SetLength(long value)
   {
      throw new NotImplementedException();
   }

   public override void Write(byte[] buffer, int offset, int count)
   {
      throw new NotImplementedException();
   }
}

[ServiceContract]
public interface IService
{
   [OperationContract]
   [WebGet(UriTemplate = "/image")]
   Stream GetImage();
}

public class Service : IService
{
   public Stream GetImage()
   {
      WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
      return new SlowStream(new FileStream("c:\\test.jpg", FileMode.Open, FileAccess.Read));
   }
}

class Program
{
   static void Main(string[] args)
   {
      string address = "https://localhost:8000/";
      WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(address));
      WebHttpBinding binding = new WebHttpBinding();
      binding.TransferMode = TransferMode.StreamedResponse;
      host.AddServiceEndpoint(typeof(IService), binding, "");
      host.Open();
      Console.ReadLine();
   }
}

Next time: AutoHeader Extension