Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

WCF and JSON Services


:P
On this page:

I took a quick look today at WCF's new capability to work with JSON data. I was looking forward to having WCF services providing this functionality and there are a few cool things that work with it. It's now essentially possible to create WCF Web Services that take JSON as input and produce JSON output and it's pretty easy to do in fact.

For example I can create a Svc style service in an ASP.NET application like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using TimeTrakker;
using System.Data;
using System.Web.Script.Services;
using System.ServiceModel.Web;

namespace TimeTrakkerWeb
{
    [ServiceContract( Namespace="", Name="TimeTrakkerService")]
    public interface ITimeTrakkerService
    {
        [OperationContract]
        //[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, 
        //           ResponseFormat = WebMessageFormat.Json, 
        //           UriTemplate = "helloworld/{name}")]
        string Helloworld(string name);

        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest,
                ResponseFormat = WebMessageFormat.Json,
                UriTemplate = "loadcustomer")]
        //[WebGet(BodyStyle = WebMessageBodyStyle.WrappedRequest,
        //        ResponseFormat = WebMessageFormat.Json,
        //        UriTemplate = "loadcustomerGet/{Pk}")] 
        CustomerEntity LoadCustomer(string Pk);

    }


    public class TimeTrakkerService : ITimeTrakkerService
    {
        public string Helloworld(string name)
        {
            return "Hello World, " + name;
        }

        public CustomerEntity LoadCustomer(string Pk)
        {
            busCustomer Customer = new busCustomer();
            Customer.Options.ThrowExceptions = true;

            CustomerEntity cust = Customer.Load(int.Parse(Pk));

            cust.InvoiceEntities = null;
            cust.ProjectEntities = null;            

            return cust;
        }
    }
}

And the following in Web.config to configure the service:

<system.serviceModel>
  <services>
    <service name="TimeTrakkerWeb.TimeTrakkerService" 
behaviorConfiguration
="TimeTrakkerServiceBehavior">
      <!--<endpoint contract="TimeTrakkerWeb.ITimeTrakkerService" binding="basicHttpBinding"/>-->
      <endpoint contract="TimeTrakkerWeb.ITimeTrakkerService" 
binding="webHttpBinding"
behaviorConfiguration="AjaxBehavior"
/>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="TimeTrakkerServiceBehavior">
        <serviceDebug includeExceptionDetailInFaults="true"/>
        <serviceMetadata httpGetEnabled="true"/>          
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
    <behavior name="AjaxBehavior">          
        <enableWebScript  />          
      </behavior>
    </endpointBehaviors>
  </behaviors>
</system.serviceModel>

The key features to enable JSON access are the behavior configuration in the configuration file that adds the enableWebScript behavior and the additional attributes on each of the service method that should be exposed to JSON. Actually the attributes are required only if you explicitly need to use REST operations - the default behavior allows you to use POST based (ASMX style) JSON requests against the server where the input is a JSON parameter list.

That's all nice and neat if you're using MS Ajax. Unfortunately it appears that everything about this service uses the funky MS AJAX formatting for JSON result sets and inputs. For example the above result returns the following JSON string:

{"d":{"__type":"CustomerEntity:#TimeTrakker", "Pk":1,"UserId":"0 ","LastName":"Strahl", "FirstName":"Rick","Company":"West Wind Technologies","Address":"32 Kaiea Place","City":"Paia", "State":"HI ","Zip":"96779 ","Zip4":" ","Country":"USA", "CountryId":"US ","Phone":"(503) 914-6335", "Email":"rstradhl@west-wind.com","Fax":"1\/1\/1900","Notes":"", "Entered":"\/Date(1187593200000-0700)\/","Updated":"\/Date(1191366660000-0700)\/", "LastOrder":"\/Date(-2208960000000-0800)\/", "BillingRate":150.00,"Xml":null, "tversion":[0,0,0,0,0,0,184,67],"ProjectEntities":[],"InvoiceEntities":[]}}

Basically the result is wrapped in an additional type (presumably to allow for multiple results?) and there's a __type property attached to each object returned. Even simple result values at least include the wrapper type, so even say a string result includes the d: type.

If you're using MS AJAX on the client there's no issue - the client knows how to parse this JSON into a clean object. But if you're using some other mechanism - jQuery or Prototype for example to retrieve a result set you get back a funky object where you have to go result.d.LastName for example, which is ugly.

There are a few options on the WebGet (and WebInvoke) attribute that allow specifying the BodyStyle and one of the options there is BodyStyle.Bare, but unfortunately it doesn't work with any of these AJAX responses apparently.

That's a real shame too, because the WebGet and WebInvoke methods allow for a really nice REST model for accessing requests by using UrlTemplates. Notice the:

UriTemplate = "loadcustomer/{pk}")

in the attributes above which allows you to specify a parameter as part of the URL extension. So to retrieve a particular customer in this case you'd use a URL like this:

localhost/timetrakkerweb/services/TimeTrakkerService.svc/LoadCustomer/1

Multiple parameters can be specified in this same format. With a little URL Rewriting you can get some decent looking URLs to access your data fairly easily.

So what would client code (non MS Ajax look like to call this service?) Even with the funky JSON object results the code is still pretty trivial. Here I'm using my wwScriptLibrary (from wwHoverPanel) for the ajax callback:

<script type="text/javascript">
function CallService()
{
    ajaxJson("services/timetrakkerservice.svc/LoadCustomer",{"Pk":"1"},Callback,onError);
}
function Callback(result)
{
    alert(result); // string
    alert( result.d.Company);
    alert( result.d.ProjectEntities[1].CustomerEntity.LastName);
}
function onError(error)
{
    alert("Error: " + error.message);
}

In this case I'm using the 'POST' mechanism to post back the result to the server. MS Ajax expects an object with a property for each of the parameters provided to the method which in this case is a single string parm of the customer id. I chose the string parm here because when using the GET mechanism WCF failed to convert the URL based 1 to an int on its own:

Operation 'LoadCustomer' in contract 'TimeTrakkerService' has a path variable named 'Pk' which does not have type 'string'. &nbsp;Variables for UriTemplate path segments must have type 'string'.

That's pretty lame given that simple values at least could easily be translated. However, typically it's better to use POST anyway.

If you're using MS Ajax you can add a script manager script reference to the service:

        <asp:ScriptManager ID="ScriptManager1" runat="server">
        <Scripts>
            <asp:ScriptReference Path="services/TimeTrakkerService.svc/js" />
        </Scripts>
        </asp:ScriptManager>

which adds the script proxy to the page and you can then directly call the Web Service by its fully qualified name (ie. Namespace.Interface.Class by default). Normally that'd be something like tempuri.org.ITimeTrakkerService.TimeTrakkerService, but I explicitly set the namespace of the ServiceContract to none and the name to just to TimeTrakkerService. So the service call is as simple as:

function CallService()
{
    TimeTrakkerService.LoadCustomer("1",Callback);
}
function Callback(result)
{
    alert(result);
    alert( result.Company);
    alert( result.ProjectEntities[1].CustomerEntity.LastName);
}

A bit cleaner of course.

Playing around a bit with the options there are a number of ways that services can be accessed using GET, POST and PUT. This is all controlled via attributes (WebGet, WebInvoke etc.) I found another annoying issue though:  It appears you can't specify both WebGet and WebInvoke for the same service method. When you do you get:

Operation 'LoadCustomer' in contract 'TimeTrakkerService' has both WebGetAttribute and WebInvokeAttribute; only one can be present.

It's quite likely that you might want to expose both Get and Post behavior to a service, although POST is usually the preferred mechanism. Certainly in public access REST scenarios you'd want to give people access in a variety of ways.

On the other hand I was pleasantly surprised that WCF WAS able to handle a circular reference scenario with LINQ to SQL entities (which is what the above returns) nicely. It cut off the circular referencing nicely and appropriately returned me the list of child items.

All in all it's nice to see JSON support in WCF, but I'm not sure how well this will really play outside of MS AJAX. Given that REST services are supposed to be as lean as possible and easy to consume the issue of the extra type wrapper is kind of a show stopper. I hope that there is somehow a way to get WCF to produce clean JSON instead of this proprietary format.

Posted in AJAX  Microsoft AJAX  WCF  

The Voices of Reason


 

Matt
November 02, 2007

# re: WCF and JSON Services

Hi Rick,
I'm trying to make the most simple service that consumes and output xml. I followed http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2282952&SiteID=1
and am able to output xml properly, but after almost a day, I am not any closer to consuming xml.

Here is what my contract looks like.

[ServiceContract]
public interface ITestService
{
[OperationContract, WebInvoke(
BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Xml,
ResponseFormat = WebMessageFormat.Xml)]
XmlElement GetAsXml(Stream input);
}

I am using fiddler to simulate a client. Here is what I am sending using fiddler:
POST http://127.0.0.1:60000/TestService.svc/GetAsXml HTTP/1.1
User-Agent: Fiddler
Host: 127.0.0.1:60000
Content-Length: 204

<TextBlock x:Name="text" IsHitTestVisible="false" Text="Hello" Foreground="black" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"/>

The response I get it is HTTP/1.1 415 Unsupported Media Type

If I add Content-Type: application/xml; charset=utf-8 to the request headers, I get the following response:
HTTP/1.1 400 Bad Request

Any ideas. All I want to do is parse the xml that is in the post request on the server

Rick Strahl
November 02, 2007

# re: WCF and JSON Services

Not sure. I haven't had any luck with the bare requests. But I think your problem is that you're not sending the proper content type. I'd guess you need to add Content-type: text/xml

Dan Finch
November 09, 2007

# re: WCF and JSON Services

try WebMessageBodyStyle.Bare

Rick Strahl
November 09, 2007

# re: WCF and JSON Services

I tried all of the various message formats, but none of them seemed to work. Some would just fail outright with exceptions and others still used the MS Ajax format.

Vish
January 12, 2008

# re: WCF and JSON Services

Have you tried using the WebHttpBehavior instead of WebScriptBehavior? Check out http://hyperthink.net/blog/2007/08/23/WebHttpBehavior+Vs+WebScriptBehavior.aspx

Rick Strahl
January 12, 2008

# re: WCF and JSON Services

Yes - this stuff works correctly in RTM. Those configuration behaviors and the factories effectively do the same thing with the behaviors being more configurable if needed. I'll have more on this in a bit.

Sean
May 07, 2008

# re: WCF and JSON Services

Hey Rick, I attended this years dev connections 2008 and I'm actually trying to throw together a presentation for my group at work based on the seminars I attended, your WCF/REST/JSON class being one of them. I beleive our app could benefit from these technologies so I've been putting together a power point and some demo apps... but for the life of me I cant get the WCF service reference working for some reason in my demo web site code, which is calling a fairly simple WCF (Json output) service.

Is there any way you could post a simple zipped up project including a really simple service / web site calling it, I imagine I'm doing something stupid .. but then again its almost 11pm and a couple captain and cokes later.

Thanks much either way,

Sean

Sean
May 07, 2008

# re: WCF and JSON Services

Whoops, ignore my last comment, just found your web log link to the dev connections sample code.

- Sean

Sean
May 07, 2008

# re: WCF and JSON Services

DOH! I have the same problem w/ your code... I get a client script error saying "AjaxService is undefined" I'm using studio 2008 team server on vista.

This concludes my web log comment spamming for the evening.

Any ideas?

Thanks

Sean

Mahesh A
December 02, 2008

# re: WCF and JSON Services

Please try

WebOperationContext.Current.OutgoingRequest.ContentType

Hellen
February 11, 2009

# re: WCF and JSON Services

I am getting this error.

Endpoints using 'UriTemplate' cannot be used with 'System.ServiceModel.Description.WebScriptEnablingBehavior'.

Rick Strahl
February 11, 2009

# re: WCF and JSON Services

@Hellen - Right. When you’re running in AJAX mode you can’t use UriTemplate(). Either remove the UriTemplate or remove the enableWebScript behavior from the configuration.

This post is a bit more detailed on configuration:
http://www.west-wind.com/WebLog/posts/310747.aspx

phucnd
March 12, 2009

# re: WCF and JSON Services

Have you got a demo project?
Please send me?

Sunny
April 22, 2009

# One way to work around...

Perhaphs instead of using:
result.d.Company

You could:
result = get_json(result);
var name = result.Company;

function get_json(result){
return result.d;
}

so that the item is isolated & does not spread throughout your code...

My 2 cents ;)

Rick Strahl
April 22, 2009

# re: WCF and JSON Services

@Sunny - yes of course. You'd want to create a wrapper for the JSON calls anyway because there are several issues you need to deal with in the JSON handling and error trapping and reporting which is vital.

There's a service wrapper client class that provides this functionality here:
http://www.west-wind.com/weblog/posts/324917.aspx

rpiz
November 17, 2009

# re: WCF and JSON Services

Hi..
I got error..

- using TimeTrakker; (could not be found)
- CustomerEntity could not be found

The first error..
is it should be the "TimeTrakker" is refer to the "namespace TimeTrakkerWeb"
So, if I change to "TimeTrakkerWeb" it ok...
The 2nd error I dont have any idea. Pls advice..
Thanks
-Json_Newbie-

Ricl O'Shay
December 14, 2009

# re: WCF and JSON Services

JAX-RS (and RESTEasy in JBoss or Jersey in Glassfish) provide a nice design example of how to expose REST services using any and all of the HTTP verbs in an almost effortless manner. You map a path prefix in web.config to point to the base service, then you add classes that append their own sub-resource (e.g., "exchange" in this case) and then each method has its own sub-resource. So you might say "GET /rest/exchange/yen?amount=500". The HTTP verb, query parameters, path parameters, and everything else is easily specified using a dirt simple annotation. Every MIME type is supported, coming and going (I show strings here but all the mime types are in an enumeration, you really use the enumerated value). If only Microsoft would do this!!!

@Produces("Text")
@Consumes("Text")
@Path("/exchange")
public interface CurrencyExchange
{
@GET
@Path("/yen")
public Decimal toYen(@QueryParam("amount") Decimal amt);
...
@POST
@Path("/rates")
@Produces("JSON")
public Rates getRates(@FormParam("date") date);
}

It's an absolute joy to use. No muck, no mire, just simplicity.

Jeremy Wood
April 15, 2010

# re: WCF and JSON Services

Had the same issue removing <enableWebScript /> from the endpoint behavior gets rid of it.

Akhil Gupta
July 07, 2010

# re: WCF and JSON Services

Hi i m using the following code -
<system.serviceModel>
<services>
<service name="AkhilWcfService.Service" behaviorConfiguration="ServiceBehavior">
<!-- Service Endpoints -->
<endpoint contract="AkhilWcfService.IService"
binding="webHttpBinding"
behaviorConfiguration="AjaxBehavior" />

</service>

</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="true"/>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->

</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="AjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Data;
using System.Web.Script.Services;
using System.ServiceModel.Web;

namespace AkhilWcfService
{
// NOTE: If you change the interface name "IService" here, you must also update the reference to "IService" in Web.config.
[ServiceContract(Namespace = "", Name = "Service")]
public interface IService
{
[OperationContract]
string Helloworld(string name);

}

}
-------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using AkhilWcfService;


public class Service : IService
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}


public string Helloworld(string name)
{
return "Hello World, " + name;
}

}
--------------------------------------------------------
& i m getting error as
Service 'Service' has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.
I don't know where is the problem
Please please help me.
I m looking for this heavily & from a long time.

Maulik
October 08, 2010

# re: WCF and JSON Services

Have you ever tried invoking HTTP GET service (which is web script enabled) from AJAX? Whenever I try to do that, AJAX still invokes it as POST service.

Rick Strahl
October 08, 2010

# re: WCF and JSON Services

@Maulik - sure you can send an AJAX request without POST. Just use the correct options on your AJAX client. With jQuery $.ajax() make sure method is GET.

Maulik
October 08, 2010

# re: WCF and JSON Services

Hey Rick,

Thanks for looking into this!!!

But I an using Javascript and <ScriptReference> like this:

  <asp:ScriptReference Path="http://localhost/RestFormat/wcf/MAPClass_WebScript.svc/rest/js" />


and then invoke it like this:

MAP._2010.Services.MAPClass_WebScript.IMAPClass2010_WebScript.WebScriptTest_WebScript('1234', 'xyz', onSuccess, onError);


When I see the log on server side. I found that it is always sending HTTP POST.

Does it make sense?

Rick Strahl
October 08, 2010

# re: WCF and JSON Services

Script references always use POST. All the MS AJAX client stuff does when talking to a service - they can't make plain REST calls (at least not the service oriented stuff).

Why would you need to? If you have REST calls use jQuery (or whatever other EASY HTTP client you want to use) to return the data and skip the Web Service interfaces.

Maulik
October 08, 2010

# re: WCF and JSON Services

Thanks Rick!!

Basically I am not the consumer of the service. I am providing infrastructure for hosting web services. I was trying to test HTTP GET web script enabled service (hosted in my application) from an AJAX client proxy and I ran into this problem.

I think because we are hosting web services, we cannot make any assumptions about how the consumers are going to use these services. That is the reason I am debating with myself whether I should support GET for web script enabled services or not.

What would be the best course of action according to you?

Rick Strahl
October 08, 2010

# re: WCF and JSON Services

You can implement dual interfaces - just create two endpoints one for POST and one for GET. Unfortunately WCF doesn't allow a single URL to handle this - you need separate URLs for this.

Frankly I wouldn't use WCF for that sort of thing. HTTP Handlers or even one of the other REST toolkits (or West Wind AJAX & Web Toolkit) which provide more flexibility on how data can be accessed off a specific URL. For example, in the WW Toolkit any service end points can be accessed via POST or GET.

Vaishali
January 06, 2011

# re: WCF and JSON Services

How are you using <enableWebScript> behaviour along with URITemplate. I am getting error if I do so "Requested Service cannot be activated" and the cause being "Endpoints using 'UriTemplate' cannot be used with 'System.ServiceModel.Description.WebScriptEnablingBehavior'"

Rick Strahl
January 07, 2011

# re: WCF and JSON Services

@Vaishali - you can't use the <enableWebScript> with any of the Http WCF attributes. WebScript implies MS AJAX style messaging which REQUIRES that message inputs are passed as JSON (with each parameter an object property) and via POST. In that case you only markup your methods with [OperationContract].

If you want raw REST style endpoints use the <webHttp /> key instead of <enableWebScript />.

                <webHttp defaultBodyStyle="Bare"
                          defaultOutgoingResponseFormat="Json"
                          faultExceptionEnabled="true"
                          helpEnabled ="true" />

Shabir Hakim
February 09, 2011

# re: WCF and JSON Services

You are simply Good.You do always something different...

Regards

Peter McClymont
January 10, 2012

# re: WCF and JSON Services

Hi, Thanks for the info.

I found a problem when I tried to implement this, I got this message in fiddler,

415 Cannot process the message because the content type 'application/json; charset=UTF-8' was not the expected type 'text/xml; charset=utf-8'.

I found the solution here,

http://meronymy.blogspot.com/2011/05/fixing-wcf-json-applicationjson-not.html

In the svc file I needed this,

Factory="System.ServiceModel.Activation.WebServiceHostFactory"

Did you need that for your solution?

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024