Reputation: 32725
I have an existing ASP.NET MVC website with actions like this:
public ActionResult Show(int id)
{
var customer = GetCustomer(id);
return View(new ShowCustomerModel(customer));
}
I now need the ability to perform these actions as part of an API, which will be called from third party applications. Ideally the action would look like:
public ActionResult Get(int id)
{
var customer = GetCustomer(id);
return Json(new CustomerResource(customer));
}
The question is, what ASP.NET MVC tools or patterns exist that allow me to combine these together - for example, Rails allows me to specify multiple return formats:
def index
@customer = get_customer(...)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @customer}
format.json { render :json => @customer}
end
end
Is that even a nice pattern? Should I perhaps just have:
public Customer Get(int id)
{
return GetCustomer(id);
}
And use action filters to selectively render as JSON or a view?
Upvotes: 2
Views: 323
Reputation: 15673
@Schwarty's answer is the best if you want to stick within your site and do the automagic ruby thing.
But I'd advise you build a separate front-end for the API. The main issue is versioning -- you really should not publish a non-versionable api. We humans reading web pages can easily adjust to an updated data model. Perhaps not easily, but we don't usually break when something changes. The consumers of your API, however, are machines that are oftentimes tied to your specific implementation and don't like change.
The short version: they are separate tasks and should be handled separately in most cases.
Upvotes: 3
Reputation: 25097
I'd create an ActionResult
that is smart enough to determine what kind of result you want based on the Accept Headers which are provided, like so:
public class AdaptiveActionResult : ActionResult
{
private readonly object _model;
public AdaptiveActionResult(object model)
{
_model = model;
}
public override void ExecuteResult(ControllerContext context)
{
var accept = context.HttpContext.Request.AcceptTypes;
if (accept == null || !accept.Contains("application/json"))
{
var viewResult = new ViewResult {ViewData = new ViewDataDictionary(_model)};
viewResult.ExecuteResult(context);
return;
}
var jsonResult = new JsonResult
{
Data = _model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
jsonResult.ExecuteResult(context);
return;
}
}
This can be expanded out to check if they want XML, RSS, Atom, what ever.
Then you can do it in your controller like so:
public ActionResult Index()
{
return new AdaptiveActionResult(new MyModel());
}
Upvotes: 8
Reputation: 2684
You could set up a custom action filter attribute to catch the content type when the action executes and store it into a parameter:
public class ContentTypeRequestFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.ActionParameters["contentType"] =
filterContext.HttpContext.Request.ContentType;
base.OnActionExecuting(filterContext);
}
}
Then in your controller you can decorate your action method, add the parameter, and do a check against that:
[ContentTypeRequestFilter]
public ActionResult Get(int id, string contentType)
{
var customer = GetCustomer(id);
if(contentType.ToLower() == "application/json")
{
return Json(new CustomerResource(customer));
}
return View(new ShowCustomerModel(customer));
}
From there you could adapt to other content request types (xml, etc) if needed. I took a similar approach with my blog post on Building a REST API architecture in MVC 3.
Upvotes: 6
Reputation: 811
You could have 3 actions for each one and create a custom attribute and create an ActionInvoker just like the way HttpPost and HttpGet work
[JsonRequest]
[ActionName("Get")]
public ActionResult GetAsJson(int id) {
//return as Json
}
[XmlRequest]
[ActionName("Get")]
public ActionResult GetAsXml(int id) {
//return as xml
}
public ActionResult Get(int id) {
//get stuff normally
}
That obviously needs a bit of coding to get working but would be re-usable.
Another way would be to create a CustomerResult object and just have the one Action and do as Daniel mentioned above but this logic could be put into your CustomerResult object.
Upvotes: 1
Reputation: 12611
It may not be exactly what you want, but you can check the request type and act accordingly.
public ActionResult Get(int id)
{
if (Request.IsAjaxRequest)
{
// do stuff
return new JsonResult(new { Foo = "Foo", Bar = "Bar" });
}
else
{
// do stuff
return View(myModel);
}
// if you need beyond IsAjaxRequest, you could
// check the controller's Request object for other
// indicators as to what type of result to
// send back
}
Upvotes: 3