Rails has a nice idiom that makes it easy for you to have a single action method return properly formated data (json, xml, just the data) based on the format specified by the client (or else deduced from the request. It looks something like this ...
respond_to do |format|
format.html #edit.html.erb
format.json {render :text=> <your json here>), :layout=> false}
format.xml ...
end
What is the preferred way to do this in ASP.NET MVC? Ideally, I'd like the framework to work in the same way as Rails (e.g. be able to return the ViewData properly formatted for the format specified by the client or else deduced from the request itself).
Rails also allows you to create views that are specific to each type giving you the opportunity to essentially return the same data to all views and letting them handle formatting the data correctly (so you have a view that builds xml, another that builds json and yet another that builds html). Is this possible with ASP.NET MVC? In fact, this model seems to best keep with the goal of separating concerns imho as it lets the controllers return view-agnostic data whereas most approaches I see today (including the above line "format.json .... :layout => false") do the JSON conversion inside the controller and return that data directly to the client given a request for that format.
Anyhow ... suggestions, thoughts, recommendations?
Thanks
-
In ASP.NET MVC, controller actions generally return objects that derive from
ActionResult
, which is then invoked by the runtime while generating the response stream.Out-of-the-box you have several classes that derive from
ActionResult
-ContentResult
for text results,ViewResult
for content from a view,JsonResult
for serializing an object hierarchy into JSON,RedirectResult
for redirecting, and so on.Generally you pass in the model to the result and let it decide how to generate the result, but it doesn't have to be the same model - I can pass a different object to each result if necessary.
The concrete type of result returned by an action is not 'baked in' to the action's signature - you can easily pass in a format parameter to your action, and have it generate and return a different ActionResult accordingly:
public ActionResult ListProducts(string format) { List<Product> products = ProductService.GetAllProducts(); if (format == "JSON") { // eg., transform model for JSON consumption List<JsonProduct> jsonProducts = ProductService.ToJSON(products); return Json(jsonProducts); } else if (format == "XML") { return new XmlResult(products); }
// default is to return HTML from view, which expects List<Product> for model return View(products);
}
Note that the methods
Json()
andView()
are built into the controller and are convenience methods for returningJsonResult
andViewResult
respectively.XmlResult
is an example of a customActionResult
that takes an object, serializes it to XML, then returns the result as an XML stream.The example is a little contrived, but it shows that the controller orchestrates all the work of selecting the result and constructing / transforming the model that is passed to that result. Controller actions should still be light-weight though so you offload the heavy tasks to services, such as loading the model from the business layer, or transforming objects in one model to objects in another model, such as for JSON consumption.
wgpubs : The only problem here is that rarely is it so easy to just return Json (your model). Typically, I'd expect folks to be using something like JQuery or ExtJs in the client that requires the Json (or xml) to be structured in a particular way. So ... where does that restructuring happen? If we should do it in the controller ... where/how? Also, if we do it there ... does this not violate "separation of concerns"? If we do it in the View ... how do we modify the framework to intelligibly pick the right view based on the requested format? Thanks.Sam : I agree, most of the time you will need a separate model for working with JSON. I don't see why separation of concerns is violated though - the controller's concern is to provide the model to the view, which includes working out what that model should be, and which view should be given that model. It's entirely relevant for the controller to do that. It would definitely be a violation if the view was doing the work of the controller. I edited my sample to indicate model construction.wgpubs : I guess I'm kind of torn on this one because yes, the controller should be responsible for how the data is packaged and sent of to the view ... but does that mean it should be responsible for such if that means returning the data in potentially many formats based on one or many client-side frameworks being used (e.g. ExtJs, Jquery, Mootools, etc...)? You see, we aren't just returning a bunch of data here ... we are returning data FOR JQuery or FOR ExtJs. That is where the separation thing gets a bit more obscured and also unit testing controllers a bit more hairy.Sam : Why would you need to return different formats for different JavaScript frameworks? Surely they can all consume the same JSON? It's just objects, after all - can you give an example of where this would be necessary?wgpubs : Nope. The JQuery and ExtJs widgets all have detailed specifications for what the JSON needs to look like ... and they aren't uniform and thus, my question: "By returning JSON in a format suitable for a given framework are we not violating proper separation of concerns?" Now I know most folks are following your approach here as it is the most straightforward ... but I'm wondering if it might be best to return the *same* DTO regardless of request type and define diff. views per request type (e.g. index_json.ascx, index_xml.ascx, index.aspx)? Seems more testable and prop. separated to me.
0 comments:
Post a Comment