How to accept JSON in a WCF DataService?

codemonkey picture codemonkey · Aug 2, 2011 · Viewed 15.6k times · Source

I'm trying to understand how to use WCF Data Services (based on EF 4.1) to create a restful web service that will persist entities passed as JSON objects.

I've been able to create a method that can accept a GET request with a set of primitive data types as arguments. I don't like that solution, I would prefer to send a POST request with a JSON object in the http request body.

I've found that I can't get the framework to serialize the json into an object for me, but i would be fine with doing it manually.

My problem is that I can't seem to read the body of the POST request - the body should be the JSON payload.

Here's a rough crack at it below. I've tried a few different iterations of this and can't seem to get the raw JSON out of the request body.

Any thoughts? A better way to do this? I just want to POST some JSON data and process it.

    [WebInvoke(Method = "POST")]
    public void SaveMyObj()
    {
        StreamReader r = new StreamReader(HttpContext.Current.Request.InputStream);
        string jsonBody = r.ReadToEnd();  // jsonBody is empty!!

        JavaScriptSerializer jss = new JavaScriptSerializer();
        MyObj o = (MyObj)jss.Deserialize(jsonBody, typeof(MyObj));

        // Now do validation, business logic, and persist my object
    }

My DataService is an Entity Framework DataService that extends

System.Data.Services.DataService<T>

If I try adding non-primitive values as parameters to the method, i see the following exception in the trace log:

System.InvalidOperationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
'Void SaveMyObj(MyNamespace.MyObj)' has a parameter 'MyNamespace.MyObj o' of type 'MyNamespace.MyObj' which is not supported for service operations. Only primitive types are supported as parameters.

Answer

Anthony Sottile picture Anthony Sottile · Aug 2, 2011

Add parameters to your method. You'll also want some additional attributes on your WebInvoke.

Here's an example (from memory so it might be a little off)

[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "modifyMyPerson")]
public void Modify(Person person) {
   ...
}

With person class something like this:

[DataContract]
public class Person {

[DataMember(Order = 0)]
public string FirstName { get; set; }

}

And json sent like this

var person = {FirstName: "Anthony"};
var jsonString = JSON.stringify({person: person});
// Then send this string in post using whatever, I personally use jQuery

EDIT: This is using "wrapped" approach. Without wrapped approach you would take out the BodyStyle = ... and to stringify the JSON you would just do JSON.stringify(person). I just usually use the wrapped methodology in case I ever need to add additional parameters.

EDIT For full code sample

Global.asax

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace MyNamespace
{
    public class Global : HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.Add(new ServiceRoute("myservice", new WebServiceHostFactory(), typeof(MyService)));
        }
    }
}

Service.cs

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace MyNamespace
{
    [ServiceContract]
    [ServiceBehavior(MaxItemsInObjectGraph = int.MaxValue)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class MyService
    {
        [OperationContract]
        [WebInvoke(UriTemplate = "addObject", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        public void AddObject(MyObject myObject)
        {
            // ...
        }

        [OperationContract]
        [WebInvoke(UriTemplate = "updateObject", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        public void UpdateObject(MyObject myObject)
        {
            // ...
        }

        [OperationContract]
        [WebInvoke(UriTemplate = "deleteObject", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        public void DeleteObject(Guid myObjectId)
        {
            // ...
        }
    }
}

And add this to Web.config

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  </system.serviceModel>