简体   繁体   English

WCF RESTful JSON Web服务客户端中的单元素数组

[英]Single element array in WCF RESTful JSON web service client

I'm trying to consume a RESTful JSON web service using WCF on the client side. 我正在尝试在客户端使用WCF消耗RESTful JSON Web服务。 The service is 3rd party, so I cannot make any changes to the server response. 该服务是第三方,因此我无法对服务器响应进行任何更改。

The server is sending back a response that looks something like this when there's only one data point... 当只有一个数据点时,服务器将发送回响应,如下所示:

Single Data Point 单数据点

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

and something like this when there's more than one data point... 当数据点不止一个时

Multiple Data Points 多个数据点

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

I have my service contract set up like this... 我已经建立了这样的服务合同...

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

and a data point's data contract like this... 以及数据点的数据合同是这样的...

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

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

and the only way I can get the single data point response to work is if I have a single instance property like this, but this does not parse the multiple data point response... 而让单数据点响应正常工作的唯一方法是,如果我有这样的单实例属性,但这不会解析多数据点响应...

Response for Single Instance 单实例响应

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

and the only way I can get the multiple data point response to work is if I have an array / list instance property like this, but this does not parse the single data point response... 而我可以获得多数据点响应的唯一方法是,如果我有这样的数组/列表实例属性,但这不会解析单个数据点响应...

Response for Multiple Instance 多实例响应

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

I understand the issue is that the response is omitting the brackets when there's only one data point returned, but it seems that WCF doesn't play well with deserializing that syntax. 我知道问题在于,当仅返回一个数据点时,响应会省略括号,但是WCF似乎在反序列化该语法方面表现不佳。 Is there some way I can tell the DataContractJsonSerializer to allow single element arrays to not include brackets and then tell my service to use that serializer? 有什么办法可以告诉DataContractJsonSerializer允许单元素数组不包含方括号,然后告诉我的服务使用该序列化程序? Maybe a service behavior or something? 也许是一种服务行为?

Any direction would be helpful. 任何方向都会有所帮助。

You can use a custom message formatter to change the deserialization of the JSON into the data contract you want. 您可以使用自定义消息格式化程序将JSON的反序列化更改为所需的数据协定。 In the code below, the data contract is defined to have a List<MyData> ; 在下面的代码中,数据协定定义为具有List<MyData> if the response contains only one data point, it will "wrap" that into an array prior to passing to the deserializer, so it will work for all cases. 如果响应仅包含一个数据点,它将在传递给解串器之前将其“包装”到一个数组中,因此它将适用于所有情况。

Notice that I used the JSON.NET library to do the JSON modification, but that's not a requirement (it just has a nice JSON DOM to work with the JSON document). 请注意,我使用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();
    }
}

I don't know about using WCF so I'll change to Asp.Net WCF. 我不知道使用WCF,所以我将更改为Asp.Net WCF。 Here is an article that will get you one the way 这是一篇文章,可以助您一臂之力

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

I just can't figure out how to determine if it's an array or a single object. 我只是不知道如何确定它是数组还是单个对象。 Here is a little code. 这是一些代码。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM