none
MSFT Json format is one way for arrays? RRS feed

  • Question

  • I have a simple WCF service that I use to return an array of a single complex type.  The WCF auto-proxy on the browser works great.  But I want to call the same service from a C# client, and I've tried everything to get a simple round-trip scenario to work.  The client and the server both share the data contract, and I've given up on the automagic C# client side (channel factory). 

    The problem seems to be that there is an extra layer of abstraction in the returned JSON - and the following code runs successfully, but ALWAYS returns an empty array.

           

            static void Main(string[] args)
            {
    
                var httpWebRequest = (WebRequest)WebRequest.Create("http://localhost/GadgetActiveIncidentService/ActiveIncidentService.svc/GetActiveIncidents?environmentAbbreviation=\"prod\"");
                httpWebRequest.ContentType = "application/text";
                httpWebRequest.Method = "GET";
                httpWebRequest.Timeout = 20000;
    
                IList<Type> knownTypes = new List<Type>();
                knownTypes.Add(typeof(IncidentData));
                knownTypes.Add(typeof(Incidents));
    
                var httpResponse = (WebResponse)httpWebRequest.GetResponse();
    
                System.Runtime.Serialization.Json.DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(Incidents), knownTypes);
    
                Incidents incidents = jsonSerializer.ReadObject(httpResponse.GetResponseStream()) as Incidents;
    
                Console.Write("Call to GetActiveIncidents returned ");
                if (null == incidents)
                {
                    Console.WriteLine("no data (null)");
                }
                else
                {
                    Console.WriteLine(incidents.Count.ToString() + " rows of incident data.");
                }
                Console.WriteLine("\nPress any key to continue...");
                Console.ReadLine();
            }

    The service is a simple webget arrangement with no bells and whistles. The interface looks like this:

        [ServiceContract]
        public interface IGetActiveIncidents
        {
            [OperationContract]
            [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle=WebMessageBodyStyle.WrappedRequest)]
            Incidents GetActiveIncidents(string environmentAbbreviation);
        }

    The Incidents class just derives from List<IncidentData>.

    The data that comes back in JSON looks like this

    {"d":[{"__type":"IncidentData:#Test.Dan.Incident.Data","description":"SomethingSomething.","devname":"","enddate":"\/Date(1357995120000-0800)\/","environmentID":10,"irNumber":"IR742989","priority":1,"startdate":"\/Date(1357973873643-0800)\/","status":"All quiet.","title":"Test."}]}

    I'm seeing lots of quesitons on this on the web, but really few answers. I could switch to a completely different object serialization stack, but I was hoping the datacontract serializer would be able to deserialize an array.

    The extray wrapper "d" is what I think may be throwing the serializer off.  Just a guess.  Has anyone got an complex type array to deserialize with the DataContract mechanism?

    For completeness, my classes are textbook:

        [CollectionDataContract]
        public class Incidents : List<IncidentData>
        {
            public Incidents() { }
            public Incidents(List<IncidentData> incidents) : base(incidents) {}
        }
    
        [DataContract]
        public class IncidentData
        {
            
            [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()
            {
            }
    }

    EDIT:  After some more testing, I have isolated this to just WCF calls. 

    In a simple test program (isolating my data class) I can serialize and deserialize fine - but I get different JSON then when I get JSON from the remote WCF service. That enclosing {"d":[]} seems to be the only difference.

    Test code:

                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)));
                System.Runtime.Serialization.Json.DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(Incidents), knownTypes);
    
                Stream temp = new MemoryStream();
    
                jsonSerializer.WriteObject(temp, incidents);
                temp.Position = 0;
    
                StreamReader printer = new StreamReader(temp);
                Console.WriteLine(printer.ReadToEnd());
    
                temp.Position = 0;
    
                Incidents outincidents = jsonSerializer.ReadObject(temp) as Incidents;

    Good JSON

    [{"description":"description","devname":"dev engaged","enddate":"\/Date(13598424
    15032)\/","environmentID":10,"irNumber":"IR12345","priority":1,"startdate":"\/Da
    te(1359756015032)\/","status":"status","title":"Title"}]

    Now the question is what do I do on the receiving side to handle that extra d?


    Microsoft Corporation


    • Edited by Dan Rogers Friday, February 1, 2013 10:01 PM
    Friday, February 1, 2013 12:43 AM

Answers

  • 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. You can do both.

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


    Carlos Figueira

    • Marked as answer by Dan Rogers Friday, February 1, 2013 11:41 PM
    Friday, February 1, 2013 10:32 PM
    Moderator

All replies

  • 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. You can do both.

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


    Carlos Figueira

    • Marked as answer by Dan Rogers Friday, February 1, 2013 11:41 PM
    Friday, February 1, 2013 10:32 PM
    Moderator
  • Thanks Carlos. 

    The extra d wrapper class is exactly what I discovered this afternoon after I realized that the issue was the d.

    The added example and learning about the differences between the behaviors is illuminating. Thank you for that.  Now I can use a C# client that calls with no wrapper by using the WebScriptEnablingBehavior on the proxy, and still have that great JavaScript client capability for the same service.

    Much appreciated.


    Dan


    Microsoft Corporation

    Friday, February 1, 2013 11:41 PM