How to write a .NET JSON Web Service using JSON.NET

I want to write a .NET 4.0 web service of some description that produces JSON and produces it quickly. I have very complex tree structures that run headlong into the performance limitations of the out of the box .NET JSON serialisers. For the same data structure using the Microsoft options I got between 43,925 ms and 4,546 ms. With Newtonsoft.Json I got 43 ms.

If you want to use WCF (which I don’t because it’s awful), you can use some of the solutions described here to use Newtonsoft.JSON in place of DataContractJsonSerializer.

If you wanted to use ‘old style’ ASP.NET web services (ASMX), you’re more or less stuck with what you get. You can use Newtonsoft.JSON to output strings which are then serialised again by JavaScriptSerializer as described here, but that leaves you with some pretty gnarly looking JSON.

Allow me to propose a third way: write your own web service! With blackjack! And hookers!

/images/Bender_Rodriguez.png

Start by making a new empty ASP.NET Web Application and adding a Generic Handler to it.

/images/add_generic_handler.png

We’ll have everything work off of a dictionary with different routes and handlers for each. First we can define a Route class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace JsonWebServiceDemo {
    public class Route {
        public delegate object RouteHandler(HttpRequest r);
        public RouteHandler Handler { get; set; }
    }
 
    public class DemoService : IHttpHandler {
        // ...
    }
}

Then we can start building out our dictionary. We’ll add a constructor to our DemoService class and build it there.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace JsonWebServiceDemo {
    public class Route {
        public delegate object RouteHandler(HttpRequest r);
        public RouteHandler Handler { get; set; }
    }

    public class DemoService : IHttpHandler {
        private Dictionary<string, Route> routes;

        public DemoService() {
            routes = new Dictionary<string, Route>();

            routes.Add("Foo", new Route() {
                Handler = delegate(HttpRequest r) {
                    return new { Foo = "Bar" };
                }
            });
        }

        // ...
    }
}

Now we can wire it up to the ProcessRequest method using some regex magic. I’m using named groups here to make things slightly more readable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using System.Text.RegularExpressions;

namespace JsonWebServiceDemo {
    public class DemoService : IHttpHandler {
        // ...

        public void ProcessRequest(HttpContext context) {
            Match pathMatch = Regex.Match(context.Request.Path, 
                @"/(?<HandlerName>\S+)/(?<RouteName>\S+)");

            if (pathMatch.Success) {
                string routeName = pathMatch.Groups["RouteName"].Value;
                object handlerResult = routes[routeName].Handler(context.Request);
            }
        }

        // ...
    }
}

And the serialisation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System.Text.RegularExpressions;
using Newtonsoft.Json;

namespace JsonWebServiceDemo {   
    public class DemoService : IHttpHandler {
        // ...

        public void ProcessRequest(HttpContext context) {
            Match pathMatch = Regex.Match(context.Request.Path, 
                @"/(?<HandlerName>\S+)/(?<RouteName>\S+)");

            context.Response.ContentType = "application/json";

            if (pathMatch.Success) {
                string routeName = pathMatch.Groups["RouteName"].Value;
                object handlerResult = routes[routeName].Handler(context.Request);

                context.Response.Output.Write(
                    JsonConvert.SerializeObject(handlerResult));
            }

            context.Response.Flush();
        }

        // ...
    }
}

And finally error handling.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System.Text.RegularExpressions;
using Newtonsoft.Json;

namespace JsonWebServiceDemo {   
    public class DemoService : IHttpHandler {
        // ...

        public void ProcessRequest(HttpContext context) {
            Match pathMatch = Regex.Match(context.Request.Path, 
                @"/(?<HandlerName>\S+)/(?<RouteName>\S+)");

            context.Response.ContentType = "application/json";

            if (pathMatch.Success) {
                string routeName = pathMatch.Groups["RouteName"].Value;

                try {
                    object handlerResult = routes[routeName].Handler(context.Request);
                    context.Response.Output.Write(
                        JsonConvert.SerializeObject(handlerResult));
                } catch (Exception ex) {
                    context.Response.StatusCode = 400;
                    context.Response.Output.Write(JsonConvert.SerializeObject(ex));
                }
            } else {
                context.Response.StatusCode = 400;
                context.Response.Output.Write(JsonConvert.SerializeObject(
                    new HttpException("Missing or invalid route specified.")));
            }

            context.Response.Flush();
        }

        // ...
    }
}

And there you have it. A simple, somewhat extensible way to implement a JSON web service using ASP.NET and without using ASMX or WCF. I might make another post on how to take this and make it a full RESTful web service later.

Full source code on GitLab