1

我正在尝试在客户端使用 WCF 使用 RESTful JSON Web 服务。该服务是第 3 方,因此我无法对服务器响应进行任何更改。

当只有一个数据点时,服务器正在发回一个看起来像这样的响应......

单个数据点

{
  "Data":
  {
    "MyPropertyA":"Value1",
    "MyPropertyB":"Value2"
  },
}

当有多个数据点时会出现类似的情况......

多个数据点

{
  "Data":
  [
    {
      "MyPropertyA":"Value1",
      "MyPropertyB":"Value2"
    },
    {
      "MyPropertyA":"Value3",
      "MyPropertyB":"Value4"
    },
    {
      "MyPropertyA":"Value5",
      "MyPropertyB":"Value6"
    }
  ],
}

我的服务合同是这样设置的...

[ServiceContract]
public interface IRewardStreamService
{
    [OperationContract]
    [WebInvoke]
    MyResponse GetMyStuff();
}

和这样的数据点的数据合同......

[DataContract]
public class MyData
{
  [DataMember]
  public string MyPropertyA { get; set; }

  [DataMember]
  public string MyPropertyB { get; set; }
}

我可以让单个数据点响应工作的唯一方法是如果我有一个像这样的单个实例属性,但这不会解析多个数据点响应......

单例响应

[DataContract]
public class MyResponse
{
  [DataMember]
  public MyData Data { get; set; }
}

我可以让多个数据点响应工作的唯一方法是如果我有一个这样的数组/列表实例属性,但这不会解析单个数据点响应......

多实例响应

[DataContract]
public class MyResponse
{
  [DataMember]
  public IList<MyData> Data { get; set; }
}

我理解问题在于,当只返回一个数据点时,响应会省略括号,但 WCF 似乎不能很好地反序列化该语法。有什么方法可以告诉 DataContractJsonSerializer 允许单元素数组不包含括号,然后告诉我的服务使用该序列化程序?也许是服务行为或什么?

任何方向都会有所帮助。

4

2 回答 2

1

您可以使用自定义消息格式化程序将 JSON 的反序列化更改为您想要的数据协定。在下面的代码中,数据合约被定义为具有List<MyData>; 如果响应只包含一个数据点,它将在传递给反序列化器之前将其“包装”到一个数组中,因此它适用于所有情况。

请注意,我使用 JSON.NET 库来进行 JSON 修改,但这不是必需的(它只是有一个很好的 JSON DOM 来处理 JSON 文档)。

public class StackOverflow_12825062
{
    [ServiceContract]
    public class Service
    {
        [WebGet]
        public Stream GetData(bool singleDataPoint)
        {
            string result;
            if (singleDataPoint)
            {
                result = @"{ 
                  ""Data"": 
                  { 
                    ""MyPropertyA"":""Value1"", 
                    ""MyPropertyB"":""Value2"" 
                  }, 
                }";
            }
            else
            {
                result = @"{ 
                  ""Data"": 
                  [ 
                    { 
                      ""MyPropertyA"":""Value1"", 
                      ""MyPropertyB"":""Value2"" 
                    }, 
                    { 
                      ""MyPropertyA"":""Value3"", 
                      ""MyPropertyB"":""Value4"" 
                    }, 
                    { 
                      ""MyPropertyA"":""Value5"", 
                      ""MyPropertyB"":""Value6"" 
                    } 
                  ], 
                } ";
            }

            WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
            return new MemoryStream(Encoding.UTF8.GetBytes(result));
        }
    }
    [DataContract]
    public class MyData
    {
        [DataMember]
        public string MyPropertyA { get; set; }

        [DataMember]
        public string MyPropertyB { get; set; }
    }
    [DataContract]
    public class MyResponse
    {
        [DataMember]
        public List<MyData> Data { get; set; }

        public override string ToString()
        {
            return string.Format("MyResponse, Data.Length={0}", Data.Count);
        }
    }
    [ServiceContract]
    public interface ITest
    {
        [WebGet]
        MyResponse GetData(bool singleDataPoint);
    }
    public class MyResponseSingleOrMultipleClientReplyFormatter : IClientMessageFormatter
    {
        IClientMessageFormatter original;
        public MyResponseSingleOrMultipleClientReplyFormatter(IClientMessageFormatter original)
        {
            this.original = original;
        }

        public object DeserializeReply(Message message, object[] parameters)
        {
            WebBodyFormatMessageProperty messageFormat = (WebBodyFormatMessageProperty)message.Properties[WebBodyFormatMessageProperty.Name];
            if (messageFormat.Format == WebContentFormat.Json)
            {
                MemoryStream ms = new MemoryStream();
                XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(ms);
                message.WriteMessage(jsonWriter);
                jsonWriter.Flush();
                string json = Encoding.UTF8.GetString(ms.ToArray());
                JObject root = JObject.Parse(json);
                JToken data = root["Data"];
                if (data != null)
                {
                    if (data.Type == JTokenType.Object)
                    {
                        // single case, let's wrap it in an array
                        root["Data"] = new JArray(data);
                    }
                }

                // Now we need to recreate the message
                ms = new MemoryStream(Encoding.UTF8.GetBytes(root.ToString(Newtonsoft.Json.Formatting.None)));
                XmlDictionaryReader jsonReader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max);
                Message newMessage = Message.CreateMessage(MessageVersion.None, null, jsonReader);
                newMessage.Headers.CopyHeadersFrom(message);
                newMessage.Properties.CopyProperties(message.Properties);
                message = newMessage;
            }

            return this.original.DeserializeReply(message, parameters);
        }

        public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
        {
            throw new NotSupportedException("This formatter only supports deserializing reply messages");
        }
    }
    public class MyWebHttpBehavior : WebHttpBehavior
    {
        protected override IClientMessageFormatter GetReplyClientFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
        {
            IClientMessageFormatter result = base.GetReplyClientFormatter(operationDescription, endpoint);
            if (operationDescription.Messages[1].Body.ReturnValue.Type == typeof(MyResponse))
            {
                return new MyResponseSingleOrMultipleClientReplyFormatter(result);
            }
            else
            {
                return result;
            }
        }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        WebServiceHost host = new WebServiceHost(typeof(Service), new Uri(baseAddress));
        host.Open();
        Console.WriteLine("Host opened");

        ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new WebHttpBinding(), new EndpointAddress(baseAddress));
        factory.Endpoint.Behaviors.Add(new MyWebHttpBehavior());
        ITest proxy = factory.CreateChannel();

        Console.WriteLine(proxy.GetData(false));
        Console.WriteLine(proxy.GetData(true));

        Console.Write("Press ENTER to close the host");
        ((IClientChannel)proxy).Close();
        factory.Close();
        Console.ReadLine();
        host.Close();
    }
}
于 2012-10-13T17:01:29.570 回答
0

我不知道如何使用 WCF,所以我将更改为 Asp.Net WCF。这是一篇文章,它将为您提供帮助

http://www.west-wind.com/weblog/posts/2012/Aug/30/Using-JSONNET-for-dynamic-JSON-parsing

我只是不知道如何确定它是数组还是单个对象。这是一个小代码。

    [TestMethod]
    public void SingleObject()
    {
        using (var client = new HttpClient())
        {
            var result = client.GetStringAsync("http://localhost:8080/api/JSONTestOne");
            string content = result.Result;
            JObject jsonVal = JObject.Parse(content);
            dynamic aFooObj = jsonVal;
            Console.WriteLine(aFooObj.afoo.A);
        }
    }

    [TestMethod]
    public void ArrayWithObject()
    {
        using (var client = new HttpClient())
        {
            var result = client.GetStringAsync("http://localhost:8080/api/JSONTest");
            string content = result.Result;
            JObject jsonVal = JObject.Parse(content);
            dynamic foos = jsonVal;
            Console.WriteLine(foos[0].A);
        }
    }
于 2012-10-10T23:59:49.120 回答