40

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

更新 8/2/2012 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.
}
4

2 回答 2

41

我终于想出了解决这个问题的办法。这不是我想要的(这将是返回特定的对象类型,并以某种方式指示 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% 肯定在反序列化方面可能会遇到什么障碍。我假设 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));

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

于 2010-06-12T07:30:53.937 回答
11

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

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

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

jQuery ajax 调用 httpget webmethod (c#) 不工作

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

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

更新:在您的代码中定义方法SayHelloString()。它的结果是一个字符串。如果您调用该方法,该字符串将再被JSON 序列化一次。字符串的JSON 序列化{"Message":"Hello World"}是带引号的字符串(请参阅http://www.json.org/定义,不是对象,而是字符串)或完全是 string "{\"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吗?是性能提升吗?DateTime您是否需要像其他方式一样实现某些数据类型的序列化DataContractJsonSerializer?还是您选择的主要原因Json.Net是其他?

于 2010-06-12T01:03:35.607 回答