WCF REST Services: Setting the response format based on request’s expected type

I just attended the Microsoft PDC in LA.  One of the many excellent sessions was a pre-conference on WCF, part of which was presented by Ron Jacobs. Ron did a fantastic job of explaining WCF REST Services and the WCF REST Starter Kit.

One of the examples he showed from the WCF REST Starter kit was an example where the response type (JSON or XML) is dynamically set based on the HTTP request’s requested content type in the "Accepts" HTTP Header.

That example works by switching between two different operation implementations (methods).

I liked the idea of using the requested content type to automatically return JSON or XML depending on the requested content type, but I wasn’t so keen on having to implement two methods.

I thought I’d try to get a similar thing working but using a single operation implementation which is called no matter whether or JSON or XML are requested.

The DynamicResponseType attribute

This is how it works.  You decorate your method with an additional DynamicResponseType attribute which I have defined:

   1: [ServiceContract]
   2: [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
   3: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   4: public class Service1
   5: {
   6:     [OperationContract]
   7:     [WebGet(UriTemplate = "GetData?param1={i}&param2={s}")]
   8:     [DynamicResponseType]
   9:     public SampleResponseBody GetData(int i, string s)
  10:     {
  11:         return new SampleResponseBody() { 
  12:             Name = "Test",
  13:             Value = s, 
  14:             Time = DateTime.Now.ToShortTimeString() 
  15:         };
  16:     }
  17: }
  18:  
  19: public class SampleResponseBody
  20: {
  21:     public string Name { get; set; }
  22:     public string Value { get; set; }
  23:     public int IntValue { get; set; }
  24:     public string Time { get; set; }
  25: }

An example client

Then when the client requests a specific type (XML or JSON), it is served automatically.

Below I have a pure HTML/Javascript client with two buttons, each of which call the same  GetWebRequest function when they are clicked but passing a different requested content type as a parameter.  The GetWebRequest function issues an HTTP request to the WCF operation I showed above.

The first button says it wants JSON, and the second XML.  This is done by setting the "Accept" request header:

   1: <body>
   2:     <form id="form1" runat="server">
   3:     <div>
   4:     
   5:     <input type="button" value="Click to request JSON" 
   6:         onclick="GetWebRequest('application/json');" />
   7:     
   8:     <input type="button" value="Click to request XML" 
   9:         onclick="GetWebRequest('application/xml');" />
  10:     
  11:     </div>
  12:     <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
  13:     </form>
  14:  
  15: <script type="text/javascript">
   1:  
   2:   function GetWebRequest(acceptType) {  
   3:     var wRequest = new Sys.Net.WebRequest();
   4:     wRequest.get_headers()["Accept"] = acceptType;
   5:     var url = "/Service1.svc/GetData?param1=12&param2=";
   6:     wRequest.set_url(url + new Date());
   7:     wRequest.set_httpVerb("GET");
   8:     wRequest.add_completed(OnWebRequestCompleted);
   9:     wRequest.invoke();
  10:   }
  11:  
  12:   function OnWebRequestCompleted(executor, eventArgs) {
  13:     alert(executor.get_responseData());
  14:   }

</script>

  16: </body>

This is the form that gets displayed initially:

image

When you click on the first button we request JSON from the WCF Service operation:

image

When you click on the second button we request XML from the same WCF Service operation:

image

How it works.

I’ve created my own  WCF ServiceHostFactory which I wire up in the SVC file:

   1: <%@ ServiceHost Language="C#" 
   2:     Debug="true" 
   3:     Service="WcfService2.Service1" 
   4:     CodeBehind="Service1.svc.cs" 
   5:     Factory="DamianBlog.ServiceHostFactory2Ex" %>

In my ServiceHostFactory2Ex class I ensure that my own WebServiceHost class gets created.

Then in my own WebServiceHost I ensure that my own WebHttpBehavior replaces the standard one.

Next in my own WebHttpBehavior I override the GetReplyDispatchFormatter method and return my own IDispatchMessageFormatter.

In my own IDispatchMessageFormatter I implement the SerializeReply method and then use a JSON formatter or XML formatter depending on the "Accepts" HTTP request header which I pick up from the OperationContext.Current.RequestContext.RequestMessage.

The full source is available for download here https://damian.fyi/WCFDynamicResponseDemo.zip.

Rob Jacobs blogs at http://blogs.msdn.com/rjacobs/

26 thoughts on “WCF REST Services: Setting the response format based on request’s expected type

  1. Scott Wojan

    Nice work around for the flawed implementation by Microsoft, however I don’t think “Service1.svc/GetData?param1=12&param2=” is a particularly restful URI

    Reply
  2. Pingback: WCF and REST, An approach to using the Content-Type and Accept HTTP Headers for Object Serialization - Daptivate > by Kyle Beyer

  3. Kyle Beyer

    Hey Damian – Awesome work on this! I really like how you boiled it down to a single method attribute.

    One thing I noticed while starting to use it in conjunction with the WCF REST starter kit is that there also needs to be support for dynamic *request* serialization. So, I’ve extended your code a bit and merged it into the starter kit code to enable this.

    You can download source here: http://daptivate.com/archive/2008/11/16/wcf-and-rest-an-approach-to-using-the-content-type-and-accept-http-headers-for-object-serialization.aspx

    Interested to hear your thoughts and suggestions.

    Reply
  4. dmehers

    Hi Kyle,

    Sounds like a great addition — I tried to get it working but hit a problem because they hadn’t made one of their methods virtual — don’t remember which one.

    Cheers,
    Damian

    Reply
  5. Mot

    Damian, thank you very much for this solution!

    Is it possible to use your workaround when having more than one endpoint specified?

    I’m wanting to provide a soap interface and a rest (which offers the flexibility your providing with the .dll) interface.

    Thanks in advance,
    Mot

    Reply
  6. dmehers

    Hi Mot,

    This is really specific to REST, using the REST starter kit … if you find a way of making it work with a SOAP interface then I’d be very interested in hearing about it though.

    /Damian

    Reply
  7. Hax

    Hi, Guy
    when i use it ,it throw a exception at here :
    return jsonDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);
    Exception:The given key was not present in the dictionary.
    I use vs2010
    Can you help me? thanks very much

    Reply
  8. Duke

    Damian,

    I learned all about calling a WCF from Outlook thanks to your article. However I still couldn’t figure out how to set the timeouts in client side. It appears that openTimeout, closeTimeout, sendTimeout and receiveTimeout in wcf’s config file have no effect on vba client. No matter what I set there, my Outlook client times out in exactly 60 seconds. I’d really appreciate if you could tell me how to increase this client timeout.

    My WCF service is hosted on IIS and its timeout is set to 120 seconds so I know that’s not the one timing out.

    Thanks
    Duke

    Reply
  9. reyquattsigh

    “However I still couldn’t figure out how to set the timeouts in client side. It appears that openTimeout, closeTimeout, sendTimeout and receiveTimeout in wcf’s config file have no effect on vba client. No matter what I set there, my Outlook client times out in exactly 60 seconds. I’d really appreciate if you could tell me how to increase this client timeout.”
    How much is realistic?

    Reply
  10. Pingback: Automatic Format Selection in WCF REST/HTTP Services « Sankarsan’s Journal

  11. mossdev

    Is there replay for this 🙂 I’ve same error, help please.

    Hi, Guy
    when i use it ,it throw a exception at here :
    return jsonDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);
    Exception:The given key was not present in the dictionary.
    I use vs2010
    Can you help me? thanks very much

    Reply
  12. ponbe

    Hello Hax, nav, teabaggs and mossdev,

    Have you resolved the error yet, I’m also getting the KeyNotFoundException

    Regards

    Reply
  13. tony

    Or ofcourse you could downgrade to .net 2 and simply use a .asmx with a [WebMethod, UseHttpGet]… and voila! both will do the trick!

    WCF is to .ASMX as ASP:TextBox is to html input…. and almost unnecessary wrapper.

    Reply
  14. Pingback: Switching serialization b/w json and xml in WCF response for non-"GET" methods | BlogoSfera

  15. VEL

    when i use it ,it throw a exception at here :
    return jsonDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);
    Exception:The given key was not present in the dictionary.
    I use vs2010
    Can you help me? thanks very much

    Reply
  16. Pingback: Creating a Windows Universal app to talk Bluetooth LE, save to SQLite and expose a REST service « Damian Mehers' Blog

  17. Hector

    This was an excellent article. Good work Damian!

    I was able to get it to work for GET. I could not get it to work for POST. It seems that POST always returns XML format. I ran a debug trace and it seems to bypass the SerializeReply method for some reason. I was tending to think something in my Web.Config probably needs tweaking to include the POST verb but not really sure.

    Any help is greatly appreciated.

    Reply
    1. damian Post author

      Hi Vector … I’m happy the article is still proving useful after so many years, but I’m afraid I’m not really using WCF much these days so I can’t help with your question 🙁 Maybe someone else will though!

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *