How can I return json from my WCF rest service (.NET 4), using Json.Net, without it being a string, wrapped in quotes?

Solution 1:

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 deserialization, 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.

Solution 2:

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

JQuery ajax call to httpget webmethod (c#) not working

Can I return JSON from an .asmx Web Service if the ContentType is not JSON?

How do I build a JSON object to send to an AJAX WebService?

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?