views:

1236

answers:

2

UPDATE 10/19/2010 I know I asked this question a while ago, but the workarounds shown in these answers are hardly satisfactory, and this is still a common problem for many. WCF just isn't flexible. I started my own open source C# library for creating REST services without WCF. Check restcake.net or rest.codeplex.com for info on said library. END UPDATE

The DataContractJsonSerializer is unable to handle many scenarios that Json.Net handles just fine when properly configured (specifically, cycles).

A service method can either return a specific object type (in this case a DTO), in which case the DataContractJsonSerializer will be used, or I can have the method return a string, and do the serialization myself with Json.Net. The problem is that when I return a json string as opposed to an object, the json that is sent to the client is wrapped in quotes.

Using DataContractJsonSerializer, returning a specific object type, the response is:
{"Message":"Hello World"}

Using Json.Net to return a json string, the response is:
"{\"Message\":\"Hello World\"}"

I do not want to have to eval() or JSON.parse() the result on the client, which is what I would have to do if the json comes back as a string, wrapped in quotes. I realize that the behavior is correct; it's just not what I want/need. I need the raw json; the behavior when the service method's return type is an object, not a string.

So, how can I have my method return an object type, but not use the DataContractJsonSerializer? How can I tell it to use the Json.Net serializer instead?

Or, is there someway to directly write to the response stream? So I can just return the raw json myself? Without the wrapping quotes?

Here is my contrived example, for reference:

[DataContract]
public class SimpleMessage
{
    [DataMember]
    public string Message { get; set; }
}

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class PersonService
{
    // uses DataContractJsonSerializer
    // returns {"Message":"Hello World"}
    [WebGet(UriTemplate = "helloObject")]
    public SimpleMessage SayHelloObject()
    {
        return new SimpleMessage("Hello World");
    }

    // uses Json.Net serialization, to return a json string
    // returns "{\"Message\":\"Hello World\"}"
    [WebGet(UriTemplate = "helloString")]
    public string SayHelloString()
    {
        SimpleMessage message = new SimpleMessage() { Message = "Hello World" };
        string json = JsonConvert.Serialize(message);
        return json;
    }

    // I need a mix of the two.  Return an object type, but use the Json.Net serializer.
}
+2  A: 

Hello!

It seems to me that you use not correct DataContractJsonSerializer. What is strange is: you don't define ResponseFormat = ResponseFormat.Json attribute for the public SimpleMessage SayHelloObject() method.

Moreover if you have {"Message":"Hello World"} in a string and display it in debugger it will be display as "{\"Message\":\"Hello World\"}", so exactly like you see string json = JsonConvert.Serialize(message); (Json.Net). So it seems to me that you have in both cases the same results.

To verify this use a client software which read the results. See some examples

http://stackoverflow.com/questions/2651091/jquery-ajax-call-to-httpget-webmethod-c-not-working/2656543#2656543

http://stackoverflow.com/questions/2670147/can-i-return-json-from-an-asmx-web-service-if-the-contenttype-is-not-json/2671583#2671583

http://stackoverflow.com/questions/2737525/how-do-i-build-a-json-object-to-send-to-an-ajax-webservice/2738086#2738086

UPDATED: In your code you define method SayHelloString(). It's result are a string. If you call the method this string will be one more time JSON serialized. JSON serialization of the string {"Message":"Hello World"} is a quoted string (see http://www.json.org/ definition for not a object, but a string) or exactly string "{\"Message\":\"Hello World\"}". So everything is correct with both methods of your Web Service.

UPDATED 2: I am glad that my tip from "Update" part of my answer helped you to swich of the double JSON serialization.

Nevertheless I would recommend you to change a little the solution to stay more at the WCF concept.

If you want implement a custom encoding of the web responce in WCF (see http://msdn.microsoft.com/en-us/library/ms734675.aspx) your WCF method should better return Message instead of void:

[WebGet(UriTemplate = "hello")]
public Message SayHello()
{
    SimpleMessage message = new SimpleMessage() {Message = "Hello World"};
    string myResponseBody = JsonConvert.Serialize(message);
    return WebOperationContext.Current.CreateTextResponse (myResponseBody,
                "application/json; charset=utf-8",
                Encoding.UTF8);
}

You can of cause use another Message formater: for example CreateStreamResponse (or some other see http://msdn.microsoft.com/en-us/library/system.servicemodel.web.weboperationcontext_methods(v=VS.100).aspx) instead of CreateTextResponse. If you want to set some additional HTTP headers or Http status code (for example in case of some error) you can do this with this way:

OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
ctx.StatusCode = HttpStatusCode.BadRequest;

At the end I want repeat my question from a comment: could you explain why you want use Json.Net instead of DataContractJsonSerializer? Is it performance improvement? Do you need implement serialization of some data types like DateTime in other way as DataContractJsonSerializer do? Or the main reason of your choose of Json.Net is some other?

Oleg
In my web.config, my <standardEndpoint> has defaultOutgoingResponseFormat="Json", so I do not have to specify it in the attribute on the service method. I am using DataContractJsonSerializer correctly, and this is not an issue of how it is displayed in the debugger. I can inspect it in FireBug or Fiddler, and see not only the exact characters that are being sent, but also the content length, so I know just what is included. It is not the same result in both cases; it is what I described in the original question.
Samuel Meacham
The problem is if you goes to http://www.jsonlint.com/ (the site of one of creator of JSON) you can verify that `{"Message":"Hello World"}` is a valid JSON data, but `"{\"Message\":\"Hello World\"}"` is not. Try paste in http://www.jsonlint.com exact strings which you see in Fiddler/FireBug and you will see whether you have correct JSON. On http://www.json.org/ you will find the JSON specification.
Oleg
I realize that "{\"Message\":\"Hello World\"}" is not valid "raw" json. It's valid json, passed as a string, so it's wrapped in quotes. Were it to be passed to eval() or JSON.parse(), then it would evaluate just fine, since it *is* valid json. But, that is neither my issue, nor my question.
Samuel Meacham
What do you mean as you write "I can just return the raw json myself? Without the wrapping quotes?" Do you want probably return `{Message:"Hello World"}`? But is is wrong JSON (try in http://www.jsonlint.com/). How do you plan use the data on the client side? If you hold the standard you can just use standard serializer. If you want use compact data you can use binary serializer. If you want just log your object, you can enumerate all object properties and write there. But if you use "JSON" as a format name you have to hold the JSON specification http://www.json.org.
Oleg
Per your updated comment: I know that SayHelloString() returns a string. Perhaps you did not read the statement in the original question: "I realize that the behavior is correct". My point is that the only way to use the Json.net serializer is to return a string. Returning the SimpleMessage object directly will force the use of the DataContractJsonSerializer, which I don't want to use. Please read the question before commenting again: "How can I have my method return an object type, but not use the DataContractJsonSerializer? How can I tell it to use the Json.Net serializer instead?"
Samuel Meacham
Returing of string instead of `SimpleMessage` object don't switch off the `DataContractJsonSerializer`. The string will be also JSON encoded, like I written in the "Update" to my answer. In your own answer you fix the problam.
Oleg
Your first update didn't help me with the double encoding, I was already aware of that as I stated in the original question. However, your suggestion to return the Message type and use WebOperationContext.Current.CreateTextResponse() worked wonderfully, and I like it better than writing directly to the HttpContext.Current.Response output stream, so I have redacted my downvote, despite your constant misunderstandings of the original question, and repeated unrelated suggestions.
Samuel Meacham
And yes, the Json.Net serializer is more performant than the DataContractJsonSerializer, and gives me control over the serialization of dates (I'm using the IsoDateTimeConverter), and it allows me to serialize object graphs that contain cycles. All in, I have much more control over what I can serialize, and how it's done. This is all hidden in my data layer, however. My service layer doesn't even have a reference to Json.Net. I basically end up calling something like: entityObject.ToDto().ToJson(), and it's done internally in the data layer.
Samuel Meacham
OK. Interesting! The information about serialization of graphs that contain cycles is new for me. I have this constructs not, but probably I should look at Json.Net more carefully.By the way, the usage of `Massage` type can help with input parameters of Web Methods also.
Oleg
+1  A: 

I finally figured out a solution to this. It's not what I would have preferred (which would be to return the specific object type, and somehow instruct WCF to use a Json.Net serializer, instead of the DataContractJsonSerializer), but it is working great, and it's simple and clear.

Extending my contrived example using this new solution:

[WebGet(UriTemplate = "hello")]
public void SayHello()
{
    SimpleMessage message = new SimpleMessage() {Message = "Hello World"};
    string json = JsonConvert.Serialize(message);
    HttpContext.Current.Response.ContentType = "application/json; charset=utf-8";
    HttpContext.Current.Response.Write(json);
}

Note the return type of void. We do not return anything, since it would be serialized with DataContractJsonSerializer. Instead, I write directly to the response output stream. Since the return type is void, the processing pipeline doesn't set the content-type to the default type of "application/json", so I set it explicitly.

Because this uses HttpContext, I'm guessing it will only work if you have [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] on your service class, since that will force requests to the service to go through the ASP.NET pipeline. Without the asp.net compatibility, the HttpContext will not be available, since wcf hosting is supposed to be host agnostic.

Using this method, the results look perfect in firebug for GET requests. Correct content-type, correct content length, and raw json, not wrapped in quotes. And, I'm getting the serialization I want using Json.Net. Best of both worlds.

I'm not 100% positive of what obstacles I might run into regarding *de*serialization, when my service methods have [DataContract] object types as input parameters. I'm assuming the DataContractJsonSerializer will be used for that too. Will cross that bridge when I come to it...if it creates a problem. It hasn't so far, with my simple DTOs.

UPDATE See Oleg's answer (the UPDATE2 part). He changes the return type of the service method from void to System.ServiceModel.Channels.Message, and rather than using HttpContext.Current.Response.Write(), he uses:

return WebOperationContext.Current.CreateTextResponse (json,
    "application/json; charset=utf-8", Encoding.UTF8);

Which is indeed a better solution. Thank you Oleg.

UPDATE 2 There is yet another way of accomplishing this. Change your service's return type from Message to Stream, and return this:

WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));

I haven't done any specific tests, but it's possible that this would be a better choice for methods that could potentially return large amounts of data. I don't know if that matters for non-binary data though. Anyway, a thought.

Samuel Meacham