Question

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.

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.

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).

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

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.

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.

My data contract looks like this:

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()
    {
    }
}
}

My interface definition is

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

and my config is straightforward as:

<?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>

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 :)

Thanks in advance

Dan

No correct solution

OTHER TIPS

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).

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.

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.

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.

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);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top