Search code examples
c#jsonwcf-data-servicesef4-code-only

How to accept JSON in a WCF DataService?


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.

Solution

  • 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>