如何使用Json.Net从我的WCF休息服务(.NET 4)返回json,而不是字符串,用引号括起来?

时间:2022-08-29 16:05:16

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

更新2010年10月19日我知道我刚才问过这个问题,但这些答案中显示的解决方法难以令人满意,对许多人来说,这仍然是一个常见的问题。 WCF只是不灵活。我创建了自己的开源C#库,用于在没有WCF的情况下创建REST服务。检查restcake.net或rest.codeplex.com以获取有关所述库的信息。结束更新

UPDATE 8/2/2012 ASP.NET Web API (previously WCF Web API, the replacement for REST WCF) uses Json.NET by default END UPDATE

更新8/2/2012 ASP.NET Web API(以前是WCF Web API,REST WCF的替代品)默认情况下使用Json.NET END UPDATE

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

DataContractJsonSerializer无法处理Json.Net在正确配置(特别是循环)时处理得很好的许多场景。

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.

服务方法可以返回特定的对象类型(在本例中是DTO),在这种情况下将使用DataContractJsonSerializer,或者我可以让方法返回一个字符串,并使用Json.Net自己进行序列化。问题是,当我返回一个json字符串而不是一个对象时,发送到客户端的json用引号括起来。

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

使用DataContractJsonSerializer,返回特定的对象类型,响应为:{“Message”:“Hello World”}

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

使用Json.Net返回json字符串,响应为:“{\”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.

我不想在客户端上使用eval()或JSON.parse()结果,如果json以字符串形式返回,用引号括起来,这就是我必须要做的。我意识到这种行为是正确的;这不是我想要/需要的。我需要原始的json;当服务方法的返回类型是对象而不是字符串时的行为。

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?

那么,我怎样才能让我的方法返回一个对象类型,但不能使用DataContractJsonSerializer?我怎么能告诉它使用Json.Net序列化器呢?

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

或者,有没有直接写入响应流?所以我可以自己退回原始的json?没有包装报价?

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 个解决方案

#1


37  

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.

我终于找到了解决方案。这不是我想要的(这将是返回特定的对象类型,并以某种方式指示WCF使用Json.Net序列化器,而不是DataContractJsonSerializer),但它工作得很好,而且它很简单明了。

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.

请注意void的返回类型。我们不返回任何内容,因为它将使用DataContractJsonSerializer进行序列化。相反,我直接写入响应输出流。由于返回类型为void,因此处理管道不会将content-type设置为默认类型“application / json”,因此我将其显式设置。

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.

因为这使用了HttpContext,我猜它只会在你的服务类上有[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]时才会起作用,因为这会强制请求服务通过ASP.NET管道。没有asp.net兼容性,HttpContext将不可用,因为wcf托管应该是主机不可知的。

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.

使用此方法,结果在firebug中对于GET请求看起来很完美。更正内容类型,正确的内容长度和原始json,不包括在引号中。而且,我正在使用Json.Net获得我想要的序列化。两全其美。

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.

当我的服务方法将[DataContract]对象类型作为输入参数时,我不是100%肯定我可能遇到的关于反序列化的障碍。我假设DataContractJsonSerializer也将用于此。当我来到它时会越过那座桥......如果它造成了问题。到目前为止,还有我简单的DTO。

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:

更新请参阅Oleg的答案(UPDATE2部分)。他将服务方法的返回类型从void更改为System.ServiceModel.Channels.Message,而不是使用HttpContext.Current.Response.Write(),他使用:

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:

更新2还有另一种方法可以实现这一目标。将您的服务的返回类型从Message更改为Stream,并返回:

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.

我没有做过任何特定的测试,但对于可能返回大量数据的方法,这可能是更好的选择。我不知道这对非二进制数据是否重要。无论如何,一个想法。

#2


10  

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.

在我看来,你使用不正确的DataContractJsonSerializer。奇怪的是:您没有为公共SimpleMessage SayHelloObject()方法定义ResponseFormat = ResponseFormat.Json属性。

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.

此外,如果你在字符串中有{“Message”:“Hello World”}并在调试器中显示它,它将显示为“{\”Message \“:\”Hello World \“}”,所以就像你看到字符串一样json = JsonConvert.Serialize(message); (Json.Net)。所以在我看来,你在两种情况下都有相同的结果。

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

要验证这一点,请使用读取结果的客户端软件。看一些例子

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

JQuery ajax调用httpget webmethod(c#)无法正常工作

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

如果ContentType不是JSON,我可以从.asmx Web服务返回JSON吗?

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

如何构建要发送到AJAX WebService的JSON对象?

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.

更新:在您的代码中定义方法SayHelloString()。结果是一个字符串。如果你调用该方法,这个字符串将再次被JS​​ON序列化。字符串的JSON序列化{“Message”:“Hello World”}是一个带引号的字符串(参见http://www.json.org/定义,不是对象,而是字符串)或字符串“{\”Message \ “:\“你好,世界\”}”。因此,Web服务的两种方法都是正确的。

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

更新2:我很高兴我的回答“更新”部分的提示帮助你完成了双JSON序列化。

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

不过我建议你改变一点解决方案,以便更多地关注WCF概念。

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:

如果您想在WCF中实现Web响应的自定义编码(请参阅http://msdn.microsoft.com/en-us/library/ms734675.aspx),您的WCF方法最好返回Message而不是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:

你可以使用另一个Message formater:例如CreateStreamResponse(或其他一些参见http://msdn.microsoft.com/en-us/library/system.servicemodel.web.weboperationcontext_methods(v=VS.100).aspx)而不是CreateTextResponse。如果您想设置一些额外的HTTP标头或Http状态代码(例如,如果出现一些错误),您可以这样做:

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?

最后,我想从评论中重复我的问题:你能解释为什么要使用Json.Net而不是DataContractJsonSerializer吗?是性能提升?您是否需要像DataContractJsonSerializer那样以其他方式实现某些数据类型(如DateTime)的序列化?或者你选择Json.Net的主要原因是另外一些?

#1


37  

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.

我终于找到了解决方案。这不是我想要的(这将是返回特定的对象类型,并以某种方式指示WCF使用Json.Net序列化器,而不是DataContractJsonSerializer),但它工作得很好,而且它很简单明了。

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.

请注意void的返回类型。我们不返回任何内容,因为它将使用DataContractJsonSerializer进行序列化。相反,我直接写入响应输出流。由于返回类型为void,因此处理管道不会将content-type设置为默认类型“application / json”,因此我将其显式设置。

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.

因为这使用了HttpContext,我猜它只会在你的服务类上有[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]时才会起作用,因为这会强制请求服务通过ASP.NET管道。没有asp.net兼容性,HttpContext将不可用,因为wcf托管应该是主机不可知的。

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.

使用此方法,结果在firebug中对于GET请求看起来很完美。更正内容类型,正确的内容长度和原始json,不包括在引号中。而且,我正在使用Json.Net获得我想要的序列化。两全其美。

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.

当我的服务方法将[DataContract]对象类型作为输入参数时,我不是100%肯定我可能遇到的关于反序列化的障碍。我假设DataContractJsonSerializer也将用于此。当我来到它时会越过那座桥......如果它造成了问题。到目前为止,还有我简单的DTO。

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:

更新请参阅Oleg的答案(UPDATE2部分)。他将服务方法的返回类型从void更改为System.ServiceModel.Channels.Message,而不是使用HttpContext.Current.Response.Write(),他使用:

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:

更新2还有另一种方法可以实现这一目标。将您的服务的返回类型从Message更改为Stream,并返回:

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.

我没有做过任何特定的测试,但对于可能返回大量数据的方法,这可能是更好的选择。我不知道这对非二进制数据是否重要。无论如何,一个想法。

#2


10  

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.

在我看来,你使用不正确的DataContractJsonSerializer。奇怪的是:您没有为公共SimpleMessage SayHelloObject()方法定义ResponseFormat = ResponseFormat.Json属性。

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.

此外,如果你在字符串中有{“Message”:“Hello World”}并在调试器中显示它,它将显示为“{\”Message \“:\”Hello World \“}”,所以就像你看到字符串一样json = JsonConvert.Serialize(message); (Json.Net)。所以在我看来,你在两种情况下都有相同的结果。

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

要验证这一点,请使用读取结果的客户端软件。看一些例子

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

JQuery ajax调用httpget webmethod(c#)无法正常工作

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

如果ContentType不是JSON,我可以从.asmx Web服务返回JSON吗?

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

如何构建要发送到AJAX WebService的JSON对象?

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.

更新:在您的代码中定义方法SayHelloString()。结果是一个字符串。如果你调用该方法,这个字符串将再次被JS​​ON序列化。字符串的JSON序列化{“Message”:“Hello World”}是一个带引号的字符串(参见http://www.json.org/定义,不是对象,而是字符串)或字符串“{\”Message \ “:\“你好,世界\”}”。因此,Web服务的两种方法都是正确的。

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

更新2:我很高兴我的回答“更新”部分的提示帮助你完成了双JSON序列化。

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

不过我建议你改变一点解决方案,以便更多地关注WCF概念。

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:

如果您想在WCF中实现Web响应的自定义编码(请参阅http://msdn.microsoft.com/en-us/library/ms734675.aspx),您的WCF方法最好返回Message而不是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:

你可以使用另一个Message formater:例如CreateStreamResponse(或其他一些参见http://msdn.microsoft.com/en-us/library/system.servicemodel.web.weboperationcontext_methods(v=VS.100).aspx)而不是CreateTextResponse。如果您想设置一些额外的HTTP标头或Http状态代码(例如,如果出现一些错误),您可以这样做:

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?

最后,我想从评论中重复我的问题:你能解释为什么要使用Json.Net而不是DataContractJsonSerializer吗?是性能提升?您是否需要像DataContractJsonSerializer那样以其他方式实现某些数据类型(如DateTime)的序列化?或者你选择Json.Net的主要原因是另外一些?