I like to allow the calling app to decide. I've peiced together a MultiViewController (much of the code I found online, I'll try to update with the credit when I find it) that, based on the action extension, will return the appropriate format. for example:
myapp.com/api/Users/1 - defaults to html based on route
myapp.com/api/Users.html/1 - html
myapp.com/api/Users.json/1 - json
myapp.com/api/Users.xml/1 - xml
myapp.com/api/Users.partial/1 - returns a partial view of action name (see code)
myapp.com/api/Users.clean/1 - partial html without styling, etc...
My controllers inherit from MultiViewController and instead of "return view(Model);" I simply call "return FormatView(Model); or FormatView("ViewName",Model);". The second if I need to apply a specific view to the result - not the implied view.
The MultiViewController looks like this. Pay special attention to FormatView, whitch returns an action result:
public abstract class MultiViewController : Controller
{
private const string FORMAT_KEY = "format";
public enum FileFormat {Html, Json, Xml, Partial, Clean}
protected MultiViewController()
{
RequestedFormat = FileFormat.Html;
}
protected FileFormat RequestedFormat { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var routeValues = filterContext.RouteData.Values;
if (routeValues.ContainsKey(FORMAT_KEY))
{
var requestedFormat = routeValues[FORMAT_KEY].ToString();
if (isValidFormat(requestedFormat))
{
RequestedFormat = (FileFormat)Enum.Parse(typeof(FileFormat), requestedFormat, true);
}
}
}
private bool isValidFormat(string requestedFormat)
{
return Enum.GetNames(typeof (FileFormat)).Any(format => format.ToLower() == requestedFormat.ToLower());
}
protected ActionResult FormatView(string viewName, object viewModel)
{
switch (RequestedFormat)
{
case FileFormat.Html:
if (viewName != string.Empty)
{
return View(viewName,viewModel);
}
return View(viewModel);
case FileFormat.Json:
return Json(viewModel);
case FileFormat.Xml:
return new XmlResult(viewModel);
case FileFormat.Partial:
//return View(this.ControllerContext.RouteData.Values["action"] + "Partial");
return PartialView(this.ControllerContext.RouteData.Values["action"] + "Partial");
case FileFormat.Clean:
if (viewName != string.Empty)
{
return View(viewName, "~/Views/Shared/Clean.master", viewModel);
}
var v = View(viewModel);
v.MasterName = "~/Views/Shared/Clean.Master";
return v;
default:
throw new FormatException(string.Concat("Cannot server the content in the request format: ", RequestedFormat));
}
}
protected ActionResult FormatView(object viewModel)
{
return FormatView("", viewModel);
}
}
Clean.master is simply a master page that doesn't contain any additional html - it takes the view (so that I can consolidate any partial classes) and renders it with clean html that can be placed directly.
If I want json - the controller builds my viewmodel and then returns that view model as json, instead of sending to the default view - the same with .xml.
Partial views are a little interesting in that, by convention, all of my main views are broken down into partials, so that particular partial can be requested by itself - this comes in handy for mimicking the functionality of an updatepanel using jquery without all the junk associated with the updatepanel.