ASP.NET MVC - SerializedDataResult
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 ActionFilters that detect the "accept-types" header
from the user's request and decide what to return based on that.
Neither of these solutions particularly appeals 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 ActionFilters.
Download SerializedDataResult.zip - 19.17 KB
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.
// data will be returned in JSON format
public SerializedDataFormat JsonQuote(string code)
{
var svc = new StockMarketService();
return this.Serialize(
svc.GetQuote(code),
SerializedDataFormat.Json,
null, null, null,
SerializedDataRequestBehavior.AllowGet);
}
// data will be returned in XML format
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.
// data will be returned in the format requested by the user,
// or JSON if it can't be determined what format the user wants
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
// a more complex call to the Serialize helper method
// data will be returned in the format requested by the user,
// or JSON if it can't be determined what format the user wants
public SerializedDataResult ComplexQuote(string code)
{
var svc = new StockMarketService();
return this.Serialize(
svc.GetQuote(code),
SerializedDataFormat.Auto,
"dataformat", // request querystring / form field containing format
"application/quoteserver", // content-type to put in response header
Encoding.Unicode, // encoding type for response
SerializedDataRequestBehavior.AllowGet);
}
Points of Interest
This ActionResult uses the RequestBehavior.AllowGet/DenyGet pattern from JsonResult and 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 sample, corrected content encoding for xml results
Post Comment
njHKf1 Really appreciate you sharing this blog article.Really thank you! Great.