MVC – Updating multiple partial views from a single (AJAX) action

Sometimes you need to make an AJAX call from your MVC view to perform some server side actions on user changes. You may then want to return some calculated data in a partial view so that you can update the view as presented to the user.

What if you have modified data in more than one partial view?

An application I worked on required some server side calculations that updated both data in a table (one partial view) and summary data displayed in the footer of the page (a second partial view). I would not want to make two separate service side calls to update the two partial views, so I looked into a mechanism to allow me to return the two partial views in the response from the server side call.

I realised that if I could package the two partial view responses into a single object, I could pass this to the client in the response, then use my client side script (JQuery in my case) to unpack the responses and update the two partial views.

The tricky bit was how to package up the partial views. Google pointed to this method to render a Razor view into a string(obtained from ):

public static String RenderRazorViewToString(ControllerContext controllerContext, String viewName, Object model)
{
  controllerContext.Controller.ViewData.Model = model;

  using (var sw = new StringWriter())
  {
    var ViewResult = ViewEngines.Engines.FindPartialView(controllerContext, viewName);
    var ViewContext = new ViewContext(controllerContext, ViewResult.View, controllerContext.Controller.ViewData, controllerContext.Controller.TempData, sw);
    ViewResult.View.Render(ViewContext, sw);
    ViewResult.ViewEngine.ReleaseView(controllerContext, ViewResult.View);
    return sw.GetStringBuilder().ToString();
  }
}

Using this method, I was able to create a controller action that returned a JSON object containing the rendering for my two partial views:

[HttpPost]
public ActionResult _CalculateValues(MyViewModel model)
{
  ModelState.Clear();
  calculationService.CalculateValues(model, User.Identity.Name);
  // The total values and summary values are displayed in two partial views
  // We can't normally return two partial views from an action, but we don't want to have another server
  // call to get the second one, so we render the two partial views into HTML strings and package them into an
  // an anonymous object, which we then serialize into a JSON object for sending to the client
  // the client side script will then load these two partial views into the relevant page elements
  var totalValuesPartialView = RenderRazorViewToString(this.ControllerContext, "_TotalValues", model);
  var summaryValuesPartialView = RenderRazorViewToString(this.ControllerContext, "_SummaryValues", model);
  var json = Json(new { totalValuesPartialView, summaryValuesPartialView });
  return json;
}

Note that my two partial views (“_TotalValues.cshtml” and “_SummaryValues.cshtml”) both use the same model in my example as the view that they are displayed on (MyModelView). I don’t believe this would be a necessary restriction though.

In my view I render the two partial views using mark up similar to:

<div id="_TotalValues" data-url='@Url.Action("_CalculateValues", "Improvement")'>
 @{
   Html.RenderPartial("_TotalValues", Model);
 }
</div>

and

<div id="_SummaryValues">
 @{
   Html.RenderPartial("_SummaryValues", Model);
 }
</div>

Finally, I have a JScript method to make the AJAX call on some event (such as the user changing a value or clicking a button – whatever event the method is bound to) and update the partial views with the response:

// Make an ajax call to recalculate the total values and summary values
function calculateTotalAndSummaryValues() {
  // Get the controlller action url 
  var url = $("#_TotalValues").data('url');
  var data = $("form").serialize();

  // Post the current contents of the form, so we show the Year 1 to 5 benefit values (which might be unsaved)
  $.post(url, data, function (response) {
    // This post will return a JSON object with two properties called totalValuesPartialView and summaryValuesPartialView
    // these will contain the rendering for the two partial views _TotalValues and _SummaryValues
    // by packaging these two up, we can update two partial views from this ajax post
    // with one single server method call
    $("#_TotalValues").html(response.totalValuesPartialView);
    $("#_SummaryValues").html(response.summaryValuesPartialView);
    });
}

This is a very simplistic implementation, with no error handling, just to show the technique.

The key features are:

  • Use a helper method to render a razor partial view into a string
  • Package the partial views into a JSON object
  • Use JScript / JQuery to unpack the AJAX call response and update HTML elements (DIVs) containing the partial views
Advertisement

Using JQuery Ajax method to pass Json to a MVC3 Action and return a partial view

ASP.NET MVC3 provides out of the box support for binding a Json data object into a Model on postback. This maybe used with a JQuery Ajax function call to post selected data back to an action. It is also possible to return a partial view from the same action, and refresh this from the ajax success callback function. For example:

In my MVC3 Razor project, I create two model classes:

public class NameResponseModel
{
    public string Name { get; set; }
    public DateTime CurrentDateTime { get; set; }
    public IList<int> Numbers { get; set; }
}
public class UpdateNameModel
{
    public string Name { get; set; }
    public IEnumerable<int> Numbers { get; set; }
}

The UpdateNameModel will be used to retrieve data submitted by the Ajax method call. It is this model that the Json object will bind to. The NameResponseModel class is used to pass information back to the UI via a template view.

In the Views/Shared/DisplayTemplates folder, I created a template that is strongly typed to the NameResponseModel class, called NameResponseModel.cshtml:

@model MvcJQuery.Models.NameResponseModel

<div>
<div class="display-label">
    Name: @Html.DisplayFor(m => m.Name)
</div>
<div class="display-label">
    Current Date Time: @Html.DisplayFor(m => m.CurrentDateTime)
</div>
@{
    var numbersCount = Model.Numbers.Count;

    for(int numberIndex = 0; numberIndex < numbersCount; numberIndex++)
    {
        <div class="display-label">
            Number: @numberIndex = @Html.DisplayFor(m => m.Numbers[numberIndex])
        </div>
    }
}
</div>

This template simply displays the contents of the associated NameResponseModel object.

Next, in the Home Controller, I added the following action:

public ActionResult UpdateName(UpdateNameModel updateNameModel)
{
    return PartialView("DisplayTemplates/NameResponseModel", new NameResponseModel
    {
        Name = updateNameModel.Name,
        CurrentDateTime = DateTime.Now,
        Numbers = updateNameModel.Numbers.ToList()
    });
}

This action takes a UpdateNameModel object as a parameter and simply copies this into a new instance of the NameResponseModel. It then returns the display template as a partial view.

Finally, my Home/Index.cshtml view looks like this:

@{
    ViewBag.Title = "Home Page";
}

<script type="text/javascript">
    $(function () {
        $('#UpdateName').click(function () {
            var inputName = $('#Name').val();
            var dataResponse = { Name: inputName, Numbers: [1, 45, 67, 89] };
            $.ajax({
                type: 'POST',
                contentType: 'application/json; charset=utf-8', 
                data: JSON.stringify(dataResponse),
                dataType: 'html',
                url: '@Url.Action("UpdateName")',
                success: function(result) {
                    $('#Response').html(result);
                }
            });
        });
    });
</script>

<h2>@ViewBag.Message</h2>
<p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
</p>

<div id="Response"></div>

<input type="text" id="Name" />
<button type="button" id="UpdateName">Update</button>

I’ve added a div with the Id=”Response”. This will be used to display the partial view following an Ajax update. When the “Update” button is clicked, the click event creates a Json object with the contents of the “Name” text input and an array of integers. Note that the names of the Json items must match exactly the names within the data model that the Json will be bound to. MVC3 will quite happily handle complex data binding, so the array of integers will be bound successfully to the Numbers IEnumerable<int> parameter in the UpdateNameModel class.

Other points to note are the contentType parameter of the Ajax call is set to ‘application/json; charset=utf-8’. This indicates that the parameter passed by the Ajax call will be a Json object. The Json object itself needs to be converted to a string, JSON.stringify will perform this function for us. The dataType parameter in the Ajax call is set to ‘html’. This indicates that the data returned by the server is expected to be html (the rendered partial view in this case). The success callback then simply loads this returned html into the “Response” div.

Hope this helps somebody! It took me a while, and a lot of googling, to figure it out!