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 = “http://” + 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 http://localhost:8000/Service/GetImage?width=50&height=40 to see that WCF can also be used as an image server 🙂

Comments (33)

  1. Anonymous says:

    The previous post mentioned how to return arbitrary data from WCF services. To receive data, however,

  2. sujeetsSpace says:

    What the differnt ways you can dispose the Stream that is returned? One way is to dispose the stream when the service type is disposed. However that will only work for per call mode.

  3. richardcollette says:

    Interesting.  The benefit that I see but not clearly stated here is that you are able to produce HTTP (and only Http since WebOperationContext is being used) protocol services that do not rely on IIS.   It is like an httpHandler except no dependency on IIS.

  4. Anonymous says:

    I’ve been looking all over the Net for this! You are a god send!

  5. Anonymous says:

    This is awesome stuff Carlos.  I really want to use this code, but I can't work out how to implement it.  Do I put this code into a .svc file?  Are there any web.config entries required?  

    If I put the second example (adding the ContentType) into a .asmx.vb file (converted the code to vb.net first obviously), I get an error: "Only Web services with a [ScriptService] attribute on the class definition can be called from script".

    So I changed it to a webservice with <System.Web.Script.Services.ScriptService()>  and <WebMethod()>, and then got "Object reference not set to an instance of an object" unless I comment out the WebOperationContext.Current.OutgoingResponse.ContentType bit (I think WebOperationContext must be inconcistent with the webservice).  

    So it seems I need to use a WCF service.  But then you haven't supplied any interface code on the second example, so how do I implement this?

    I'm in pain.  Please help!

    Thanks  

  6. Josh,

    The "raw" programming model only works with WCF services, not with ASMX ones – the WebOperationContext.Current property will only be set when the code is running within a WCF service, so if you try to access it in an ASMX one it will be null (Nothing).

    Regarding the interface code, on the first and second examples there is none – the service contract is defined in the implementing class itself (on WCF you can choose to split the contract and the implementation, or you can implement everything in a class). If you want to split the interface, you should have something similar to the code below:

       <ServiceContract()> Public Interface IRaw

           <OperationContract(), WebGet()> Function GetValue() As Stream

       End Interface

       Public Class RawService

           Implements IRaw

           Public Function GetValue() As System.IO.Stream Implements IRaw.GetValue

               Dim result As String = "Hello world"

               Dim resultBytes As Byte() = Encoding.UTF8.GetBytes(result)

               WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"

               Return New MemoryStream(resultBytes)

           End Function

       End Class

  7. Anonymous says:

    I need an xml with xslt applied as the output. What should the OutgoingResponse.contentType be in this case for the xml to be displayed as required on the browser?

  8. Swapna, to find out the correct content type for a certain file type, I usually deploy a sample file from that type in IIS and browse to it – whatever content-type is returned by IIS I use in it, since what the raw programming model is essentially doing is mimicking a web server.

    As for XSLT specifically, application/xml is a good value (that's what's returned by IIS). The example below returns a XML formatted as a table using a XSL transformation (done in the browser) – when running it you can browse to http://your_machine_name:8000/Service/products.xml and it will be properly formatted in the browser.

       public class BlogPostComment

       {

           [ServiceContract]

           public class Service

           {

               [WebGet(UriTemplate = "/products.xml")]

               public Stream GetXml()

               {

                   string xml = @"<?xml version=""1.0"" encoding=""utf-8""?>

    <?xml-stylesheet type=""text/xsl"" href=""products.xslt""?>

    <products>

     <product>

       <name>Chocolate</name>

       <price>1.30</price>

     </product>

     <product>

       <name>Soda</name>

       <price>2.45</price>

     </product>

     <product>

       <name>Cookies</name>

       <price>7.50</price>

     </product>

    </products>";

                   WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";

                   return new MemoryStream(Encoding.UTF8.GetBytes(xml));

               }

               [WebGet(UriTemplate = "/products.xslt")]

               public Stream GetXslt()

               {

                   string xslt = @"<xsl:stylesheet xmlns:xsl=""http://www.w3.org/…/Transform"&quot; version=""1.0"">

    <xsl:output method=""html""/>

    <xsl:template match=""/"">

    <html>

     <head>

     </head>

     <body>

       <xsl:apply-templates />

     </body>

    </html>

    </xsl:template>

    <xsl:template match=""products"">

       <h3>Products in stock</h3>

       <table>

           <tr>

               <th>Name</th>

               <th>Price</th>

           </tr>

           <xsl:for-each select=""product"">

               <tr>

                   <td><xsl:value-of select=""name""/></td>

                   <td><xsl:value-of select=""price""/></td>

               </tr>

           </xsl:for-each>

       </table>

    </xsl:template>

    </xsl:stylesheet>

    ";

                   WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";

                   return new MemoryStream(Encoding.UTF8.GetBytes(xslt));

               }

           }

           public static void Main()

           {

               string baseAddress = "http://&quot; + Environment.MachineName + ":8000/Service";

               WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));

               host.Open();

               Console.WriteLine("Host opened");

               Console.WriteLine("Press ENTER to close");

               Console.ReadLine();

               host.Close();

           }

       }

  9. Anonymous says:

    Carlos,

    I had tried this approach but i get a "Cannot view XML input using XSL style sheet. Access denied" error. Any clues why this could be happening?

  10. Swapna, can you try saving two static files, one with the XML, one with the XSL, on IIS (and reference one from the other) and see if you can browse to it? If you can't (which seems to be what's going on), then you may have a problem in your browser itself, not on the server.

  11. Anonymous says:

    Carlos:

    Just wondering if it is possible to return an image from a DataContract rather than a ServiceContract? Something like:

    [DateContract()]

    public class ImageClass

    {

     [DataMember]

     public Stream Image;

    }

    Not sure how you would handle the content type in this case. I have found elsewhere on the web that you can use the Image class and let the serializer handle the translation. It seems to me the Stream approach would be better with less overhead.

  12. thehandygeek, this does not work for data contracts. The "special" Stream treatment only happens at the ServiceContract level (it's mapped to the HTTP body). The body has a well-defined end (either defined by the Content-Length header, or by the chunk length in case of chunked transfer). In a data contract, there's no way to determine (without some expensive look-ahead logic) where the serializer should stop reading the stream and starting reading the next element (such as the end element for the ImageClass).

  13. Great thing Carlos.

    Is the following also possible ? I can see no reaction, must be something missing:

           public void sendRedirect()

           {

               WebOperationContext.Current.OutgoingResponse.Location = "http://www.google.com&quot;;

           }

    regards

    Thomas

  14. thkerkmann, you're missing the status code change. With that it should work fine:

    [WebGet]

    public void SendRedirect() {

       WebOperationContext.Current.OutgoingResponse.Location = "http://www.bing.com&quot;;

       WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Redirect;

    }

  15. Anonymous says:

    How can I serve multipart data? The code below does not seem to send anything to the browser until I stop writing to the stream. I want to keep writing continuously!

    public Message MJPGstream(string s)

           {

               return WebOperationContext.Current.CreateStreamResponse(outmjpeg,"multipart/x-mixed-replace;boundary=" + BOUNDARY + "rn");

           }

    public void outmjpeg(Stream stout)

    {// endless loop writing jpegs into stout

    }

  16. Anonymous says:

    I am following the posted answer in

    stackoverflow.com/…/creating-my-own-mjpeg-stream

    It worked fine for me with HTTPlistener but I prefer your approach and can't get it to work.

  17. You can do that, but you'll need to create your own subclass of Stream to do that. Essentially, your stream would be consumed (read) by the WCF infrastructure (you'd return it), and your code which wants to write would be the producer for that stream. If there is no data to return, your stream would need to block on the Read method; when your application wants to write something, it would write to it then flag the stream (using some sort of mutex / event) so that any pending Read calls are unblocked. Look for more information on the producer/consumer problem, and for an example of a custom stream, you can look at James Osborne's blog entry at blogs.msdn.com/…/streaming-with-wcf-part-2-plugging-a-custom-stream-into-a-wcf-service.aspx (it's a simple read/only stream, what you'd want to do would be a little more complex)

  18. Anonymous says:

    Thanks a million. Your suggestions helped me figure out the problem in my code but the solution was extremely simple (set TransferMode to streamed)

    WebHttpBinding bb = new WebHttpBinding();

    bb.TransferMode = TransferMode.Streamed;

    host.AddServiceEndpoint(typeof(IService), bb, "");

    host.Open();

  19. Anonymous says:

    will this approach with com clients?

  20. @Muzeeb, it works with any HTTP-based client – this code creates a HTTP server, and to consume it you need to create an HTTP client (whether using a COM component to do it or not). You cannot consume it via a direct COM call, though.

  21. @Calos

    I am not able to consume the method GetValue i am getting error

    ServiceReference1.Service1Client obj = new ServiceReference1.Service1Client();        

    System.IO.Stream ss = obj.GetValue("test");

    The content type text/plain of the response message does not match the content type of the binding (text/xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly.

  22. @george.santosh, you cannot really add a service reference to create a client proxy for non-SOAP endpoints (i.e., those which use WebHttpBinding) in WCF – even if you don't have operations with Stream parameters. For SOAP there is an industry standard for metadata (WSDL) which WCF uses to expose the metadata which tools such as svcutil or "Add Service Reference" can consume to create a client. For non-SOAP endpoints there isn't such a tool, so even if you create one, the client code won't be usable (as you found out yourself).

    It's possible that you can change the generated client to be able to support the service (try the changes below, they may or may not work), but in general, that is not supported.

    ServiceReference1.Service1Client obj = new ServiceReference1.Service1Client();

    obj.Endpoint.Binding = new WebHttpBinding();

    obj.Endpoint.Behaviors.Add(new WebHttpBehavior());

    System.IO.Stream ss = obj.GetValue("test");

  23. @Carlos

    Is it any way to return message as below.

    <Start-Data><values1>;<values2>;……….<values60>;<End-Data>

    like..

    <Start-Data>306;1000003927;123456789;AuthorizeResponse;0912;Fleet;;True;800;000;Active;0;—;—;11/24/2011 6:34:39 PM; ;0; ;; ;False;0;—;—;1;;False;; ;;False;NoOne;0;0;102;PointsNotAvailable;0;0;0; ;;0;;0;; ; ; ; ; ;0;;0;0;0; ; ; ; ; ;<End-Data>

  24. @george.santosh, I saw that you posted this question in the MSDN forums at social.msdn.microsoft.com/…/f363c1d5-40db-49c9-9878-9a6ce5d72202 – I'll answer it there since it has better formatting for code.

  25. @Calos

    Hi

    I created a WCF service with the use of your code provided in the like

    social.msdn.microsoft.com/…/f363c1d5-40db-49c9-9878-9a6ce5d72202

    Is it possible to call this service from my legacy application like VB6. If yes means pls provide some idea.

    And also normally if i consume a WCF service in VB6 the response will be XML  or Plain text format.

    I used the below code i used Mex binding in my WCF service

       Dim service As Object

       Dim monString As String

       monString = "service4:mexaddress=http://localhost/WCFMoniker/ServiceMoniker.svc/mex" & _

                   ", address=http://localhost/WCFMoniker/ServiceMoniker.svc" & _

                   ", contract=IServiceMoniker, contractNamespace=http://tempuri.org/&quot; & _

                   ", binding=BasicHttpBinding_IServiceMoniker, bindingNamespace=http://tempuri.org/&quot;

       Set service = GetObject(monString)

       MsgBox (service.GetData(10))

    Thanks in Advance

    George

  26. Anonymous says:

    hello,

    i try to configure binding to receive a raw content type, here my code:

    <customBinding>

                   <binding name="RawReceive">

                                        <webMessageEncoding  webContentTypeMapperType="conTypeMap.MyContentTypeMapper,conTypeMap, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>

                       <httpTransport manualAddressing="true" maxReceivedMessageSize="524288000" transferMode="Streamed"/>

                                       </binding>

               </customBinding>

    but i get this error; can't find file or assemply 'conTypeMap, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' ….

    ????

    can you helpe me?

  27. sam, is the assembly accessible to your application? If it's self-hosted, it should be on the same location as the .EXE which is running; if webhosted it should be located in the /Bin directory of the web application.

    Another thing to check is whether the name is correct; try tracing out the value of <<typeof(conTypeMap.MyContentTypeMapper).AssemblyQualifiedName>>. That should be the value to be specified in the configuration.

  28. Anonymous says:

    Carlos your articles are quite useful, Keep rocking.  I need a clarification, we can customize the serialization by inheriting XmlObjectSerializer and using IContractBehavior we can achieve it for a normal web service. Why the same thing is not applicable for REST architecture, I need to go with either of XML/JSON else I can use stream. why not my own customized serializer?

  29. Anonymous says:

    Carlos,

    I know that this blog post has been here for a while, but I just had to say, you are a life saver! Getting my WCF service working correctly has been real frustrating, but your solution worked immediately.

    Thank you so much!!

  30. Anonymous says:

    Cheers, helpful and simple. Helped me tie down jsonp wcf easily.  Thanks

  31. iltafo says:

    Thank you, found it helpful.

  32. Anonymous says:

    Thanks ,this was the article i was looking for

  33. Very Nice says:

    Looks like a good workaround for WCF's DataContract appending of default namespaces .