Returning data and view from a single controller action

Introduction

In ASP.NET MVC the controller is the component that receives requests from the client, performs some actions and returns results back to the client. The controller typically returns results as data or views. The ASP.NET MVC 4 beta comes with a new type of controller called ApiController that helps to create RESTful applications in an easy and efficient way. So having two types of controllers now experts advise to use the default controller for returning views and the ApiController for returning data. Since the default controller itself capable of delivering both data as well as views I don’t see many benefits by introducing the new ApiController. It would be great if the default controller has been extended to add the RESTful features into the MVC framework.

In most of the MVC applications we have controllers that return both data and views. The problem is that from a single controller action we can’t return both data as well as views. Many times we end up writing two controller actions one to return view and the other to return data in either JSON or XML format (for AJAX calls). Having two controller actions makes more chances of code duplication. It would be really nice if we can return both data as well as view from a single action itself. Doing this stuff is quite difficult in ApiController because it doesn’t have capabilities to return views. So we left with the option of using the default controller to achieve that.

Controller vs. ApiController

Although both types of controllers looks very similar they are not even derived from the same base class or implements the same interface (at-least in the beta version). The default controller derives from the abstract class ControllerBase (which implements IController) and the ApiController implements the interface IHttpController. From the default controller we can return both data and view but from the ApiController we can return only data.

Plan

So at the higher level here is what we are going to accomplish. We are going to make the controller actions to return either data or view based upon the request parameters. If the request is an AJAX request we are going to return data in specific format based upon the Accept header.

In case of non-AJAX requests, if the url contains "type" parameter in the query-string with value as either "xml" or "json" then we are going to return data in that format else we are going to return view.

Strategy

First I thought of creating a custom action result but I soon understood returning views from action results going to be very difficult and its because the methods that does the job exists as protected methods in Controller. So one better idea is extending the default controller with adding our methods that performs what we need. All the other controllers in the application should derive from the extended controller.

Implementation

Lets create our custom controller BaseController by extending the default controller.

public class BaseController : Controller
{
	protected virtual ActionResult Result(object data)
	{
		// returns data as either xml,  json or view.
	}

	protected virtual string GetContentType()
	{
		// returns the content type of response
	}
}

Our BaseController has couple of methods. The Result() method which will be called from the controller actions to return data or view. The GetContentType() method determines the response type, for data it is either XML or JSON and for view it is HTML.

Here is the implementation of both methods.

protected virtual ActionResult Result(object data)
{
   ActionResult actionResult;

   switch (GetContentType().ToLower())
   {
       case "xml":
           actionResult = new XmlResult(data);
           break;
       case "json":
           actionResult = new JsonResult
           {
               Data = data,
               JsonRequestBehavior = JsonRequestBehavior.AllowGet
           };
           break;
       default:
           actionResult = View(data);
           break;
   }

   return actionResult;
}

protected virtual string GetContentType()
{
	var type = Request.QueryString["type"];

	if (string.IsNullOrEmpty(type))
	{
	   if (Request.IsAjaxRequest())
		   type = Request.Headers["Accept"].Contains("application/json") 
					? "json" 
					: "xml";
	   else
		   type = "html";
	}

	return type;
}

The Result() method invokes the GetContentType() to know the response content-type whether it is "xml", "json" or "html". Based upon the type it instantiates and returns the corresponding ActionResult. The XmlResult is a custom action result that returns the model as XML (you can see the implementation in the attached source).

Here is an example controller with a single action that returns data (XML, JSON) or view (HTML).

public class MotorController : BaseController
{
   public ActionResult Index()
   {
       return Result(new CarRepository().GetAll());
   }
}

The below table list down the different action results returned from the above controller action for different addresses and headers.

Address Is Ajax? Action Result
http://localhost:7777/motor No Returns view
http://localhost:7777/motor?type=xml No Returns data in XML format
http://localhost:7777/motor?type=json No Returns data in JSON format
http://localhost:7777/motor Yes Returns data in XML format
http://localhost:7777/motor?type=xml Yes Returns data in XML format
http://localhost:7777/motor?type=json Yes Returns data in JSON format
http://localhost:7777/motor
Accept: "appliation/json"
Yes Returns data in JSON format
http://localhost:7777/motor
Accept: "appliation/xml"
Yes Returns data in XML format

Download Sample

blog comments powered by Disqus