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

Posted

技术标签:

【中文标题】如何使用 Json.Net 从我的 WCF 休息服务(.NET 4)返回 json,而不是用引号括起来的字符串?【英文标题】:How can I return json from my WCF rest service (.NET 4), using Json.Net, without it being a string, wrapped in quotes? 【发布时间】:2011-03-02 21:36:11 【问题描述】:

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

2012 年 8 月 2 日更新 ASP.NET Web API(以前的 WCF Web API,REST WCF 的替代品)默认使用 Json.NET 结束更新

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

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

使用DataContractJsonSerializer,返回具体的对象类型,响应为:"Message":"Hello World"

使用Json.Net返回一个json字符串,响应为:"\"Message\":\"Hello World\""

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

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

或者,有没有办法直接写入响应流?所以我可以自己返回原始 json 吗?没有包装引号?

这是我做的例子,供参考:

[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.

【问题讨论】:

我不确定我理解您为什么要使用任何序列化。如果您希望在 WCF 服务中以 JSON 形式返回对象,您只需在合同中将响应格式设置为 WebMessageFormat.Json Cody DataContract 糟透了。你不能做关键值,而且日期被搞砸了,没有回报。它也较慢,但这并不重要 【参考方案1】:

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

使用这个新解决方案扩展我的人为示例:

[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);

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

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

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

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

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

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

这确实是一个更好的解决方案。谢谢奥列格。

更新 2 还有另一种方法可以做到这一点。将服务的返回类型从 Message 更改为 Stream,并返回:

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

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

【讨论】:

【参考方案2】:

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

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

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

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?

更新:在您的代码中定义方法SayHelloString()。它的结果是一个字符串。如果您调用该方法,此字符串将被再次 JSON 序列化。字符串"Message":"Hello World" 的 JSON 序列化是一个带引号的字符串(请参阅http://www.json.org/ 定义,不是对象,而是字符串)或完全是字符串"\"Message\":\"Hello World\""。所以你的 Web 服务的两种方法都是正确的。

更新 2:我很高兴我的回答中“更新”部分的提示帮助您切换了双 JSON 序列化。

尽管如此,我还是建议您对解决方案稍作改动,以便更多地遵循 WCF 概念。

如果您想在 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);

您当然可以使用另一个消息格式化程序:例如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;

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

【讨论】:

在我的 web.config 中,我的 有 defaultOutgoingResponseFormat="Json",所以我不必在服务方法的属性中指定它。我正确使用了 DataContractJsonSerializer,这不是它在调试器中如何显示的问题。我可以在 FireBug 或 Fiddler 中检查它,不仅可以查看正在发送的确切字符,还可以查看内容长度,因此我知道其中包含什么。两种情况下的结果都不一样;这是我在原始问题中描述的。 问题是如果您访问jsonlint.com(JSON 的创建者之一的站点),您可以验证"Message":"Hello World" 是有效的JSON 数据,但"\"Message\":\"Hello World\"" 不是。尝试粘贴您在 Fiddler/FireBug 中看到的 jsonlint.com 确切字符串,您将看到您是否有正确的 JSON。在json.org,您将找到 JSON 规范。 我意识到 "\"Message\":\"Hello World\"" 不是有效的“原始”json。它是有效的 json,作为字符串传递,所以用引号括起来。如果将它传递给 eval() 或 JSON.parse(),那么它的评估就很好,因为它 is 是有效的 json。但是,这既不是我的问题,也不是我的问题。 当你写“我可以自己返回原始 json?没有包装引号?”时,你是什么意思?你想大概返回Message:"Hello World"吗?但是 JSON 是错误的(试试jsonlint.com)。您计划如何在客户端使用数据?如果您持有标准,则可以使用标准序列化程序。如果你想使用紧凑的数据,你可以使用二进制序列化器。如果您只想记录您的对象,您可以枚举所有对象属性并在那里写入。但如果您使用“JSON”作为格式名称,您必须遵守 JSON 规范 json.org。 好的。有趣的!关于包含循环的图的序列化信息对我来说是新的。我没有这个结构,但可能我应该更仔细地查看 Json.Net。顺便说一句,Massage 类型的使用也可以帮助 Web 方法的输入参数。

以上是关于如何使用 Json.Net 从我的 WCF 休息服务(.NET 4)返回 json,而不是用引号括起来的字符串?的主要内容,如果未能解决你的问题,请参考以下文章

Angular 4 两次调用 WCF REST

使用 WCF Web Api 大量返回动态类型/Expandos?

WCF 休息服务.. 将数据表转换为 XML

WCF REST 服务的 JSON.NET 序列化程序

WCF 休息和 Wcf 肥皂服务之间的区别

为啥 WCF 休息服务(不是使用 WCF 休息服务模板创建的)不起作用?