How to accept JSON in a WCF DataService?
Asked Answered
I

1

5

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.
Iamb answered 2/8, 2011 at 19:19 Comment(0)
I
8

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>
Incurrence answered 2/8, 2011 at 21:14 Comment(5)
My service doesn't seem to allow non-primitive types as parameters. I'm using an Entity Framework DataService (extends System.Data.Services.DataService<T>). If I have any kind of non-primitive in the arguments I get an exception: code... has a parameter 'MyNamespace.MyObj bv' of type 'MyNamespace.MyObj' which is not supported for service operations. Only primitive types are supported as parameters.codeIamb
That's an unfortunate shortcoming... If I were you I'd drop the DataService<T> unless you are getting some sort of other benefits out of it. I'll edit my post for a full code sample of what I use that works excellently for me.Incurrence
Good suggestion, wasn't sure if it was a limitation of the DataService<T>. As I understand it, I get automatic REST CRUD operations with that DataService (GET req's only). In this case, I don't want automatic CRUD since i need to perform some validation and business logic before persisting. I will try your approach and update here after.Iamb
This worked great for me. My problem was definitely due to extending DataService<T> and it's disappointing that it doesn't support object parameters for service method calls.Iamb
@Iamb That's actually a limitation of the OData standard, not of WCF Data Services per se. You can hack WCF Data Services to get at the post data. It's mentioned briefly in this relevant blog post by Glenn Gailey called "Uploading Data to a Service Operation": blogs.msdn.com/b/writingdata_services/archive/2011/07/05/… Having said that, such a hack is not recommended and therefore complex types should be serialized (also mentioned in the blog post).Indention

© 2022 - 2024 — McMap. All rights reserved.