WCF "Raw" programming model (Web)

I've seen quite a few times in the forums people asking how to control exactly how the data returned by a WCF service. People want to use a certain format for the output of the data which isn't (natively) supported by WCF, such as XML or JSON. A few examples of questions of this nature:

  • I want to return the string "Hello world", but even though I set the BodyStyle property in the Web[Get/Invoke] attribute to Bare, the result is still wrapped in a <string> tag, like "<string xmlns="...">Hello world</string>; how do I remove the <string> wrapper?
  • I need to return my parameters in a certain format which is not supported by WCF. For example, my operation returns a list of triples, and I want them to be returned in a .csv-like format, with the header names in the first line and the values in subsequent lines. How can I get it done?

One way to do it is to create a separate MessageEncoder, which is capable of taking a Message object and converting it to the bytes, and use that encoder in the binding of the endpoint which contains the operation. Although this is certainly doable, it requires a lot of work - a Message object contains an XML representation of its contents, so you'd need a mapping between XML and whichever format you want, possibly requiring a new XmlWriter/XmlReader implementation; you'll also need all the plumbing parts needed to connect the encoder with the endpoint (a MessageEncoderFactory, a MessageEncodingBindingElement and so on).

The Web programming model introduced in WCF on .NET Framework 3.5 simplifies this task. The magic happens when the operation return type is of System.IO.Stream (the abstract class, not one of its concrete implementations). By returning a stream, WCF assumes that the operation wants total control over the bytes that will be returned in the response, and will apply no formatting whatsoever in the data that is returned. This service, for example, solves the first question listed above:

[

ServiceContract]
public class RawService
{
[OperationContract, WebGet]
    public System.IO.Stream GetValue()
{
        string result = "Hello world";
        byte[] resultBytes = Encoding.UTF8.GetBytes(result);
        return new MemoryStream(resultBytes);
}
}

This should work in most of the cases, since the result is fairly simple. It is a good practice, however, whenever you're using the raw programming model, to specify the Content-Type for the response. If nothing is specified, responses of operations with Stream return values will have a content type of application/octet-stream (i.e., binary). Some browsers (such as IE) may identify that the content is actually text, and print it correctly, but that's not a guarantee. The example will then become:

[ServiceContract]
public class RawService
{
[OperationContract, WebGet]
    public System.IO.Stream GetValue()
{
        string result = "Hello world";
        byte[] resultBytes = Encoding.UTF8.GetBytes(result);
        WebOperationContext .Current.OutgoingResponse.ContentType = "text/plain" ;
        return new MemoryStream(resultBytes);
}
}

Notice that this isn't limited to text only; with the raw programming model you can create a service which returns pretty much anything, in any format, such as images created on the fly:

public class BlogPostRaw
{
[ServiceContract]
public interface ITest
{
[OperationContract, WebGet]
Stream GetImage(int width, int height);
}
public class Service : ITest
{
public Stream GetImage(int width, int height)
{
Bitmap bitmap = new Bitmap(width, height);
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
bitmap.SetPixel(i, j, (Math.Abs(i - j) < 2) ? Color.Blue : Color.Yellow);
}
}
MemoryStream ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Position = 0;
WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
return ms;
}
}
public static void Test()
{
string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

When running the test method, you can point the browser to https://localhost:8000/Service/GetImage?width=50&height=40 to see that WCF can also be used as an image server :)