Creating a RESTful Web Service Using ASP.Net MVC Part 15 – Adding Connectedness to JSON

January 25, 2009 01:29 by admin

At the end of my last post about adding connectedness to our web service I had added support for XHTML and XML representations. This allowed us to inject URIs into the representation without changing our Model classes. This was relatively simple because, for those representations, we had stuck to the principle introduced in Multiple Representations of having a transformation layer between internal and external representations of our entities. In this post I want to tackle JSON representations.

So far, our framework has used the MVC framework’s JsonResult to serialise a Model object into a JSON string to return to the client. Internally, this class uses a JavaScriptSerializer to automatically serialise our object. The JavaScriptSerializer class does allow us to change the way it serialises an object. Through its RegisterConverters method we can add one or more classes derived from JavaScriptConverter. A JavaScriptConverter class must implement three methods:

  • SupportedTypes – returns a list of Types for which the class can provide custom serialisation and deserialisation functionality;
  • Serialize – called by the JavaScriptSerializer when an object of a supported Type needs serialising
  • Deserialize – called by the JavaScriptSerializer when an object of a supported Type needs deserialising

By implementing your own JavaScriptConverters you get full control over how an object is serialised. So this looks just the thing we need… but how do you tell JsonResult about your home grown JavaScriptConverters? Well if we were writing an AJAX enabled page we could use the jsonSerialization element in our web.config file to register our converters. Naively, I added this element to the web service’s web.config file, put a break point in my prototype JavaScriptConverter and hit F5, then… nothing. My JavaScriptConverter was completely ignored. Using Reflector to look at the implementation of JsonResult, the reason is obvious. Its ExecuteResult simply instantiates a JavaScriptSerializer and calls its Serialize method. At no point does it call RegisterConverters, let alone read from the web.config file. Those entries in the web.config file are used elsewhere in the .Net framework, by classes that are marked “internal” so are not available to us.

So, what I thought was going to be a simple task became a lot more complex. We could compromise and add fields to our Model classes to support the properties we want visible in our external representations… but having managed to get the other representations (XHTML and XML) working without compromising, I wasn’t going to give up that easily. I needed to replace JsonResult so that we would have full control over the initialisation of the JavaScriptSerializer. I created JsonConverterResult. This follows the same pattern as the other custom ActionResult class we created, XsltResult. It hands off the complexity of deciding which JavaScriptConverters should be registered and performing the serialisation to another class, JsonSerializer.

Here is JsonSerializer’s code to initialise a JavaScriptSerializer:

private JavaScriptSerializer InitialiseSerializer(ControllerContext controllerContext)

{

    JavaScriptConverterEngineResult javaScriptConverterEngineResult = JsonConverterEngine.FindJsonConverters(controllerContext);

 

    if (javaScriptConverterEngineResult.Errors.Count > 0)

    {

        StringBuilder builder = new StringBuilder();

        foreach (string str in javaScriptConverterEngineResult.Errors)

        {

            builder.AppendLine();

            builder.Append(str);

        }

 

        Exception ex = controllerContext.HttpContext.Server.GetLastError();

 

        if (controllerContext.HttpContext.AllErrors == null)

        {

            throw new InvalidOperationException(string.Format("…"));

        }

    }

 

    JavaScriptSerializer serializer = new JavaScriptSerializer();

    serializer.MaxJsonLength = javaScriptConverterEngineResult.MaxJsonLength;

    serializer.RecursionLimit = javaScriptConverterEngineResult.RecursionLimit;

    serializer.RegisterConverters(javaScriptConverterEngineResult.Converters);

 

    return serializer;

}   

Using the standard ActionResult pattern, it uses a pluggable engine to find the JavaScriptConverters to register. For good measure the engine also returns the additional configuration items MaxJsonLength and RecursionLimit that can be set through jsonSerialization element in a the web.config. As with XsltResult, I have, for now, hardcoded which engine to use rather than allowing it to be configured in the web.config file… maybe one day I’ll come back to that!

Aside: If an error is returned by the engine, the above code only throws an exception if we are not already handling an exception (that is why it checks the AllErrors property). Otherwise we could hit an infinite loop... something I managed to do as I experimented.

So the next decision is how should the engine find the JavaScriptConverters? Given that I wanted the JavaScriptConverter classes to live in the same folders as the ASPX and the XSLT view files, I had a couple of ideas:

  1. Use reflection to find classes with a particular format name and a namespace to match the folder containing the view we are trying to use. The class to be loaded probably wouldn’t be a JavaScriptConverter itself, instead it would be a registrar that could be asked which JavaScriptConverters are required.
  2. Reuse the jsonSerialization element in the web.config file

Option 1 would make the developer’s job easier. They would just create the class and magically it would be used… in the same way that the ASPX and XSLT files are executed without any configuration. However, the developer would have to take care to keep the namespace of the class consistent with its location. A bigger issue with this approach is deciding how the engine should construct the namespace to search? The last part of the namespace should be the path to the folder containing the view… but what about the root? I thought perhaps the root could be based on the namespace of the HttpApplication or perhaps it could be stored in the web.config file.

Option 2 would require the developer to register the JavaScriptConverters in the web.config file. That does provide some advantages. For example, since web.config files inherit from each other we can add and remove JavaScriptConverters in each folder. One disadvantage would be that within a particular folder the same JavaScriptConverters will be loaded regardless of the Action being rendered (i.e. both Item and List would be rendered with the same JavaScriptConverters).

Option 1 seemed too fragile to me. Option 2 meant we could reuse a well known configuration element along with the power of hierarchical configuration. So I created a WebConfigJavaScriptConverterEngine which loaded JavaScriptConverters based on the web.config file, if present, in the view folder (I’ve snipped the error handling from the code below, see the full project for the full version):

public virtual JavaScriptConverterEngineResult FindJsonConverters(ControllerContext controllerContext)

{

    string controllerName = controllerContext.RouteData.GetRequiredString("controller");

 

    string cacheKey = _cacheKeyPrefix_JsonConverter + controllerName;

    JavaScriptConverterEngineResult result = JavaScriptConverterCache.Get(cacheKey);

 

    // If the converters are not cached, we need to create a

    // JavaScriptConverterEngineResult from scratch

    if (result == null)

    {

        // Load the appropriate Web.Config for the current controller.

        System.Configuration.Configuration configuration = WebConfigurationManager.OpenWebConfiguration(String.Format(CultureInfo.InvariantCulture, WebConfigLocationFormat, controllerName));

 

        // Get the JSON section.

        ScriptingJsonSerializationSection jsonSection = (ScriptingJsonSerializationSection)configuration.GetSection("system.web.extensions/scripting/webServices/jsonSerialization");

 

        // Load the configuration details into the result object

        result = new JavaScriptConverterEngineResult(new List<JavaScriptConverter>());

        result.MaxJsonLength = jsonSection.MaxJsonLength;

        result.RecursionLimit = jsonSection.RecursionLimit;

        foreach (Converter converterDefinition in jsonSection.Converters)

        {

            Type converterType = BuildManager.GetType(converterDefinition.Type, false);

            result.Converters.Add((JavaScriptConverter)Activator.CreateInstance(converterType));

        }

 

        // Add the new result to the cache

        JavaScriptConverterCache.Set(cacheKey, result);

    }

 

    return result;

}

As with the VirtualPathProviderXsltEngine, the WebConfigJavaScriptConverterEngine caches the results. It makes use of the .Net framework classes that wrap the jsonSerialization configuration element.

With this framework in place, I created an ItemJsonConverter and an ListJsonConverter. Just to prove it worked, I registered the ListJsonConverter in the root web.config file and the ItemJsonConverter in a  web.config file in the /View/Products folder.

Finally, it is possible to inject extra properties into the JSON being created for external consumption. This Serialize method adds an Href property to the JSON… allowing us to add connectedness to the representation:

public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)

{

    Dictionary<string, object> result = new Dictionary<string, object>();

    Product product = obj as Product;

 

    if (product != null)

    {

        // Create the basic representation

        result["Id"] = product.Id.ToString("N");

        result["Name"] = product.Name;

        result["Category"] = product.Category;

        result["Details"] = product.Details;

 

        // Add an extra Href property to the representation.

        RouteValueDictionary routeValueDictionary = new RouteValueDictionary();

        routeValueDictionary.Add("idString", product.Id.ToString());

        routeValueDictionary.Add("categoryString", product.Category);

        RequestContext requestContext = new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData());

        result["Href"] = new UrlHelper(requestContext).Action("Item", "Products", routeValueDictionary, HttpContext.Current.Request.Url.Scheme, null);

    }

 

    return result;

}

So there we have it. Connectedness added to our JSON representation and also a separation between the internal and external representations… without compromising the Model classes. I can’t say using JavaScriptConverters is as easy as transforming XML with XSLT (the above example shows you have to write a fair bit of code to serialise even a small class) but at least it is possible.

The code for this version of the web service can be downloaded here: RESTfulMVCWebService15.zip (93 kb) 

UPDATE: I have recompiled this code against ASP.Net MVC RC1. This download now includes minor changes required for RC1. I have also removed some code behind files.

UPDATE: I have changed the CSS in the download. In Firefox, the DIV containing the Help menu overlaid the main menu, so you couldn’t actually click on the menu items. Thank you to Jim Solderitsch for reporting the problem.

Another Aside: I’ve also fixed an issue with this version that prevented the site from working when hosted within the Visual Studio Development Server (thank you to Adam Storr for pointing it out). In my last post I mentioned using one of ActionLink’s more heavily parameterised overloads to ensure an absolute URI was returned. I was passing in Context.Request.Url.Authority. Unfortunately, this meant the URI created would include two Port numbers. I could have changed to passing in Context.Request.Url.Host to remove the Port number. However I decided to pass an empty string instead, allowing the framework to extract the host itself. There isn’t an overload that allows you to force the Port number, which is a shame as it means ActionLink cannot be used to redirect a resource to another server if really necessary.

kick it on DotNetKicks.com


Comments (5) -

January 30. 2009 17:10

Khalid Abuhakmeh

That's cool, but you do realize that the newest WCF .NET 3.5 SP1 has the ability to create REST based services. Using ASP.NET MVC to create one works, but isn't ideal.

Khalid Abuhakmeh

January 30. 2009 17:39

Piers

Yes, I have played with it a bit, however it doesn't have the flexibility I was looking for. In particular, the MVC puts in place a framework that allows me to present data via a view, keeping internal and external representations well separated. I can also serve up those representations in numerous formats, not just XML and JSON.

To be honest, I haven't looked at WCF in great detail and was considering looking at it next.... but what I have seen just isn't flexible enough.

Also... I did justify in the first post why I chose MVC rather than WCF... the reason? Because I wanted to learn MVC.

Piers

February 16. 2009 15:55

Jonas

Great series, I really enjoyed it, thank you!

Have you had a chance to look at the MVC Contrib project and their SimplyRestfulRouteHandling? I think it would be a nice addition to your service.

Jonas

February 16. 2009 20:23

Piers

Thanks for your comment. Yes, before Preview 5 was released the approach taken by SimplyRestfulRouteHandling was the easiest way to filter by HTTP Verb (and support overloaded verbs). But Preview 5 introduced AcceptVerbs which allows us to do much the same filtering within the framework. I did eventually extend AcceptVerbs so that overloaded POST was accepted in Post 9.

So, yes, I was aware of the great SimplyRestfulRouteHandling, but didn't need it in the end.

Piers

Piers

January 30. 2010 02:00

Alondra

hi buddy, I found your site from wikipedia and read a few of your other blog posts.They are cool. Pls continue this great work.

Alondra

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading