0

We have a WCF data service using latest ODATALib (5.6). The JSON results we are getting back from the service are not indented. They are single line strings. Is there a way to format the JSON as indented (i.e. pretty format)?

Heems
  • 49
  • 7
  • dulicate http://stackoverflow.com/questions/12943819/how-to-python-prettyprint-a-json-file – user2255757 Jan 29 '16 at 18:25
  • I need the WCF service to return the pretty JSON. Don't have the option to make it pretty at the client unfortunately. – Heems Jan 29 '16 at 18:43
  • This post seems to be closest to what I need alas no code posted. http://stackoverflow.com/questions/29123116/wcf-dataservice-indent-xml-response – Heems Jan 29 '16 at 19:25

1 Answers1

0

OK, so this was not the easiest thing to do, but it worked for me (pieced together from various other posts). The basic plan consists of making our own implementation of the IDispatchMessageFormatter interface. This implementation is then weaved in as a service behavior extension in the config file. So the WCF calls in/out of that service will get intercepted to this class which can the modify the HTML content going between the service and the client.

The interface only has two methods that need to be implemented. I don't bother with the DeserializeRequest method as I don't care about incoming. So I simply call the original/default formatter (which is a pass through formatter itself) in that case. For the outgoing data to the client, I have to check the original WCF message to see if it's a JSON format. Then I proceed to retrieve the body, indent it and replace the outgoing WCF message with a new message.

public class NewtonsoftJsonDispatchFormatter : IDispatchMessageFormatter
{ 
    OperationDescription operation;
    private IDispatchMessageFormatter originalFormatter;

    public NewtonsoftJsonDispatchFormatter(IDispatchMessageFormatter formatter, OperationDescription operation)
        {
            this.originalFormatter = formatter;
            this.operation = operation;
        }

    public void DeserializeRequest(Message message, object[] parameters)
        {
           originalFormatter.DeserializeRequest(message, parameters);
        }

    public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
        {            
            var message = result as Message;
            if (message == null) return message;

            if (IsJsonContentType(message))
            {
                byte[] messageBody;
                var httpResponseBody = ReadResponseBody(message);
                var jObj = JObject.Parse(httpResponseBody);
                messageBody = Encoding.UTF8.GetBytes(jObj.ToString(Newtonsoft.Json.Formatting.Indented));
                return ReplaceMessage(messageBody,messageVersion);
            }           
            else
            {
                return message;
            }         
       }

    private string ReadResponseBody(Message message)
        {
            var bodyReader = message.GetReaderAtBodyContents();
            bodyReader.ReadStartElement("Binary");
            byte[] rawBody = bodyReader.ReadContentAsBase64();

            string httpResponseBody;
            using (var ms = new MemoryStream(rawBody))
            {
                using (var sr = new StreamReader(ms))
                {
                    httpResponseBody = sr.ReadToEnd();
                }
            }
            return httpResponseBody;
        }

        private bool IsJsonContentType(Message message)
        {
           bool isMatch;
           object responseHeader;
           if (message.Properties.TryGetValue("httpResponse", out responseHeader) && responseHeader is HttpResponseMessageProperty)
           {
               var contentTypeVals = ((HttpResponseMessageProperty) responseHeader).Headers.GetValues("Content-Type");
               isMatch = contentTypeVals != null && contentTypeVals.Any(v => v.Contains("application/json"));
           }
           else
           {
               isMatch = false;
           }
           return isMatch;
        }

      private Message ReplaceMessage(byte[] messageBody, MessageVersion messageVersion)
       {
             var replyMessage = Message.CreateMessage(messageVersion, operation.Messages[1].Action, new RawBodyWriter(messageBody));
            replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
            var respProp = new HttpResponseMessageProperty();
            respProp.Headers[HttpResponseHeader.ContentType] = "application/json";
            replyMessage.Properties.Add(HttpResponseMessageProperty.Name, respProp);
            return replyMessage;

    }
}

public class RawBodyWriter : BodyWriter
{
    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();
    }
}

And the config file changes with corresponding service and operation behaviors that will intercept the WCF messages for my service:

 <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="newtonsoftJsonBehaviorExtension" type="WCFProject.NewtonsoftJsonServiceBehavior, WCFProject, Version=1.0.0.0, Culture=neutral"/>
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>       
        <behavior name="newtonsoftJsonBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>          
          <newtonsoftJsonBehaviorExtension />
        </behavior>
      </serviceBehaviors>   
    </behaviors>    
    <services>
      <service name="Myservice" behaviorConfiguration="newtonsoftJsonBehavior">
        <endpoint bindingConfiguration="msgSize" address="" binding="webHttpBinding" contract="System.Data.Services.IRequestHandler"/>
      </service>
    </services>
</system.serviceModel>
 public class NewtonsoftJsonServiceBehavior : BehaviorExtensionElement, IServiceBehavior
{
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (ServiceEndpoint endpoint in serviceDescription.Endpoints)
        {
            foreach (OperationDescription operation in endpoint.Contract.Operations)
            {
                operation.Behaviors.Add(new NewtonsoftJsonOperationBehavior());
            }
        }
    }

    protected override object CreateBehavior()
    {
        return this;
    }

    public override Type BehaviorType
    {
        get { return typeof(NewtonsoftJsonServiceBehavior); }
    }
}

 public class NewtonsoftJsonOperationBehavior : IOperationBehavior
{
    public void Validate(OperationDescription operationDescription)
    {
    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        var originalFormatter = dispatchOperation.Formatter;
        dispatchOperation.Formatter = new NewtonsoftJsonDispatchFormatter(originalFormatter, operationDescription);
    }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {
    }

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {
    }
}

Final note: Turns out OData has a batch concept where the request/reply can be multi-part http. This means the code above has to be modified to handle the case where each part of the message is examined individually and indented if it's JSON formatted. I leave it as an exercise for the reader to implement that enhancement.

Heems
  • 49
  • 7