Seeking .NET Compact Framework support in Visual Studio 2010?
Enabling PING (aka ICMP) on Windows Azure roles

Using JSON.NET as a default serializer in WCF HTTP/Web/REST (vNext)

Just yesterday a client walked up to me and asked how to replace the default JSON serializer in WCF’s web programming model (whether in .NET 3.5 or 4.0).
Honestly, this is not too easy – if you aim at adding a different JSON engine - like the wonderful JSON.NET – to the WCF pipeline you won’t have too much fun. A lot of plumbing works needs to be done. A more practical approach is to use Message or Stream as the response/request type and apply the serialization/deserialization inside the service façade operation implementation.

I thought there should be an easier and more HTTP/Web/Rest-style way to do this.

Good news: I found one.
Bad news: it is based on the WCF HTTP Preview 1 (and thus non-RTM bits)

To get you up and running with the ideas of WCF HTTP vNext please refer to the master–of-disaster-PM Glenn Block’s blog Smile

 

So, in order to support JSON.NET all I had to do is to write a custom media type processor by deriving from MediaTypeProcessor. Please also refer to Cibrax’ great blog entry about versioning REST services with processors.

Here is a little sample for the JsonNetProcessor class::

namespace Microsoft.ServiceModel.Http
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.ServiceModel.Description;
    using Microsoft.Http;
    using Newtonsoft.Json;
    public class JsonNetProcessor : MediaTypeProcessor
    {
        private Type parameterType;
        public JsonNetProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
            : base(operation, mode)
        {
            if (this.Parameter != null)
            {
                this.parameterType = this.Parameter.ParameterType;
            }
        }
        public override IEnumerable<string> SupportedMediaTypes
        {
            get
            {
                return new List<string> { "text/json", "application/json" };
            }
        }
        public override void WriteToStream(object instance, Stream stream, HttpRequestMessage request)
        {
            var serializer = new JsonSerializer();
            
            using (var sw = new StreamWriter(stream))
            using (var writer = new JsonTextWriter(sw))
            {
                serializer.Serialize(writer, instance);
            }
        }
        public override object ReadFromStream(Stream stream, HttpRequestMessage request)
        {
            var serializer = new JsonSerializer();
            using (var sr = new StreamReader(stream))
            using (var reader = new JsonTextReader(sr))
            {
                var result = serializer.Deserialize(reader, parameterType);
                return result;
            }
        }
    }
}

 

Very straight-forward and actually almost too easy. As JSON.NET also supports binary JSON, named BSON, I added another class BsonProcessor for this as well:

namespace Microsoft.ServiceModel.Http
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.ServiceModel.Description;
    using Microsoft.Http;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Bson;
    public class BsonProcessor : MediaTypeProcessor
    {
        private Type parameterType;
        public BsonProcessor(HttpOperationDescription operation, MediaTypeProcessorMode mode)
            : base(operation, mode)
        {
            if (this.Parameter != null)
            {
                this.parameterType = this.Parameter.ParameterType;
            }
        }
        public override IEnumerable<string> SupportedMediaTypes
        {
            get
            {
                return new List<string> { "application/bson" };
            }
        }
        public override void WriteToStream(object instance, Stream stream, HttpRequestMessage request)
        {
            var serializer = new JsonSerializer();
            
            using (var writer = new BsonWriter(stream))
            {
                serializer.Serialize(writer, instance);
            }
        }
        public override object ReadFromStream(Stream stream, HttpRequestMessage request)
        {
            var serializer = new JsonSerializer();
            using (var reader = new BsonReader(stream))
            {
                var result = serializer.Deserialize(reader, parameterType);
                return result;
            }
        }
    }
}
 
Well. This is it. No more buzz.
Now let’s register the new processor with the pipeline by implementing a custom host configuration:
public class MyResourceConfiguration : HostConfiguration
{
    public override void RegisterRequestProcessorsForOperation(HttpOperationDescription operation, IList<Processor> processors, MediaTypeProcessorMode mode)
    {
        processors.Add(new JsonNetProcessor(operation, mode));
        processors.Add(new BsonProcessor(operation, mode));
    }
    public override void RegisterResponseProcessorsForOperation(HttpOperationDescription operation, IList<Processor> processors, MediaTypeProcessorMode mode)
    {
        processors.Add(new JsonNetProcessor(operation, mode));
        processors.Add(new BsonProcessor(operation, mode));
    }
}
Done.
Looking forward to more Web/HTTP goodness for WCF vNext
(and yes, feel free to discuss with me why bother about WCF at all when it comes to
HTTP-close-to-the-metal-programming…).

Comments

Feed You can follow this conversation by subscribing to the comment feed for this post.

Jasonsirota

I was actually just looking at this code path last night. I figured out how to add a JSON media type to the processors but I was having trouble with adding it as the default processor. Unless you pass the accept header, it still defaults to XML. Have you had that issue?

Christian Weyer

Hi,

you need to clean up first, like this:

public override void RegisterResponseProcessorsForOperation(HttpOperationDescription operation, IList processors, MediaTypeProcessorMode mode)
{
processors.ClearMediaTypeProcessors();

processors.Add(new JsonNetProcessor(operation, mode));
processors.Add(new BsonProcessor(operation, mode));
}

Glenn Block

Nice post Christian!

In the current bits, we take the first processor in the response pipeline as the default. You can use clear that processor collection in the RegisterResponseProcessorsForOperation method by calling the ClearMediaTypeProcessors on the processors param. Then just add back in Json first and it should work.

Black

It's a Very helpful article for me. Actually, I am fond of reading online punjabi news. Thanks for writing such a complete ..And,I wantn't to miss them.
Thank you for sharing..

Merlyn Morgan-Graham

I'm looking for a way to use Json.NET BSON that doesn't involve coding up a service framework, and doesn't encode the binary data as base64 (which is the best I've done so far). Something that streamed the data would be even better. This post looks perfect, but I also want to use this in production :)

You mentioned: "A more practical approach is to use Message or Stream as the response/request type and apply the serialization/deserialization inside the service façade operation implementation." Can you point me to some references, or basic classes I'd need to mess with to start along this path?

Ryan Riley

Excellent post! Would you mind if I added this into http://wcfhttpcontrib.codeplex.com/?

Cheers,
Ryan

Christian Weyer

Hi Ryan,

sure, go ahead! Just include the original reference, pls.

-Christian

Ryan Riley

Thanks! I just pushed it. Let me know if you would like me to add a copyright, etc. http://wcfhttpcontrib.codeplex.com/SourceControl/changeset/changes/768111a9a285

Ryan Riley

Btw, I noticed that WriteToStream didn't work. I worked with Glenn on it tonight and discovered that 1) the writer is closing the underlying stream before its use is completed (http://ydie22.blogspot.com/2008/02/about-idisposable-close-streams-and.html) and 2) the contents weren't flushed. Not sure if those are new issues with the latest bits, but I'm fixing them now. Should be up tomorrow.

rsf

will this approach honor [DataContract] / [DataMember] directives?

Dan

I am running my services through a windows service....where do I configure the custom host configuration?

thanks

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been saved. Comments are moderated and will not appear until approved by the author. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment

Comments are moderated, and will not appear until the author has approved them.

Your Information

(Name is required. Email address will not be displayed with the comment.)