Unobtrusive Ajax in Asp.net MVC

讲述怎么使用Unobtrusive Ajax之前,先看在Asp.net MVC传统情况下怎么来实现一个下拉框联动效果:

1. 简单的定义一个model:

public class Appointment

{

public string ClientName { get; set; }

[DataType(DataType.Date)]

public DateTime Date { get; set; }

public bool TermsAccepted { get; set; }

}

2. controller和action的实现:

index.cshtml:

@model string

@{

ViewBag.Title = "Index";

}

<h4>Appointment List</h4>

@using (Html.BeginForm())

{

<table>

<thead>

<th>

Client Name

</th>

<th>

Appointment Date

</th>

</thead>

<tbody >

@Html.Action("AppointmentData", new { id = Model })

</tbody>

</table>

<p>

@Html.DropDownList("id", new SelectList(new[] { "All", "Joe", "Jane", "Bob" }, (Model ?? "All")))

<input type="submit" value="Submit" />

</p>

}

AppointmentData.cshtml:

@using UnobtrusiveAjax.Models

@model IEnumerable<UnobtrusiveAjax.Models.Appointment>

@{

Layout = null;

}

@foreach (Appointment appt in Model)

{

<tr>

<td>@Html.DisplayFor(m => appt.ClientName)

</td>

<td>@Html.DisplayFor(m => appt.Date)

</td>

</tr>

}

最终的效果就是:当切换下拉框的选择项时,Appointment list也对应变化,但是页面是通过postback来刷新数据的。现在想实现Ajax切换效果:

4. 启用Unobtrusive Ajax脚本了。

web.config中新增:

<add key="UnobtrusiveJavaScriptEnabled" value="true"/>。

然后在页面中引入:

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>

5. 页面中更新form为ajax form:

@model string

@{

ViewBag.Title = "Index";

AjaxOptions ajaxOptions = new AjaxOptions

{

UpdateTargetId = "tabledata"

};

}

<h4>Appointment List</h4>

@using(Ajax.BeginForm("AppointmentData", ajaxOptions))

{

<table>

<thead>

<th>

Client Name

</th>

<th>

Appointment Date

</th>

</thead>

<tbody >

@Html.Action("AppointmentData", new { id = Model })

</tbody>

</table>

<p>

@Html.DropDownList("id", new SelectList(new[] { "All", "Joe", "Jane", "Bob" }, (Model ?? "All")))

<input type="submit" value="Submit" />

</p>

}

这时候,基本的Ajax效果已经出来了。如果要继续完善美化,可以从AjaxOptions着手。先看AjaxOptions的属性有:Confirm, HttpMethod, InsertionMode, LoadingElementId, LoadingElementDuration, UpdateTargetId, Url,OnBegin, OnComplete, OnFailure, OnSuccess等。详细的可以参考:http://msdn.microsoft.com/en-us/library/system.web.mvc.ajax.ajaxoptions(v=vs.108).aspx。利用这些属性,我们来一个完美的吧:

@model string

@{

ViewBag.Title = "Index";

AjaxOptions ajaxOptions = new AjaxOptions

{

UpdateTargetId = "tabledata",

Url = Url.Action("AppointmentData"),

LoadingElementId = "loading",

LoadingElementDuration = 2000,

Confirm = "Do you wish to request new data?",

OnBegin = "OnBegin",

OnComplete = "OnComplete",

OnFailure = "OnFailure",

OnSuccess = "OnSuccess"

};

}

<h4>Appointment List</h4>

<script type="text/javascript">

function OnBegin() {

alert("This is the OnBegin Callback");

}

function OnSuccess(data) {

alert("This is the OnSuccessCallback: " + data);

}

function OnFailure(request, error) {

alert("This is the OnFailure Callback:" + error);

}

function OnComplete(request, status) {

alert("This is the OnComplete Callback: " + status);

}

</script>

<div >

<p>

Loading Data...</p>

</div>

@using (Ajax.BeginForm(ajaxOptions))

{

<table>

<thead>

<th>

Client Name

</th>

<th>

Appointment Date

</th>

</thead>

<tbody >

@Html.Action("AppointmentData", new { id = Model })

</tbody>

</table>

<p>

@Html.DropDownList("id", new SelectList(new[] { "All", "Joe", "Jane", "Bob" }, (Model ?? "All")))

<input type="submit" value="Submit" />

</p>

}

其中标黄部分是为了兼容不支持js的浏览器(原有实现在不支持js的浏览器中直接返回没有html的原始数据)。

6. 增加Ajax Links(支持Ajax的超链接)。在index顶部增加:

@foreach (string str in new[] { "All", "Joe", "Jane", "Bob" })

{

<div >

@Ajax.ActionLink(str, "AppointmentData", new { id = str },

new AjaxOptions

{

UpdateTargetId = "tabledata",

LoadingElementId = "loading",

})

</div>

}

这时,点击它所生成的链接,和切换下拉框选中内容,效果完全一样。

7. 为了让上一步中ajax兼容不支持js的浏览器,并将ajax response回来的html数据改为json原始数据,做一些改进。

controller变为:

public class AppointmentController : Controller

{

public ActionResult Index(string id)

{

return View("Index", (object)id);

}

public ActionResult AppointmentData(string id)

{

IEnumerable<Appointment> data = new[]

{

new Appointment

{ClientName = "Joe", Date = DateTime.Parse("1/1/2012")},

new Appointment

{ClientName = "Joe", Date = DateTime.Parse("2/1/2012")},

new Appointment

{ClientName = "Joe", Date = DateTime.Parse("3/1/2012")},

new Appointment

{ClientName = "Jane", Date = DateTime.Parse("1/20/2012")},

new Appointment

{ClientName = "Jane", Date = DateTime.Parse("1/22/2012")},

new Appointment

{ClientName = "Bob", Date = DateTime.Parse("2/25/2012")},

new Appointment

{ClientName = "Bob", Date = DateTime.Parse("2/25/2013")}

};

if (!string.IsNullOrEmpty(id) && id != "All")

{

data = data.Where(e => e.ClientName == id);

}

if (Request.IsAjaxRequest())

{

return Json(data.Select(m => new

{

ClientName = m.ClientName,

Date = m.Date.ToShortDateString()

}),

JsonRequestBehavior.AllowGet);

}

else

{

return View(data);

}

}

}

标黄部分的变更前者是为了兼容get和post2种请求,后者是为了兼容普通请求和ajax请求并分别生成不同类容。其中,Request.IsAjaxRequest()方法可以判断当前请求是否来自Ajax。

view变更为:

@model string

@{

ViewBag.Title = "Index";

AjaxOptions ajaxOptions = new AjaxOptions

{

UpdateTargetId = "tabledata",

//Url = Url.Action("AppointmentData"),

LoadingElementId = "loading",

LoadingElementDuration = 2000,

Confirm = "Do you wish to request new data?",

//OnBegin = "OnBegin",

//OnComplete = "OnComplete",

//OnFailure = "OnFailure",

//OnSuccess = "OnSuccess"

};

}

<h4>Appointment List</h4>

<script type="text/javascript">

function OnSuccess2(data) {

var target = $('#tabledata');

target.empty();

for (var i = 0; i < data.length; i++) {

target.append('<tr><td>' + data[i].ClientName + '</td><td>' + data[i].Date + '</td></tr>');

}

}

</script>

<div >

<p>

Loading Data...</p>

</div>

@using (Ajax.BeginForm(ajaxOptions))

{

<table>

<thead>

<th>

Client Name

</th>

<th>

Appointment Date

</th>

</thead>

<tbody >

@Html.Action("AppointmentData", new { id = Model })

</tbody>

</table>

<p>

@Html.DropDownList("id", new SelectList(new[] { "All", "Joe", "Jane", "Bob" }, (Model ?? "All")))

<input type="submit" value="Submit" />

</p>

}

@foreach (string str in new[] { "All", "Joe", "Jane", "Bob" })

{

<div >

@Ajax.ActionLink(str, "Index", new { id = str }, new AjaxOptions

{

Url = Url.Action("AppointmentData", new { id = str }),

LoadingElementId = "loading",

LoadingElementDuration = 2000,

OnSuccess = "OnSuccess2"

})

</div>

}

8. 原理:

Browser端查看html源码发现form部分为:

<form action="/Appointment" data-ajax="true" data-ajax-confirm="Do you wish to request new data?" data-ajax-loading="#loading" data-ajax-loading-duration="2000" data-ajax-mode="replace" data-ajax-update="#tabledata" method="post">

ajax links部分为:

<a data-ajax="true" data-ajax-loading="#loading" data-ajax-loading-duration="2000" data-ajax-success="OnSuccess2" data-ajax-url="/Appointment/AppointmentData/Bob" href="/Appointment/Index/Bob">Bob</a>

所以它实际上和validation部分类似,不过是在html中隐藏了很多自定义属性,然后在unobtrusive ajax 的js中去获取,然后根据这些值来分别处理。

9. 如果禁用了unobtrusive ajax,则mvc framework会自动启用MicrosoftAjax.js和MicrosoftMVCAjax,但是你需要手动引入它们。它们效果相同,但实现原理不一样。

源码download