9

at the project I am working on, we have a requirement to have a DataContract that can contain some undefined JSON.

The DataMember is some JSON that makes sense to the client only. We want to allow the client to send us json we do not know about.

Example:

public class Contract
{
    [DataMember]
    public int clientId;
    [DataMember]
    public string json;
}

Obviously, having a contract defined like that would require the client to escape the json like this:

{
    "clientId":1,
    "json": "{\"test\":\"json\"}"
}

Obviously, this is not what we need. The json the client should sent us should look like this:

{
    "clientId":1,
    "json": {"test":"json"}
}

Possible solutions we investigated:

  1. use Stream as contract parameters for the request's body. Works but puts the work on our side instead of using the framework.
  2. Defining "json" as a DynamicObject. Does not work. Was not able to get the property written properly.
  3. Using Newtonsoft library, change the default contract serializer in the WCF endpoint to serialize all inputs to a JObject. We mst also handle serialization on the request, and it causes problems in our application. We'd prefer to avoid this way.

Does anyone have a possible solution to this problem?

EDIT

The service offers rest json resources. It defines a single endpoint with webHttpBinding. The operation is defined like this (stripped down for simplicity):

[WebInvoke(Method = "POST", UriTemplate = "...", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
[OperationContract]
Stream Create(Contract c);

Also, the service is decorated with following attribute:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]

Thank you. JF

3
  • Sounds like your client is consuming your service output as soap/XML and you're trying to tunnel JSON strings through the soap message. Have you considered keeping separate endpoints for soap/XML and the JSON response. You could keep the standard DataContact endpoint in your current binding as-is and add a new WebHttpBinding endpoint to respond with the JSON string. Yeah, it adds a call but it'll simplify your contracts.
    – Sixto Saez
    Commented Oct 16, 2012 at 14:50
  • @Sixto Saez: We do no offer soap. We have 1 endpoint with webHttpBinding to support REST json only. Commented Oct 16, 2012 at 15:15
  • will you be using that undefined JSON that the client had sent in the request?
    – Igarioshka
    Commented Oct 16, 2012 at 15:56

5 Answers 5

4

WCF (as of 4.5) doesn't support deserializing arbitrary JSON as part of a data contract. You'll need to use another serializer which does that - JSON.NET is one which I personally like. To be able to change the serializer, you can use a different message formatter, and in the post at https://github.com/microsoftarchive/msdn-code-gallery-community-s-z/tree/master/Supporting%20different%20data%20and%20serialization%20formats%20in%20WCF I have a sample which does exactly that - replaces the default serialization used by WCF with JSON.NET.

Notice that to receive arbitrary JSON using that library, you'll need to change the type of the "json" property to the equivalent of arbitrary JSON in JSON.NET, JToken:

public class Contract 
{ 
    [DataMember] 
    public int clientId; 
    [DataMember] 
    public Newtonsoft.Json.Linq.JToken json; 
} 
2
  • Hi Carlos, I looked a lot at your blog for my problem even before asking this question. ;) I already tried this route, implementing my own message formatter, but it implicates some problems in our application. I'd like to find another way if possible. Otherwise, I'll end up changing the formatter. Thanks for your blog by the way, I read a lot of your posts on WCF extensibility in the last months. Commented Oct 19, 2012 at 18:36
  • 1
    Link was down I changed it to Microsoft official GitHub archive of Msdn Blog archive. In this case it's better than a web archive link as it provide the whole code solution too Commented Oct 1, 2020 at 9:03
0

Do yo configured the class and the method with these tags? Before class implementation

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class xxx {...}

Before method implementation

[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public Contract getContract() {...}
2
  • the AspNetCompatibilityRequirements attribute is on our contract instance, and the operation is decorated with the [OperationContract] and [WebInvoke(Method = "POST" UriTemplate="....", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare Commented Oct 16, 2012 at 15:15
  • A roundtrip to this could be declare your class property named 'json' as an object instead of a string. That object must be a class including one unique property: public string test {get;set;}
    – SamuGG
    Commented Oct 16, 2012 at 15:44
0

Have you tried this?

public class Contract 
{ 
    [DataMember] 
    public int clientId; 
    [DataMember] 
    public Dictionary<string,string> DynamicProperties; 
}
1
  • this would give only flat objects... think of {students:[{name:"john", grade:54},{name:"michelle", grade:95}], class:"Eco 1o1"}
    – Tomer W
    Commented Jan 1, 2017 at 15:16
0

Change the signature to accept stream. eg:

public String ProcessJson(Stream jsondata)
{
//play with jsondata

}

To receive as stream, override WebContentTypeMapper methods.

 public class RawWebContentTypeMapper : WebContentTypeMapper 
    {
        public override WebContentFormat GetMessageFormatForContentType(string contentType)
        {            
                return WebContentFormat.Raw;       
        }   
    }
-1

You don't want 'json' property to contain a string, you want it to containt an object. Like this:

public class Contract
{
    [DataMember]
    public int clientId;
    [DataMember]
    public JsonObj json;
}
public class JsonObj
{
    [DataMember]
    public string test;
}

That way the json parser will output what you need. I hope I made myself clear.

Cheers!

1
  • Unfortunately, that won't do it. For a client it would work, but another might post some completely different json. some like: { "clientId":1, "json": {"id":13, "ref":"theOther"} } Commented Oct 16, 2012 at 17:41

Not the answer you're looking for? Browse other questions tagged or ask your own question.