Udostępnij za pośrednictwem


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 :)

Comments

  • Anonymous
    April 17, 2008
    The previous post mentioned how to return arbitrary data from WCF services. To receive data, however,
  • Anonymous
    August 11, 2008
    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.
  • Anonymous
    November 12, 2008
    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.
  • Anonymous
    October 09, 2009
    The comment has been removed
  • Anonymous
    November 08, 2010
    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  
  • Anonymous
    November 20, 2010
    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
  • Anonymous
    January 03, 2011
    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?
  • Anonymous
    January 04, 2011
    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=""www.w3.org/.../Transform"" 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://" + 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();       }   }
  • Anonymous
    January 04, 2011
    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?
  • Anonymous
    February 02, 2011
    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.
  • Anonymous
    February 24, 2011
    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.
  • Anonymous
    February 26, 2011
    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).
  • Anonymous
    July 08, 2011
    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";       }regardsThomas
  • Anonymous
    July 26, 2011
    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";   WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Redirect;}
  • Anonymous
    September 01, 2011
    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}
  • Anonymous
    September 01, 2011
    I am following the posted answer instackoverflow.com/.../creating-my-own-mjpeg-streamIt worked fine for me with HTTPlistener but I prefer your approach and can't get it to work.
  • Anonymous
    September 01, 2011
    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)
  • Anonymous
    September 01, 2011
    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();
  • Anonymous
    October 02, 2011
    will this approach with com clients?
  • Anonymous
    October 03, 2011
    The comment has been removed
  • Anonymous
    January 10, 2012
    @CalosI am not able to consume the method GetValue i am getting errorServiceReference1.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.
  • Anonymous
    January 10, 2012
    @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");
  • Anonymous
    January 11, 2012
    @CarlosIs 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>
  • Anonymous
    January 13, 2012
    @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.
  • Anonymous
    January 25, 2012
    @CalosHiI created a WCF service with the use of your code provided in the likesocial.msdn.microsoft.com/.../f363c1d5-40db-49c9-9878-9a6ce5d72202Is 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/" & _               ", binding=BasicHttpBinding_IServiceMoniker, bindingNamespace=http://tempuri.org/"   Set service = GetObject(monString)   MsgBox (service.GetData(10))Thanks in AdvanceGeorge
  • Anonymous
    June 03, 2012
    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?
  • Anonymous
    June 04, 2012
    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.
  • Anonymous
    October 30, 2012
    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?
  • Anonymous
    October 30, 2013
    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!!
  • Anonymous
    July 02, 2014
    Cheers, helpful and simple. Helped me tie down jsonp wcf easily.  Thanks
  • Anonymous
    February 05, 2015
    Thank you, found it helpful.
  • Anonymous
    May 03, 2015
    Thanks ,this was the article i was looking for
  • Anonymous
    November 17, 2015
    Looks like a good workaround for WCF's DataContract appending of default namespaces .