C# WCF REST 客户端 JSON 数组
本文关键字:JSON 数组 客户端 REST WCF | 更新日期: 2023-09-27 17:56:09
这里的例子(http://stackoverflow.com/questions/835839/client-configuration-to-consume-wcf-json-web-service)简单明了。 我创建了一个返回简单对象数组的 WCF RESTFUL JSON 服务。 在 JavaScript 中,我处理返回的内容没有问题,因此我的 Web 客户端工作正常。
我希望使此服务可从 Web 或 C# 客户端调用,以便公开的数据可以在多个上下文中使用,而无需复制获取数据的代码。
在我的 C# 代码中,我有代理客户端、接口协定和基于配置的绑定定义。 我在 C# 服务和 C# 客户端(用作测试验证工具的简单控制台应用)之间共享接口和数据代码模块。
在 C# 客户端上,不是数组形式的数据返回得很好。 一旦我让我的服务返回一个对象数组,无论是作为裸数组返回还是作为简单对象的包装属性,客户端的序列化程序就会静默失败并返回一个空数组。
这是我在顶层的客户端代码
// 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 个元素。
棘手的部分是序列化程序只是默默地丢弃数据 - 这让我想知道我是否应该放弃基于 WCF 客户端库的代理并使用原始 HTTP get 和一个单独的 JSON 解析器来处理数据。
我的数据协定如下所示:
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行......
{"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"}]}
希望这里有人能阐明我需要做些什么才能使这项工作:)
提前致谢
担
WCF 有两种现成的行为用于启用 JSON 数据:WebScriptEnablingBehavior
(如果通过配置,则<enableWebScript/>
)和WebHttpBehavior
(相当于 <webHttp/>
)。前者在使用 ASP.NET AJAX框架时使用,它在JS中为您提供了一个知道如何与服务通信的"代理"。后者用于更通用的JSON通信(比Web脚本的开销更少)。
根据您的评论,您正在使用第一个。该行为基于 ASP.NET AJAX框架的要求,必须将响应包装在一个对象中(您看到的{"d":...}
事物),IIRC以防止某种JS原型劫持数组。因此,如果你想使用来自这样一个端点的JSON,你需要"解包"响应,去掉那个"d",或者使用一个真正理解它的行为。
如果要使用"常规"HTTP 客户端使用服务,然后使用 JSON 序列化程序反序列化响应,最简单的方法是简单地创建一个包装类,然后将该类作为序列化程序的根类型传递,而不是 Incidents 类。如果您只有一个(或几个)类,这是一个简单的解决方案,但如果有很多类,它可能会成为维护问题。
如果要使用基于 WCF 的客户端使用服务,则需要确保使用与服务中使用的行为相同的行为 - WebScriptEnablingBehavior,而不是更常见的 WebHttpBehavior。那也将奏效。
如果您拥有该服务,还有另一种选择。您可以添加另一个端点,这次使用 WebHttpBehavior,它将返回数据而不使用包装"d"。这样,您应该能够直接使用 HTTP 客户端和反序列化程序。
下面的代码显示了前两个替代方法。
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);
}
}