Update - May 2012
This code has been made obsolete by ASP.NET MVC 4 Web API, which does everything here and much more.
Introduction
I recently developed an ASP.NET MVC application that uses jQuery to call JsonResult
controller methods and display the data in the browser. When the application was finished, I realized that my JsonResult
controller methods effectively constituted an API that allowed other applications to query the data behind my application, without any additional effort on my part. It was an "accidental API", but worked pretty well. I then got a request to return data as XML instead of JSON, and this is where I hit a bit of a wall with MVC.
ASP.NET MVC has several built-in ActionResult
types, including one data serialization type: JsonResult
. While JsonResult
is awesome, it does not address the need to be able to return data in other serialization formats, especially XML. It would be nice to write one controller method that can return either JSON or XML, depending on what the user has requested. Some people have achieved this by branching within their controller methods, returning a JsonResult
if X and an XmlResult
(from MvcContrib) if Y. Others have created custom ActionFilter
s that detect the "accept-types" header from the user's request and decide what to return based on that.
Neither of these solutions particularly appeal to me. In the first case, branching based on a data format parameter or header value inside the controller method seems a bit ugly - you're repeating this if logic for every controller method that returns serialized data, and this logic could become quite complex and make your controller difficult to understand. In the second case, it seems logically incorrect to have a JsonResult
class for returning JSON data, but switch to an ActionFilter
when something more advanced is required - why not have another ActionResult
type that fits in with existing pattern, but is more flexible? And finally in both cases, the controller signature uses the base ActionFilter
type, instead of the more specific (and this preferable) JsonResult
or XmlResult
type.
To meet my requirements, I've created a class called SerializedDataResult
that can return data in either JSON or XML format, depending on either explicit instructions within the controller method, or implicit instructions with the request querystring, form, or headers. There is also a helper method implemented as an extension to the controller class, similar to the Json
method used to create JsonResult
instances. The code is based on the JsonResult
class (from the open-source ASP.NET MVC 3 library, thanks Microsoft!) and the XmlResult
class (from the MvcContrib project), and inspired by the various attempts at solving this problem using ActionFilter
s.
Using the Code
There are two basic scenarios for using this code. In the first scenario, you know what format you want to return the data in, and you tell SerializedDataResult
using the SerializedDataFormat enum
.
public SerializedDataFormat JsonQuote(string code)
{
var svc = new StockMarketService();
return this.Serialize(
svc.GetQuote(code),
SerializedDataFormat.Json,
null, null, null,
SerializedDataRequestBehavior.AllowGet);
}
public SerializedDataResult XmlQuote(string code)
{
var svc = new StockMarketService();
return this.Serialize(
svc.GetQuote(code),
SerializedDataFormat.Xml,
null, null, null,
SerializedDataRequestBehavior.AllowGet);
}
In the second scenario, the requesting user decides what format to return the data in. They can do this either in the querystring, form, or accept-type headers of the request.
public SerializedDataResult AutoQuote(string code)
{
var svc = new StockMarketService();
return this.Serialize(svc.GetQuote(code),
SerializedDataRequestBehavior.AllowGet);
}
SerializedDataResult
will first check request fields in the querystring and form for a field named "format" (this is configurable). This field must have a value of 'xml' or 'json'. If it doesn't find a format field in the querystring or form, it will check the accept-types header value for recognized types, specifically 'text/xml
' and 'application/json
'. If one of these types is found, it does the obvious thing and returns data in the corresponding format. Finally, if it can't figure out what type the requesting user wants, it defaults to JSON.
e.g. http://www.example.com/StockMarket/Quote/ABC?format=xml - this will return a quote in XML format
public SerializedDataResult ComplexQuote(string code)
{
var svc = new StockMarketService();
return this.Serialize(
svc.GetQuote(code),
SerializedDataFormat.Auto,
"dataformat", "application/quoteserver", Encoding.Unicode, SerializedDataRequestBehavior.AllowGet);
}
Points of Interest
This ActionResult
uses the RequestBehavior.AllowGet
/DenyGet
pattern from JsonResult
and is explained by Phil Haack in this post. I nicked the logic for this out of the JsonResult
code.
History
- Feb 10 2010 - Version 1
- Feb 11 2010 - Version 1.1 - Added some unit tests to project, took CSS and JavaScript files out of the sample, corrected content encoding for XML results