Extending MVC: Auto Refreshing A Partial View Using AJAX

This is something that I put together in a few minutes to provide a way to refresh a partial view on a specified time interval.

Let's create a MVC User Control that displays the current date and time called "TimeControl.ascx":

    1: <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TimeControl.ascx.cs" Inherits="SAWeb.UI.Views.UserControls.TimeControl" %>
    2: <div>
    3: <%= DateTime.Now.ToString() %>
    4: </div>

We can render this in a view using the Html.RenderPartial() method.  How can we update this control using AJAX?  MVC provides an Ajax.ActionLink() helper method that we could use.  An example of this can be found here and here.  The only problem with this is that you have to click a link to update the control.  What if we want it to automatically update the partial view control via AJAX on a certain time interval, like every 5 seconds?  To do this, I have created a helper method that provides this functionality. 

The code for the helper method (Html.RenderPartialRefresh) is shown below:

    1: public static class RenderExtensions
    2: {    
    3:     // Extension method to render a partial view every interval specified.
    4:     public static void RenderPartialRefresh(this HtmlHelper htmlHelper, string name, string controller, string actionMethod, 
    5:         TimeSpan interval, string partialViewName)
    6:     {
    7:         RenderPartialRefresh(htmlHelper, name, controller, actionMethod, interval, partialViewName, htmlHelper.ViewData);
    8:     }
    9:  
   10:     // Extension method to render a partial view every interval specified, and providing a model to the control.
   11:     public static void RenderPartialRefresh(this HtmlHelper htmlHelper, string name, string controller, string actionMethod, 
   12:         TimeSpan interval, string partialViewName, object model)
   13:     {
   14:         if (model == null)                
   15:             throw new ArgumentNullException("model");
   16:         if (String.IsNullOrEmpty(name))
   17:             throw new ArgumentException("Argument cannot be null or empty.", "name");
   18:         if (String.IsNullOrEmpty(partialViewName))
   19:             throw new ArgumentException("Argument cannot be null or empty.", "partialViewName");
   20:  
   21:         // Building the javascript that registers the interval and performs the AJAX request.
   22:         StringBuilder builder = new StringBuilder();
   23:         builder.Append("<script language=\"javascript\">");
   24:         builder.Append("setInterval(function() {");
   25:         builder.Append("Sys.Mvc.MvcHelpers.$1(");
   26:         builder.AppendFormat("'{0}',", GenerateUrl(htmlHelper.ViewContext, null, actionMethod, controller, new RouteValueDictionary()));
   27:         builder.Append("'post','','', { insertionMode: Sys.Mvc.InsertionMode.replace, updateTargetId: '");
   28:         builder.AppendFormat("{0}", name);
   29:         builder.Append("'});},");
   30:         builder.AppendFormat("{0});", interval.TotalMilliseconds);
   31:         builder.Append("</script>");
   32:  
   33:         // Wraps the partial view in a div tag so that we can update by the id of the div.
   34:         htmlHelper.ViewContext.HttpContext.Response.Output.Write(builder.ToString());
   35:         htmlHelper.ViewContext.HttpContext.Response.Output.Write("<div id=\"{0}\" name=\"{0}\">", name);
   36:         htmlHelper.RenderPartial(partialViewName, model);
   37:         htmlHelper.ViewContext.HttpContext.Response.Output.Write("</div>");
   38:     }
   39:  
   40:     // Method to generate the URL given the controller and action method.
   41:     private static string GenerateUrl(ViewContext viewContext, string routeName, string actionName, 
   42:         string controllerName, RouteValueDictionary valuesDictionary)
   43:     {
   44:         UrlHelper helper = new UrlHelper(viewContext);
   45:         Type t = typeof(UrlHelper);
   46:         Object[] paramArray = { routeName, actionName, controllerName, valuesDictionary };
   47:         MethodInfo m = t.GetMethod("GenerateUrl", BindingFlags.NonPublic | BindingFlags.Instance);
   48:         string s = (string)m.Invoke(helper, paramArray);
   49:         return HttpUtility.HtmlAttributeEncode(s);
   50:     }
   51: }

The next step is to make sure that we have the AJAX scripts registered.  In the master page or the view, ensure that you have the following script references (with the correct path to the scripts):

    1: <script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    2: <script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

And now, lets create a controller action method to return the partial view when called.  In this example, we will add the action method to the Home controller, and it simply returns a View of the TimeControl.ascx partial view.  This is called by the AJAX script every interval to update the partial view.

    1: [HandleError]
    2: public class HomeController : Controller
    3: {
    4:     ...
    5:  
    6:     // Simple action method that returns the partial view for the TimeControl.ascx
    7:     public ActionResult GetTimeControl()
    8:     {
    9:         return View("/Views/UserControls/TimeControl.ascx");
   10:     }
   11: }

Finally, to render the auto updating view, we can use the helper method that we created above, passing the name for the wrapping div ("timeDisplay"), the controller and action method to call for the update ("Home" and "GetTimeControl"), the TimeSpan for the interval (5 seconds), and the partial view to render ("TimeControl.ascx"):

    1: <h2>Auto Updating Partial View Example</h2>
    2:  
    3: <% Html.RenderPartialRefresh("timeDisplay", "Home", "GetTimeControl", new TimeSpan(0, 0, 5), "/Views/UserControls/TimeControl.ascx"); %>

Initially, the View will be rendered with the Date and Time of the request as shown below:

image

And every five seconds, the partial view control will update via AJAX (without a postback) to reflect the current time.  The image below shows the same page after 80 seconds without the need for a refresh.  You can see that the time has automatically updated.

image

The trickiest part of this was determining the javascript to perform the AJAX request.  There might be a better (and cleaner) way to do this, but this is something that I put together in about 20 minutes.  The goal of this is to show the power of AJAX in MVC and the ease of creating extension helpers in MVC to provide the functionality that you might need.

Comments