none
Avoid Raw model but return json using a custom serializer

    Question

  • I have a service which currently uses the raw programming model; i.e. returns Streams. I am returning mime type application/json and using a custom json serializer from Newtonsoft which gives me the output control and automatic polymorphism handling that I require. This is all working good. I have the service and the IIS service host in separate assemblies. Now the problem; I want to expose my service though a secondary host using NetTcp and binary serialization. But right now the JSON serialization and mime type selection is tightly coupled with my service, so there is no way to get the raw clr objects from it. 
    What I want to do is stop using the raw model, and have my service return the serializable types, and let each service host be conserned with how it should serialize the objects. My question then is how do I go about implementing this? I've hooked up the plumbing for a custom ServiceHost and ServiceHostFactory, and see that there are a lot of possibilities with implementing different kinds of behaviours, dispatchers etc. 
    My problem is that there are so many things, and I need somebody to point me in the right direction. Should I look at implementing/configuring and hooking custom
    1. Xml serialization (or will this always give me an outer xml tag at best, and full xml at worst?)
    2. IDispatchMessageFormatter (but how do I hook it, and what other things must I then implement?)
    3. IChannelFactory (can I use this to effectively change the output scheme to Stream, even if my contract returns another Type?)
    4. MessageEncoder (suggested in a related thread, see below)
    5. Or something entirely different
    I see a related thread: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/2542228f-9b73-4158-bc8f-f1caede95398, but unless I am mistaken the response is still wrapped in some xml that I'd rather do without. There are tags for GetPersonsResponse and GetPersonsResult within the s:Body tag, wrapping the JSON.
    I want a response looking like this, which is what my raw implementation yields:<br/>
    HTTP/1.1 200 OK
    Content-Length: 325
    Content-Type: application/json
    Server: Microsoft-IIS/7.5
    X-Powered-By: ASP.NET
    Date: Sun, 17 Apr 2011 00:51:37 GMT
    
    {
     "FirstName": "First",
     "LastName": "Last",
     "BirthDate": "1993-04-17T02:51:37.0478456+02:00",
     "Description": "Generic Person",
     "Pets": [
     {
     "Name": "Generic pet 1",
     "Color": "Beige",
     "Markings": "Some markings",
     "Id": 0
     },
     {
     "Name": "Generic pet 2",
     "Color": "Gold",
     "Markings": "Other markings",
     "Id": 0
     }
     ],
     "Id": 0
    }
    In short: I would like my contract operations to return objects of type SomethingDTO, but I would like the service host to create message bodies consisting of the raw unwrapped json produced by the Newtonsoft serializer for one of my hosts.
    Many thanks in advance.


    • Edited by Mithon Sunday, April 17, 2011 1:09 AM Formatting
    Sunday, April 17, 2011 1:07 AM

Answers

  • The best way to accomplish that is to create an IDispatchMessageFormatter, in which you'll be able to do your custom serialization. You can even use the raw mode internally inside the formatter, so you serialize your objects to raw bytes, and tag the message with the WebBodyFormatMessageProperty to tell the web encoder to serialize it as raw. The code below shows it in action - with two endpoints, one using a "normal" binding (BasicHttpBinding, but it could be NetTcpBinding as well), and one using the web binding with this new dispatch formatter.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.ServiceModel.Web;
    using System.Text;
    using System.Xml;
    
    namespace CustomNonXmlSerialization
    {
      [DataContract]
      [Newtonsoft.Json.JsonObject(MemberSerialization = Newtonsoft.Json.MemberSerialization.OptIn)]
      public class Person
      {
        [DataMember(Order = 1), Newtonsoft.Json.JsonProperty]
        public string FirstName;
        [DataMember(Order = 2), Newtonsoft.Json.JsonProperty]
        public string LastName;
        [DataMember(Order = 3),
          Newtonsoft.Json.JsonProperty,
          Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.IsoDateTimeConverter))]
        public DateTime BirthDate;
        [DataMember(Order = 4), Newtonsoft.Json.JsonProperty]
        public List<Pet> Pets;
        [DataMember(Order = 5), Newtonsoft.Json.JsonProperty]
        public int Id;
      }
    
      [DataContract, Newtonsoft.Json.JsonObject(MemberSerialization = Newtonsoft.Json.MemberSerialization.OptIn)]
      public class Pet
      {
        [DataMember(Order = 1), Newtonsoft.Json.JsonProperty]
        public string Name;
        [DataMember(Order = 2), Newtonsoft.Json.JsonProperty]
        public string Color;
        [DataMember(Order = 3), Newtonsoft.Json.JsonProperty]
        public string Markings;
        [DataMember(Order = 4), Newtonsoft.Json.JsonProperty]
        public int Id;
      }
    
      public class NewtonsoftJsonBehavior : WebHttpBehavior
      {
        public override void Validate(ServiceEndpoint endpoint)
        {
          BindingElementCollection elements = endpoint.Binding.CreateBindingElements();
          WebMessageEncodingBindingElement webEncoder = elements.Find<WebMessageEncodingBindingElement>();
          if (webEncoder == null)
          {
            throw new InvalidOperationException("This behavior must be used in an endpoint with the WebHttpBinding (or a custom binding with the WebMessageEncodingBindingElement).");
          }
    
          foreach (OperationDescription operation in endpoint.Contract.Operations)
          {
            if (operation.Messages.Count > 1)
            {
              if (operation.Messages[1].Body.Parts.Count > 0)
              {
                throw new InvalidOperationException("Operations cannot have out/ref parameters.");
              }
            }
          }
        }
    
        protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
        {
          if (operationDescription.Behaviors.Find<WebGetAttribute>() != null)
          {
            // no change for GET operations
            return base.GetRequestDispatchFormatter(operationDescription, endpoint);
          }
          else
          {
            WebInvokeAttribute wia = operationDescription.Behaviors.Find<WebInvokeAttribute>();
            if (wia != null)
            {
              if (wia.Method == "HEAD")
              {
                // essentially a GET operation
                return base.GetRequestDispatchFormatter(operationDescription, endpoint);
              }
            }
          }
    
          if (operationDescription.Messages[0].Body.Parts.Count == 0)
          {
            // nothing in the body, still use the default
            return base.GetRequestDispatchFormatter(operationDescription, endpoint);
          }
    
          return new NewtonsoftJsonFormatter(operationDescription);
        }
    
        protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
        {
          return new NewtonsoftJsonFormatter(operationDescription);
        }
      }
    
      class NewtonsoftJsonFormatter : IDispatchMessageFormatter
      {
        OperationDescription operation;
        bool isVoidInput;
        bool isVoidOutput;
        public NewtonsoftJsonFormatter(OperationDescription operation)
        {
          this.operation = operation;
          this.isVoidInput = operation.Messages[0].Body.Parts.Count == 0;
          this.isVoidOutput = operation.Messages.Count == 1 || operation.Messages[1].Body.ReturnValue.Type == typeof(void);
        }
    
        public void DeserializeRequest(Message message, object[] parameters)
        {
          XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
          bodyReader.ReadStartElement("Binary");
          byte[] rawBody = bodyReader.ReadContentAsBase64();
          MemoryStream ms = new MemoryStream(rawBody);
    
          using (StreamReader sr = new StreamReader(ms))
          {
            Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
            if (parameters.Length == 1)
            {
              // single parameter, assuming bare
              parameters[0] = serializer.Deserialize(sr, operation.Messages[0].Body.Parts[0].Type);
            }
            else
            {
              // multiple parameter, needs to be wrapped
              throw new NotImplementedException("Not implemented yet");
            }
          }
        }
    
        public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
        {
          byte[] body;
          if (this.isVoidOutput)
          {
            body = RawBodyWriter.EmptyByteArray;
          }
          else
          {
            Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
            using (MemoryStream ms = new MemoryStream())
            {
              using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
              {
                using (Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter(sw))
                {
                  writer.Formatting = Newtonsoft.Json.Formatting.Indented;
                  serializer.Serialize(writer, result);
                  sw.Flush();
                  body = ms.ToArray();
                }
              }
            }
          }
    
          Message replyMessage = Message.CreateMessage(messageVersion, operation.Messages[1].Action, new RawBodyWriter(body));
          replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
          return replyMessage;
        }
    
        class RawBodyWriter : BodyWriter
        {
          internal static readonly byte[] EmptyByteArray = new byte[0];
          byte[] content;
          public RawBodyWriter(byte[] content)
            : base(true)
          {
            this.content = content;
          }
    
          protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
          {
            writer.WriteStartElement("Binary");
            writer.WriteBase64(content, 0, content.Length);
            writer.WriteEndElement();
          }
        }
      }
    
      [ServiceContract]
      public interface ITest
      {
        [WebGet, OperationContract]
        Person GetPerson();
        [WebInvoke, OperationContract]
        Pet EchoPet(Pet pet);
      }
    
      public class Service : ITest
      {
        public Person GetPerson()
        {
          return new Person
          {
            FirstName = "First",
            LastName = "Last",
            BirthDate = new DateTime(1993, 4, 17, 2, 51, 37, 47, DateTimeKind.Local),
            Id = 0,
            Pets = new List<Pet>
            {
              new Pet { Name= "Generic Pet 1", Color = "Beige", Id = 0, Markings = "Some markings" },
              new Pet { Name= "Generic Pet 2", Color = "Gold", Id = 0, Markings = "Other markings" },
            },
          };
        }
    
        public Pet EchoPet(Pet pet)
        {
          return pet;
        }
      }
    
      class Program
      {
        public static string SendRequest(string uri, string method, string contentType, string body)
        {
          string responseBody = null;
    
          HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
          req.Method = method;
          if (!String.IsNullOrEmpty(contentType))
          {
            req.ContentType = contentType;
          }
          if (body != null)
          {
            byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
            req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
            req.GetRequestStream().Close();
          }
    
          HttpWebResponse resp;
          try
          {
            resp = (HttpWebResponse)req.GetResponse();
          }
          catch (WebException e)
          {
            resp = (HttpWebResponse)e.Response;
          }
          Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
          foreach (string headerName in resp.Headers.AllKeys)
          {
            Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
          }
          Console.WriteLine();
          Stream respStream = resp.GetResponseStream();
          if (respStream != null)
          {
            responseBody = new StreamReader(respStream).ReadToEnd();
            Console.WriteLine(responseBody);
          }
          else
          {
            Console.WriteLine("HttpWebResponse.GetResponseStream returned null");
          }
          Console.WriteLine();
          Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
          Console.WriteLine();
    
          return responseBody;
        }
    
        class MyRawMapper : WebContentTypeMapper
        {
          public override WebContentFormat GetMessageFormatForContentType(string contentType)
          {
            return WebContentFormat.Raw;
          }
        }
    
        static void Main(string[] args)
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
          host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "soap");
          WebHttpBinding webBinding = new WebHttpBinding();
          webBinding.ContentTypeMapper = new MyRawMapper();
          host.AddServiceEndpoint(typeof(ITest), webBinding, "json").Behaviors.Add(new NewtonsoftJsonBehavior());
          Console.WriteLine("Opening the host");
          host.Open();
    
          ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new BasicHttpBinding(), new EndpointAddress(baseAddress + "/soap"));
          ITest proxy = factory.CreateChannel();
          Console.WriteLine(proxy.GetPerson());
    
          SendRequest(baseAddress + "/json/GetPerson", "GET", null, null);
          SendRequest(baseAddress + "/json/EchoPet", "POST", "application/json", "{\"Name\":\"Fido\",\"Color\":\"Black and white\",\"Markings\":\"None\",\"Id\":1}");
    
          Console.WriteLine("Press ENTER to close");
          Console.ReadLine();
          host.Close();
          Console.WriteLine("Host closed");
        }
      }
    }
    
    
    • Proposed as answer by Carlos Figueira Monday, April 18, 2011 9:02 PM
    • Marked as answer by Yi-Lun Luo Friday, April 22, 2011 9:05 AM
    Monday, April 18, 2011 12:16 AM