Json.Net is a popular framework for working with JSON. In particular, it has a bunch of features that are not supported by the DataContractJsonSerializer such as being much more flexible in what kind of types it can serialize and exactly how they should be serialized. The ASP.NET Web API supports an open-ended set of formatters that can read and write data to and from any media type you want to support. For example, if you want to support the vCard format which has the media type text/vcard (previously it was text/directory) media type then you can write a formatter for vCard and register it for the media type (or types) in question.

Note: JSON.NET is now an integral part of ASP.NET Web API so you can just us it out of the box. 

This sample shows how to hook in Json.Net as the default formatter replacing the built in DataContractJsonSerializer formatter (in the beta bits DataContractJsonSerializer is the default formatter). There are already a bunch of Json.Net formatters provided by the community that may well be more full-featured but this should allow you to get started.

Building the Formatter

The first thing we do is building the formatter. The key part of the formatter is to provide support for reading and writing content of a given media type. The sample formatter derives from the base MediaTypeFormatter class; we are working on a buffered media type formatter that will help working with lots of small reads and writes but for this sample the goal is to keep things simple. The formatter looks like this:  

   1: public class JsonNetFormatter : MediaTypeFormatter
   2: {
   3:     private JsonSerializerSettings _jsonSerializerSettings;
   4:  
   5:     public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
   6:     {
   7:         _jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();
   8:  
   9:         // Fill out the mediatype and encoding we support
  10:         SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
  11:         Encoding = new UTF8Encoding(false, true);
  12:     }
  13:  
  14:     protected override bool CanReadType(Type type)
  15:     {
  16:         if (type == typeof(IKeyValueModel))
  17:         {
  18:             return false;
  19:         }
  20:  
  21:         return true;
  22:     }
  23:  
  24:     protected override bool CanWriteType(Type type)
  25:     {
  26:         return true;
  27:     }
  28:  
  29:     protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
  30:     {
  31:         // Create a serializer
  32:         JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
  33:  
  34:         // Create task reading the content
  35:         return Task.Factory.StartNew(() =>
  36:         {
  37:             using (StreamReader streamReader = new StreamReader(stream, Encoding))
  38:             {
  39:                 using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
  40:                 {
  41:                     return serializer.Deserialize(jsonTextReader, type);
  42:                 }
  43:             }
  44:         });
  45:     }
  46:  
  47:     protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext)
  48:     {
  49:         // Create a serializer
  50:         JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
  51:  
  52:         // Create task writing the serialized content
  53:         return Task.Factory.StartNew(() =>
  54:         {
  55:             using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
  56:             {
  57:                 serializer.Serialize(jsonTextWriter, value);
  58:                 jsonTextWriter.Flush();
  59:             }
  60:         });
  61:     }
  62: }

Building a Sample ApiController

Next we need a controller to try things out. For illustrative purposes we create a type that would not serialize well with DataContractJsonSerializer but other than that this is a completely vanilla controller that knows nothing about serialization:

   1: public class HomeController : ApiController
   2: {
   3:     public HomeInfo Get()
   4:     {
   5:         return new HomeInfo();
   6:     }
   7: }
   8:  
   9: public class HomeInfo
  10: {
  11:     private readonly DateTime _created = DateTime.UtcNow;
  12:     private readonly Dictionary<int, string> _colorMap = new Dictionary<int, string>
  13:     {
  14:         { 1, "blue"},
  15:         { 2, "red" },
  16:         { 3, "green" },
  17:         { 4, "black" },
  18:         { 5, "white" },
  19:     };
  20:  
  21:     public DateTime Created { get { return _created; } }
  22:  
  23:     public IDictionary<int, string> ColorMap { get { return _colorMap; } }
  24: }

Hosting the Controller

Now that we have the controller we can host it in either ASP or as selfhost. In this case we selfhost the controller in a simple console application but it would work exactly the same if hosted in ASP.

The first part is to configure the selfhost server and injecting the JsonNetFormatter as the first formatter in the configuration so that it becomes the default formatter. We also configure Json.Net to serialize DateTime types using ISO 8601 format instead of the more esoteric "/Date(1240718400000)/” format. The part of the console app that configures and starts the server looks like this:

   1: // Set up server configuration
   2: HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("http://localhost:8080");
   3: config.Routes.MapHttpRoute("Default", "{controller}", new { controller = "Home" });
   4:  
   5: // Create Json.Net formatter serializing DateTime using the ISO 8601 format
   6: JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
   7: serializerSettings.Converters.Add(new IsoDateTimeConverter());
   8: config.Formatters[0] = new JsonNetFormatter(serializerSettings);
   9:  
  10: // Create server
  11: server = new HttpSelfHostServer(config);
  12:  
  13: // Start listening
  14: server.OpenAsync().Wait();

Note: In order to successfully start the selfhost server you have to run as admin (or configure http.sys with the appropriate URI prefix using netsh).

Trying it Out

Once the controller is running, we can access it using any HTTP client. In this case we use HttpClient to access it and print out the result to the console. If we put both the server configuration and the client in the same Main then we get something like this:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         HttpSelfHostServer server = null;
   6:         try
   7:         {
   8:             // Set up server configuration
   9:             HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("http://localhost:8080");
  10:             config.Routes.MapHttpRoute("Default", "{controller}", new { controller = "Home" });
  11:  
  12:             // Create Json.Net formatter serializing DateTime using the ISO 8601 format
  13:             JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
  14:             serializerSettings.Converters.Add(new IsoDateTimeConverter());
  15:             config.Formatters[0] = new JsonNetFormatter(serializerSettings);
  16:  
  17:             // Create server
  18:             server = new HttpSelfHostServer(config);
  19:  
  20:             // Start listening
  21:             server.OpenAsync().Wait();
  22:  
  23:             // Create HttpClient, do an HTTP GET on the controller, and show the output
  24:             HttpClient client = new HttpClient();
  25:             client.GetAsync("http://localhost:8080").ContinueWith(
  26:                 (requestTask) =>
  27:                 {
  28:                     // Get HTTP response from completed task.
  29:                     HttpResponseMessage response = requestTask.Result;
  30:  
  31:                     // Check that response was successful or throw exception
  32:                     response.EnsureSuccessStatusCode();
  33:  
  34:                     // Read response asynchronously as string and write out
  35:                     response.Content.ReadAsStringAsync().ContinueWith(
  36:                         (readTask) =>
  37:                         {
  38:                             Console.WriteLine(readTask.Result);
  39:                         });
  40:                 });
  41:  
  42:             Console.WriteLine("Hit ENTER to exit...");
  43:             Console.ReadLine();
  44:  
  45:         }
  46:         finally
  47:         {
  48:             if (server != null)
  49:             {
  50:                 // Stop listening
  51:                 server.CloseAsync().Wait();
  52:             }
  53:         }
  54:     }
  55: }

The resulting output written to the console is

{"Created":"2012-02-18T00:54:06.8447642Z","ColorMap":{"1":"blue","2":"red","3":"green","4":"black","5":"white"}}

Note the ISO date and the nice serialization of the dictionary!

Henrik

del.icio.us Tags: ,,,,