Yesterday, I wrote a post detailing how easy we can invoke an ASP.NET MVC controller action from JavaScript using jQuery. Lets up the ante a bit, and see if we can't use the same approach to get some partial rendering going. To accomplish this, we'll use a fact about the RenderViewResult class that hasn't got a lot of attention so far - the fact that its View propetry can be set to point at a UserControl, not just a WebForm. When doing so, only the user control will be rendered - which sounds like exactly what we need for doing partial rendering..
Rendering User Controls
Lets say we're building a simple task list. We might have the following UserControl representing a single task:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Task.ascx.cs" Inherits="MvcApplication2.Views.Tasks.Task" %>
<div class="task">
<div class="task_name"><%= ViewData.Name %></div>
<div class="task_due"><%= ViewData.Due %></div>
<div class="task_desc"><%= ViewData.Description %></div>
</div>
Our TaskList view may then use this to render a list of tasks:
<h1>Tasks</h1>
<div id="task_list">
<% foreach(Task task in ViewData) {%>
<%= Html.RenderUserControl("~/Views/Tasks/Task.ascx", task) %>
<%} %>
</div>
<h1>Add task</h1>
Name: <input type="text" id="newtask_name" /><br />
Due: <input type="text" id="newtask_due" /><br />
Description: <input type="text" id="newtask_desc" /><br />
<input type="button" onclick="AddTask()" value="Add" />
The TaskList view is rendered by the Index action on our TaskController, which looks like this:
public ActionResult Index()
{
Task[] tasks = // get tasks from repository
return RenderView("TaskList", tasks);
}
Next, we need an action for creating a new task. However, we want to invoke it from JavaScript, and have it only return the markup for the new task item so that we can append it to the task list dynamically, instead of refreshing the entire page. To accomplish this, we implement it using the previously mentioned trick of the RenderViewResult class:
public ActionResult New(string name, string desc, DateTime due)
{
Task task = new Task { Name = name, Description = desc, Due = due };
// ...save task to repository
return RenderView("Task", task);
}
Notice here that "Task" points to the Task.ascx user control, not a Web Form. When invoked, this action will render the markup for that control only.
Manipulating the DOM
Like I mentioned in yesterdays post, we can tell jQuery to handle the response from an Ajax call as html. More than that, we can use jQuery to create a DOM element for us from the returned html, and drop it anywhere in our document. The following JavaScript function accomplishes both:
function AddTask()
{
var name = $("#newtask_name").attr("value");
var due = $("#newtask_due").attr("value");
var desc = $("#newtask_desc").attr("value");
$.ajax(
{
type: "POST",
url: "/Tasks/New",
data: "name=" + name + "&due=" + due + "&desc= "+ desc,
dataType: "html",
success: function(result)
{
var domElement = $(result); // create element from html
$("#task_list").append(domElement); // append to end of list
}
});
}
Here, we're making an Ajax call to the New action on the Tasks controller. We then create a new DOM element from the resulting html that it renders, and append it to the task list. And that's it! When the user adds a task, the following POST is sent:
The following response is received from the server, which we append to the task list:
If you want to take a look at a working example, you can download the project I used to demo this here.
Note: For some reason, the Ajax calls are quite slow (~1 sec) when running the demo using Visual Studio's built-in development server. Publish the site to IIS however, and things are blazingly fast (as you can see from the screenshots above) :)