C#WCF REST客户端JSON数组 [英] C# WCF REST client JSON array

查看:133
本文介绍了C#WCF REST客户端JSON数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此处的示例(http://stackoverflow.com/questions/835839/client-configuration-to-consume-wcf-json-web-service)简单明了.我创建了WCF RESTFUL JSON服务,该服务返回简单对象的数组.在JavaScript中,我可以轻松处理返回的内容,因此我的Web客户端可以正常工作.

The example here (http://stackoverflow.com/questions/835839/client-configuration-to-consume-wcf-json-web-service) is straightforward and simple. I have created a WCF RESTFUL JSON service that returns an array of simple objects. In JavaScript, I have no problem dealing with what is returned, and my Web client therefore works fine.

我希望可以从Web或C#客户端调用此服务,以便可以在多个上下文中使用公开的数据,而不必重复获取数据的代码.

I want to make this service callable from either web or C# clients so that the data that is exposed can be used in multiple contexts without having to duplicate the code that gets the data.

在我的C#代码中,我具有代理客户端,接口协定和基于配置的绑定定义.我在C#服务和C#客户端(用作测试验证工具的简单控制台应用程序)之间共享接口和数据代码模块.

In my C# code, I have the proxy client, the interface contract, and the config based binding definitions. I share the interface and data code modules between my C# service and my C# client (a simple console app to use as a test verification harness).

在C#客户端上,非数组形式的数据又回来了.一旦我使服务返回对象数组(无论是作为裸数组返回还是作为简单对象的包装属性),客户端上的序列化程序都会静默失败并返回一个空数组.

On the C# client, the data that is not in array form comes back great. As soon as I make my service return an array of objects, either as a bare array return or as a wrapped property on a simple object, the serializer on the client side silently fails and returns an empty array.

这是我在顶层的客户代码

Here is my client code at the top level

 // for managed code to call our Ajax/Json incident service, we need to reuse the interface contract, 
 // and use the ServiceModel.Channels infra to hand-code a proxy client.
 public class IncidentClient : ClientBase<IIncidentServices.IGetActiveIncidents>, IIncidentServices.IGetActiveIncidents
 {
    public incidents GetActiveIncidents(string environmentAbbreviation)
    {
        return base.Channel.GetActiveIncidents(environmentAbbreviation);
    }
 }

 class Program
 {
    static void Main(string[] args)
    {


        IncidentClient client = new IncidentClient();

        incidents data = client.GetActiveIncidents("prod");
        Console.Write("Call to GetActiveIncidents returned ");
        if (null == data)
        {
            Console.WriteLine("no data (null)");
        }
        else
        {
            Console.WriteLine(data.incidentList.Count.ToString() + " rows of incident data.");
        }
        Console.WriteLine("\nPress any key to continue...");
        Console.ReadLine();
    }
 }
}

代码运行时,它总是告诉我零行,调试器告诉我返回了一个空数组.我已经通过激活日志记录来跟踪与WCF跟踪工具交换的消息,并且可以看到我的数据返回了(数组中有100个元素.

When the code runs, it ALWAYS tells me that there were zero rows and the debugger shows me that an empty array returns. I have traced into the messages being exchanged with the WCF trace tools by activating logging, and I can see my data coming back (100 elements in the array.

棘手的部分是序列化程序只是默默地丢弃了数据-这使我想知道是否应该放弃基于WCF clientBase的代理并使用原始HTTP get和一个单独的JSON解析器来处理数据.

The tricky part is that the serializer just silently throws the data away - and that is causing me to wonder if I should just abandon the WCF clientBase based proxy and use raw HTTP get, and a separate JSON parser to deal with the data.

我的数据合同如下:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Dan.Test.Incident.Data
{

[DataContract(Name="incidents", Namespace="Dan.Test.Incident.Data")]
public class incidents
{
    public incidents()
    {
            data = new List<incidentData>();
    }

    [DataMember(Name="incidentList")]
    private List<incidentData> data;

    [IgnoreDataMember]
    public List<incidentData> incidentList
    {
        get {
            if (null == data)
            {
                data = new List<incidentData>();
            }
            return data;
        }
    }
}

[DataContract(Name="incidentData", Namespace="Dan.Test.Incident.Data")]
[Serializable]
public class incidentData
{
    // define incident members and accessors for read-only get operations

    [DataMember(Name = "irNumber")]
    private string m_irNumber = null;       // the incident identifier as IR12345, etc.

    [DataMember(Name = "title")]
    private string m_title = null;          // the title of the incident

    [DataMember(Name = "devname")]
    private string m_devname = null;      // list of team members who were engaged

    [DataMember(Name = "description")]
    private string m_description = null;    // description of the incident

    [DataMember(Name = "startdate")]
    private DateTime m_startdate;

    [DataMember(Name = "priority")]
    private int m_priority = 0;

    [DataMember(Name = "environmentID")]
    private int m_environmentID = 0;

    [DataMember(Name = "status")]
    private string m_status;

    [DataMember(Name = "enddate")]
    private DateTime m_enddate;

    public incidentData()
    {
    }
}
}

我的界面定义是

using Dan.Test.Incident.Data;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace IIncidentServices
{
[ServiceContract(Namespace = "Dan.Test.Incident.Data")]
public interface IGetActiveIncidents
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    incidents GetActiveIncidents(string environmentAbbreviation);
}
}

我的配置很简单:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
    <sources>
        <source name="System.ServiceModel.MessageLogging" switchValue="Error,ActivityTracing">
            <listeners>
                <add type="System.Diagnostics.DefaultTraceListener" name="Default">
                    <filter type="" />
                </add>
                <add name="ServiceModelMessageLoggingListener">
                    <filter type="" />
                </add>
            </listeners>
        </source>
        <source propagateActivity="true" name="System.ServiceModel" switchValue="Verbose,ActivityTracing">
            <listeners>
                <add type="System.Diagnostics.DefaultTraceListener" name="Default">
                    <filter type="" />
                </add>
                <add name="ServiceModelTraceListener">
                    <filter type="" />
                </add>
            </listeners>
        </source>
    </sources>
    <sharedListeners>
        <add initializeData="c:\users\danro\documents\visual studio 2012\projects\gadgetactiveincidentservice\consoleapplication1\app_messages.svclog"
            type="System.Diagnostics.XmlWriterTraceListener, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
            <filter type="" />
        </add>
        <add initializeData="c:\users\danro\documents\visual studio 2012\projects\gadgetactiveincidentservice\consoleapplication1\app_tracelog.svclog"
            type="System.Diagnostics.XmlWriterTraceListener, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            name="ServiceModelTraceListener" traceOutputOptions="Timestamp">
            <filter type="" />
        </add>
    </sharedListeners>
</system.diagnostics>
<startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<diagnostics>
  <messageLogging logEntireMessage="true" logMalformedMessages="true"
    logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true"
    maxMessagesToLog="3000" maxSizeOfMessageToLog="100000" />
  <endToEndTracing messageFlowTracing="true" />
</diagnostics>
<bindings>
  <webHttpBinding>
    <binding name="NewBinding2" openTimeout="00:05:00" receiveTimeout="00:50:00"
      sendTimeout="00:05:00" hostNameComparisonMode="WeakWildcard"
      maxBufferSize="2000000" maxBufferPoolSize="2000000" maxReceivedMessageSize="2000000"
      useDefaultWebProxy="false" contentTypeMapper="">
      <readerQuotas maxDepth="32" maxStringContentLength="100000" maxArrayLength="10000"
        maxBytesPerRead="2000000" maxNameTableCharCount="100000" />
    </binding>
  </webHttpBinding>
</bindings>
<behaviors>
  <endpointBehaviors>
    <behavior name="webwcf">
      <webHttp defaultBodyStyle="WrappedRequest" defaultOutgoingResponseFormat="Json" 
        automaticFormatSelectionEnabled="true" faultExceptionEnabled="true" />
    </behavior>
  </endpointBehaviors>
</behaviors>
<client>
  <endpoint address="http://localhost/GadgetActiveIncidentService/ActiveIncidentService.svc"
    behaviorConfiguration="webwcf" binding="webHttpBinding" bindingConfiguration="NewBinding2"
    contract="IIncidentServices.IGetActiveIncidents" name="ActiveIncidentService" />
</client>
</system.serviceModel>
</configuration>

看看我的数据,这只是返回的简短内容-唯一看起来很奇怪的是数据中的[d] __type行....

Looking at my data, this is a short snippet of what comes back - and the only thing that looks strange is the [d] __type row in the data ....

{"d":[{"__type":"incidentData:Dan.Test.Incident.Data",
"description":"My description","devname":"",
"enddate":"\/Date(1357995120000-0800)\/",
"environmentID":10,"irNumber":"IR742989","priority":1,
"startdate":"\/Date(1357973873643-0800)\/",
"status":"closed","title":"A subset of users "},
{"__type":"incidentData:Dan.Test.Incident.Data","description":"second description.",
"devname":"","enddate":"\/Date(1352871180000-0800)\/",
"environmentID":10,"irNumber":"IR595320","priority":2,
"startdate":"\/Date(1352758680000-0800)\/",
"status":"This incident has been downgraded.",
"title":"Users will be unable to upgrade"}]}

希望这里的人可以阐明我需要做些什么:)

Hope someone here can shed light on what I need to do to make this work :)

预先感谢

推荐答案

WCF有两种开箱即用的行为来启用JSON数据:WebScriptEnablingBehavior(或<enableWebScript/>,如果通过配置);和WebHttpBehavior(等效于<webHttp/>).前者在使用ASP.NET AJAX框架时使用,它在JS中为您提供了代理",该代理知道如何与服务进行通信.后者用于更通用的JSON通信(比Web脚本少的开销).

There are two behaviors out-of-the-box from WCF for enabling JSON data: the WebScriptEnablingBehavior (or <enableWebScript/>, if via config), and the WebHttpBehavior (equivalent to <webHttp/>). The former is used when you're using the ASP.NET AJAX framework, and it gives you, in JS, a "proxy" which knows how to talk to the service. The latter is used for more general-purpose JSON communication (less overhead than the web script one).

根据您的评论,您正在使用第一个.根据ASP.NET AJAX框架的要求,该行为必须将响应包装在IIRC对象中(您正在看到的{"d":...}东西),以防止对数组进行某种JS原型劫持.因此,如果您要使用来自此类端点的JSON,则需要解开"响应,删除该"d",或者使用实际能够理解的行为.

Based on your comment, you're using the first one. That behavior, based on a requirement of the ASP.NET AJAX framework, has to wrap responses in an object (the {"d":...} thing you're seeing), IIRC to prevent some sort of JS prototype hijacking for arrays. So if you want to consume JSON which comes from such an endpoint, you need to either "unwrap" the response, taking that "d" away, or use a behavior which actually understands it.

如果要使用常规" HTTP客户端使用服务,然后使用JSON序列化程序反序列化响应,则最简单的方法是简单地创建包装类,然后将该类作为序列化程序的根类型,而不是事件类.如果只有一个(或几个)类,这是一个简单的解决方案,但如果有多个类,则可能成为维护问题.

If you want to consume the service using a "regular" HTTP client, and then use the JSON serializer to deserialize the response, the easiest thing to do would be to simply create a wrapping class, and then pass that class as the root type of the serializer, instead of the Incidents class. It's a simple solution if you only have one (or a few) classes, but it may become a maintenance problem if you have many.

如果要使用基于WCF的客户端使用该服务,则需要确保使用与该服务中使用的行为相同的行为-WebScriptEnablingBehavior,而不是更常见的WebHttpBehavior.那也可以.

If you want to consume the service using a WCF-based client, then you need to make sure that you use the same behavior as the one used in the service - WebScriptEnablingBehavior, instead of the more common WebHttpBehavior. That will also work.

如果您拥有服务,还有另一种选择.您可以添加另一个端点,这次使用WebHttpBehavior,它将返回数据而没有换行符"d".这样,您应该能够直接使用HTTP客户端和反序列化器.

There's yet another alternative, if you own the service. You can add another endpoint, this time using the WebHttpBehavior, which would return the data without the wrapping "d". with that you should be able to use a HTTP client and the deserializer directly.

下面的代码显示了前两个选择.

The code below shows the first two alternatives.

public class Post_e5cb2f0a_717d_4255_9142_7c9f7995fa4f
{
    [ServiceContract]
    public interface IGetActiveIncidents
    {
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        Incidents GetActiveIncidents(string environmentAbbreviation);
    }
    [CollectionDataContract]
    public class Incidents : List<IncidentData>
    {
        public Incidents() { }
        public Incidents(List<IncidentData> incidents) : base(incidents) { }
    }
    public class Service : IGetActiveIncidents
    {
        public Incidents GetActiveIncidents(string environmentAbbreviation)
        {
            Incidents incidents = new Incidents();
            incidents.Add(new IncidentData(
                "IR12345", "Title", "dev engaged", "description", DateTime.UtcNow, 1, 10, "status", DateTime.UtcNow + new TimeSpan(1, 0, 0, 0)));
            return incidents;
        }
    }
    [DataContract]
    public class IncidentData
    {
        public IncidentData(string irNumber, string title, string devName, string description, DateTime startDate, int priority, int envId, string status, DateTime endDate)
        {
            m_irNumber = irNumber;
            m_title = title;
            m_devname = devName;
            m_description = description;
            m_startdate = startDate;
            m_priority = priority;
            m_environmentID = envId;
            m_status = status;
            m_enddate = endDate;
        }

        [DataMember(Name = "irNumber")]
        private string m_irNumber = null;

        [DataMember(Name = "title")]
        private string m_title = null;

        [DataMember(Name = "devname")]
        private string m_devname = null;

        [DataMember(Name = "description")]
        private string m_description = null;

        [DataMember(Name = "startdate")]
        private DateTime m_startdate;

        [DataMember(Name = "priority")]
        private int m_priority = 0;

        [DataMember(Name = "environmentID")]
        private int m_environmentID = 0;

        [DataMember(Name = "status")]
        private string m_status;

        [DataMember(Name = "enddate")]
        private DateTime m_enddate;

        public IncidentData()
        {
        }
    }
    [DataContract]
    class IncidentWrapper
    {
        [DataMember(Name = "d")]
        public Incidents Incidents { get; set; }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        var endpoint = host.AddServiceEndpoint(typeof(IGetActiveIncidents), new WebHttpBinding(), "");
        endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
        host.Open();
        Console.WriteLine("Host opened");

        //Using a "normal" HTTP client
        WebClient c = new WebClient();
        byte[] data = c.DownloadData(baseAddress + "/GetActiveIncidents?environmentAbbreviation=dd");
        MemoryStream ms = new MemoryStream(data);
        DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(IncidentWrapper));
        IncidentWrapper wrapper = (IncidentWrapper)dcjs.ReadObject(ms);
        Console.WriteLine("Using HttpClient/DCJS: {0}", wrapper.Incidents.Count);

        // Using a WCF client (with WebScriptEnablingBehavior
        ChannelFactory<IGetActiveIncidents> factory = new ChannelFactory<IGetActiveIncidents>(new WebHttpBinding(), new EndpointAddress(baseAddress));
        factory.Endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
        IGetActiveIncidents proxy = factory.CreateChannel();

        Console.WriteLine("Using WCF client (with WSEB): {0}", proxy.GetActiveIncidents("dd").Count);
    }
}

这篇关于C#WCF REST客户端JSON数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆