views:

1683

answers:

7

I am having trouble deciding if a controller action, which is called by AJAX, should return a partial view, or the "raw" JSON.

Returning a partial view, with rendered HTML makes it easier for the javascript to simply update the current DOM with the returned HTML. However, it does limit what javascript client consuming the webservice can do with the returned HTML.

On the other-hand, having the controller action return JSON would require the javascript making the call to "manually" create the markup based on the JSON that is returned.

So as usual, each approach has it's benefits and weakness. Are there any other pros/cons for each approach?

A: 

If you use JQuery you should use JSON or XML because it's simpler to modify, but if your ajax call only returns items for an listbox for example you could also use html.

My personal preference is JSON or XML because use JQuery very often

JSC
+14  A: 

If you are using the MVC paradigm, the controller should return data (JSON) and let the view sort it out for itself, just like its job is to find/adapt the data in the model and pass it on to the view on the server side.

You get browny points for

  • preserving separation of concerns between logic and UI

  • making your ajax actions testable (good luck testing the HTML returned from that action...)

It's a bit more complicated maybe, but it fits.

You can use client-templating systems such as what is now available in the MS Ajax Toolkit to help take some of the load and preserve the logic/rendering separation on the client side.

So I'd say JSON, definitely. But hey, YMMV as usual...

Denis Troller
What he said. Even if you don't care about the MVC pattern, JSON will be easier to manipulate and verify on the client side.
Gromer
Almost all my controller actions return json/xml. However, there's one action per page that returns an html page. You could go to a completely static client and have all your actions return json, but I prefer to send initial data embedded in the html as json to save a round trip, since the client has to make a connection to the server to see if the file has been modified anyway.
Juan Mendes
+2  A: 

To maintain seperation of concerns you should return JSON.

When you return html you limit what you can do with the returned data. If you need a list of data and want to present it in different ways use JSON, othewise you would have to have different methods on the server to get the different renderings of the same data.

Jeffrey Hines
+10  A: 

In my opinion, returning JSON and then letting the client side view sort it out can be messy because of the following limitations:

  1. No standard templating language for JavaScript. In the worst case scenario, you'll be tempted to concatenate strings to form the HTML that you require.
  2. No easy way to unit test the HTML generated by your concatenation code.
  3. Lack of IntelliSense for your JavaScript means that you're also prone to make more mistakes.

The way I've handled this is to return rendered HTML, BUT return this rendered HTML using a partial view instead. This gives you the best of both worlds. You've got Server-side templates as well as IntelliSense support.

Here's an example:

Here's my Ajax call, as you can see all it does is replace the html for my unordered list:

FilterRequests: function() {
    $.post("/Request.aspx/GetFilteredRequests", { }, function(data) {
        $('ul.requests').html(data);
    });
},

Here's my action on my controller:

public ActionResult GetFilteredRequests(string filterJson)
{
    var requests = _requestDao.LoadAll();

    return PartialView("FilteredRequests", requests);
}

Finally here is my partial view (there's no need to understand this, I'm just showing you how complex some rendering can get in a real world application. I'd dread doing this in JavaScript. You'll also notice that my partial view in turn calls other partial views as well.):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Request>>" %>
<%@ Import Namespace="Diangy.HelpDesk.Models.Lookups"%>
<%@ Import Namespace="Diangy.HelpDesk.Models.Requests"%>
<%@ Import Namespace="System.Web.Mvc.Html"%>

<% foreach (var request in ViewData.Model) %>
<%{%>
    <li class="request">
        <h2>#<%= request.Id %>: <%= request.InitialInteraction().Description %></h2>
        <p>from <%= request.Customer.FullName %> (<%= request.Customer.EmailAddress %>), <%= request.InitialInteraction().UsableTimeStamp %></p>

        <h3>Custom Fields & Lookups</h3>
        <div class="tabs">
            <ul>
         <li><a href="#customfields<%= request.Id %>">Custom Fields</a></li>
         <% foreach (var lookupDefinition in (List<LookupDefinition>)ViewData["LookupDefinitions"]) %>
         <%{%>
         <li><a href="#<%= lookupDefinition.Name.ToLowerInvariant().Replace(" ", "") + request.Id %>"><%= lookupDefinition.Name %></a></li>
         <%}%>
        </ul>
        <% Html.RenderPartial("CustomFields", request); %>
    </div>

    <% Html.RenderPartial("Discussion", request); %>
    <% Html.RenderPartial("Attachment", request); %>
    </li>
<%}%>
Praveen Angyan
totally not agree. With json you have little more work to do, but network traffic is much smaller, and rendering/presentation logic stays in "smart" client (jquery, extjs,...). New MS AJAX will have templateing engine
Hrvoje
@hrvoje By that logic we should have all controller actions return just the view's data. View engines are useless in that world. I think Praveen is saying that returning html rendered by the view engine is ideal when your updating more than 1 DOM element. This approach is exactly the same as calling "View(...)" in your action - "ParitialView()" rendering an html snippet and "View()" rendering an html doc. Unless you want to discard the concept of a view engines I'm not sure how you can disagree so strongly.
TheDeeno
Completely disagree. Look at http://code.google.com/closure/templates/docs/helloworld_js.html. It's a very mature and fast templating systems, no need for ugly string concatenation. Keeping this logic separate allows you to write another client (iphone, swing, flash) without rewriting server side code because you relegated to server to do what it does best -- provide data -- and moved the view part to the client. Google's templating system also allows you to run your templates server side in case you need to serve a client that doesn't support javascript.
Juan Mendes
+2  A: 

Why not both json and html? In current project we are making routes so you can choose from front end, which format is the best suited in some case...so why don't make two controllers, first will return expected data in json and the other controller will return same data but with html...this way you can choose from let's say jQuery what and when you want and in which format you want it...and the best thing is, for different format you just need to call different adress...

in other words, make it restful, baby! :)

cheers

Marko
That could also be taken care of using the accept content type of the request, since you are actually looking at the same resource (uri), but a different representation.I don't know if ASP.net MVC could be made to route to a different action based on that though, which seems better to me than a giant switch case on the content-type in the action handler...
Denis Troller
I think it is more cleaner to did something like this:{controller}/{action}/{...}and the from jQuery if you need json format:json/list/....and if you need html:html/list/...cheers
Marko
+2  A: 

As for me, I choose data-driven approach. Here is a small set of features for both:

Data-driven:

  1. JSON from server (Controller sends model directly to response)
  2. JS template binding (May take more time at client side)
  3. Low bandwidth load
  4. It's just soooo sexy!

Html-driven:

  1. HTML from server (Controller sends model to some partial View, it renders result and returns it - may take more time at server side)
  2. No overload in JS binding
  3. High bandwidth load
  4. Not sexy, no no :)

So you can maintain MVC even with HTML-driven approach though it will be a bit harder.

Ivan Suhinin
+1  A: 

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.

Hal
Pretty interesting. A lot like what Rails allows you to do. Makes sense for a lot of situations.
Charlie Flowers