Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Returning More Views in an ASP.NET MVC Action

0.00/5 (No votes)
18 Jan 2014 1  
This small code enables an MVC application to update many parts of the HTML in one request-response roundtrip.

Introduction

Recently, we have had a web-based project on which a very few of us (developers) worked. One of the most important goals was to create a complex UI like Facebook, but we didn't want to build a complicated, heavy weight client-side code, so we decided to minimize the amount of JavaScript code, and to render the HTML content on the server-side instead. My plan was to slice the page into many, nested, sometimes very small (like a single line in a listbox) partial views, and transfer the final HTML snippets to the client side, using very simple JS .ajax functions, and doing most of the work on the server-side.

Pretty soon, I understood that the built-in ActionResults of the MVC framework aren't suitable for this at all, so I have created an own solution.

The Solution 

I have created a new ActionResult class, which allows an ASP.NET MVC application to update more, unrelated parts of the HTML DOM, in a single request-response roundtrip. The aim of this is to render more partial views on the client side, so it's called MultipartialResult.

For example, you are working on a web-based email reader. When the user selects an email and then clicks on the Delete button, several things should happen at the same time:

  • the current email should be removed from the email listbox, and the next email should be selected
  • the preview pane should be updated by the content of the newly selected email
  • the indicator that shows the number of the emails in the inbox should be updated
  • the browser title (since it also shows the number of the unread emails) should be updated

In my case, the email list is a partial view, so is the preview pane, and the part that contains the number of the emails is a simple text in the DOM.

With the MultipartialResult, the action that handles the Delete click looks like this:

public ActionResult OnDelete(long EmailId)
{
     //... does the deleting
     //... creates the model for the new InboxList partial view (InboxListModel)
     //... creates the model for the PreviewPane partial view (PreviewPaneModel)
     //... calculates the number of the emails (EmailCount)
     //... renders the browser title with the updated unread email number (BrowserTitle)
    
     MultipartialResult result = new MultipartialResult();
     result.AddView("_InboxList","InboxListDiv",InboxListModel);
     result.AddView("_PreviewPane","PreviewDiv",PreviewPaneModel);
     result.AddContent(EmailCount.ToString(),"EmailCountDiv");
     result.AddScript(string.Format("document.title='{0}';",BrowserTitle));
     return result;
}

The AddView function will cause the "InboxListDiv" HTML element to be updated with the _InboxList view.
The AddContent function will cause the content of the "EmailCountDiv" HTML element to be updated with the given string.
The AddScripts function will cause the given JavaScript code to be executed on the client side.

In the event-handler on the client side (which works without page refresh of course), the only thing you have to do is to call the MultipartialUpdate in the OnSuccess JavaScript event:

@Ajax.ActionLink("Delete", "OnDelete", new { EmailId = Model.CurrentEmail.Id }, new AjaxOptions { OnSuccess = "MultipartialUpdate" }) 

or, you can use it in an Ajax form:

@using (Ajax.BeginForm("OnDelete", 
 new { EmailId = Model.CurrentEmail.Id }, new AjaxOptions { OnSuccess = "MultipartialUpdate" }))

or, you can use it in a jQuery .post or .ajax function

function deleteClicked(emailId) { 
    $.ajax({
        url: "/inbox/ondelete",
        type: "POST",
        data: { emailId: emailId },
        success: function (result) {
            MultipartialUpdate(result);
        },
    }); 

Background

The concept of the MultiPartial is very simple. It is inherited from the JsonResult class, it renders the specified elements into strings, collects those strings, packs them into a json data, and sends this json to the client side.

On the client side, a small JavaScript function iterates through those strings, and updates the DOM respectively.

When collecting the different kind of results, the MultipartialResult flags them properly by content:

public MultipartialResult AddView(string viewName, string containerId, object model = null)
{ 
        views.Add(new View() { Kind = ViewKind.View, 
        ViewName = viewName, ContainerId = containerId, Model = model });
  return this;
}
public MultipartialResult AddContent(string content, string containerId)
{
  views.Add(new View() { Kind = ViewKind.Content, Content = content, ContainerId = containerId });
  return this;
} 
public MultipartialResult AddScript(string script)
{ 
  views.Add(new View() { Kind = ViewKind.Script, Script = script });
  return this;
}

After the action returns the Result object, the MVC framework calls the ExecuteResult function, which produces a json string by processing the elements one by one:

        public override void ExecuteResult(ControllerContext context)
        {
            List<object> data = new List<object>();
            foreach (var view in views)
            {
                string html = string.Empty;
                if (view.Kind == ViewKind.View)
                {
                    //view result
                    html = RenderPartialViewToString(mController, view.ViewName, view.Model);
                    data.Add(new { updateTargetId = view.ContainerId, html = html });
                }
                else if (view.Kind == ViewKind.Content)
                {
                    //content result
                    html = view.Content;
                    data.Add(new { updateTargetId = view.ContainerId, html = html });
                }
                else if (view.Kind == ViewKind.Script)
                {
                        //script result
                    data.Add(new { script = view.Script });
                }
            }
            Data = data;
            base.ExecuteResult(context);
        } 

Note that rendering the partial view into string is not the subject of this tip.

On the client side, the only thing to do is to iterate through the json, and for all the elements update the DOM, or run the script:

function MultipartialUpdate(views) {
    for (v in views)
        if (views[v].script) {
            eval(views[v].script);
        }
        else {
            $('#' + views[v].updateTargetId).html(views[v].html);
        }
    return false;
}

I hope there is someone out there who can benefit from this. :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here