Ajax(或者,如果你愿意,也可以称为AJAX)是Asynchronous JavaScripts and XML(异步JavaScripts与XML)。其XML部分并不如它往常那样意义重大,但是异步部分却使AJax十分有用。这是后台请求服务器数据,而不必重载Web页面的一种模型。MVC框架包含了对渐进式Ajax内建的支持,这意味着你将使用辅助器方法来定义AJax特性,而不必在整个视图中添加代码。
准备示例项目
新建一个空的MVC项目,然后新建一个People控制器,代码如下图所示:
public class PeopleController : Controller{private Person[] personData = {new Person { FristName = "Admin",LastName = "FreeMan",Role = Role.Admin},new Person { FristName = "Jacqui",LastName = "Griffyth",Role = Role.User},new Person { FristName = "John",LastName = "Smith",Role = Role.User},new Person { FristName = "Anne",LastName = "Jones",Role = Role.Guset}};// GET: Peoplepublic ActionResult Index(){return View();}public ActionResult GetPeople(){return View(personData);}[HttpPost]public ActionResult GetPeople(string selectedRole){if (selectedRole == null || selectedRole == "ALL"){return View(personData);}else{Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);return View(personData.Where(p => p.Role == selected));}}}
在Views/Shared/_Layout.cshtml中添加CSS样式,如下图所示:
@{Layout = null;}<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width" /><title>_Layout</title><style typeof="text/css">label {display :inline-block;width:100px;}div.dataELem {margin: 5px;}h2 > label {width:inherit;}.editor-label, .editor_field {float:left;margin-top:10px;}.editor_field input {height:20px;}.editor-label {clear:left;}.editor_field {margin-left:10px;}.input[type=submit] {float:left;clear:both;margin-top:10px;}.column {float:left;margin:10px;}table, td, th {border:thin solid black;border-collapse:collapse;padding:5px;background-color:lemonchiffon; text-align:left;margin:10px 0;}div.load {color:red;margin:10px;font-weight:bold;}div.ajaxLink {margin-top :10px;margin-right:5px;float:left;}</style></head><body><div> </div>@RenderBody()</body></html>
创建同步表单,/Views/People/GetPeople.cshtml,代码如下图所示:
@using WebApplication1.Models;@model IEnumerable<Person>@{ViewBag.Title = "GetPeople";Layout = "/Views/Shared/_Layout.cshtml";}<h2>Get People</h2><table><thead><tr>First</tr><tr>Last</tr><tr>Role</tr></thead><tbody>@foreach (Person p in Model){<tr><td>@p.FristName</td><td>@p.LastName</td><td>@p.Role</td></tr>}</tbody></table>@using (Html.BeginForm()){<div>@Html.DropDownList("selectedRole",new SelectList(new[] { "ALL"}.Concat(Enum.GetNames(typeof(Role)))))<button type="submit">submit</button></div>}
运行程序,并导航到/People/Getpeople/,如下图所示:
选择下拉框中的角色类型,并点击submit按钮后,可以筛选出人员菜单,如下图所示:
创建基本的链接和URL
视图的最基本任务之一是创建链接或URL,使用户能够随之进入应用程序的其他部分。以下是常见的可用的HTML辅助器:
为Index控制器,添加了Index.html视图文件,代码如下图所示:
@{ViewBag.Title = "Index";Layout = "/View/Shared/_Layout.cshtml";}<h2>Basci Links</h2><table><thead><tr><th>Helper</th><tr>Output</tr></thead><tbody><tr><td>Url.Content("~/Content/Site.css")</td><td>@Url.Content("~/Content/Site.css")</td></tr> <tr><td>Html.ActionLink("My Link","Index","Home")</td><td>@Html.ActionLink("My Link","Index","Home")</td></tr> <tr><td>Url.Action("GetPeople","People")</td><td>@Url.Action("GetPeople","People")</td></tr> <tr><td>Url.RouteUrL(new {controller = "People",action = "GetPeople"})</td><td>@Url.RouteUrl(new { controller = "People",action = "GetPeople"})</td></tr><tr><td>Html.RouteLink("My Link",new { controller = "People",action = "GetPeople"})</td><td>@Html.RouteLink("My Link",new { controller = "People",Action = "GetPeople"})</td></tr><tr><td>Html.RouteLink("My Link","FormRoute",new { controller = "People",action = "GetPeople"})</td><td>@Html.RouteLink("My Link","FormRoute", new { controller = "People",action = "GetPeople"})</td></tr></tbody></table>
并在RouteConfig的RegisterRoutes方法中,新增一条名叫FormRoute的路由,代码如下图所示:
routes.MapRoute("FormRoute","app/forms/{controller}/{action}/{id}",defaults:new { controller = "Home",action = "Index",id = UrlParameter.Optional})
运行程序,并导航到/home/index,如下图所示:
使用AJax的条件
1、在Web.config文件中,UnobtrusiveJaveScriptsEnable的属性条目,必须为true,如下图所示:
2、需要引入JS文件,一个是JQuery库文件,另一个是Ajax文件,如下图所示,在NugGet程序包中搜索下载即可:
在_Layout.cshtml中引入这两个JS文件:
创建渐进式Ajax表单
我们的目标是,在点击submit按钮时,只有HTMLtable元素中的数据被替换。首先重构People控制器中的方法,如下图所示:
public class PeopleController : Controller{private Person[] personData = {new Person { FristName = "Admin",LastName = "FreeMan",Role = Role.Admin},new Person { FristName = "Jacqui",LastName = "Griffyth",Role = Role.User},new Person { FristName = "John",LastName = "Smith",Role = Role.User},new Person { FristName = "Anne",LastName = "Jones",Role = Role.Guset}};// GET: Peoplepublic ActionResult Index(){return View();}//public ActionResult GetPeople()//{// return View(personData);//}//[HttpPost]//public ActionResult GetPeople(string selectedRole)//{// if (selectedRole == null || selectedRole == "ALL")// {// return View(personData);// }// else// {// Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);// return View(personData.Where(p => p.Role == selected));// }//}public ActionResult GetPeople(string selectedRole = "All"){return View((object)selectedRole);}public PartialViewResult GetPeopleData(string selectedRole = "All"){IEnumerable<Person> data = personData;if (selectedRole != "All"){Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);data = personData.Where(p => p.Role == selected);}return PartialView(data);}}
添加一个对GetPeopleData动作的分部视图,GetPeopleData.cshtml方法:
@using WebApplication1.Models@model IEnumerable<Person>@{Layout = null;}@foreach (Person p in Model){<tr><td>@p.FristName</td><td>@p.LastName</td><td>@p.Role</td></tr>}
更新GetPeople.cshtml代码,如下图所示:
@using WebApplication1.Models@model string@{ViewBag.Title = "GetPeople";Layout = "/Views/Shared/_Layout.cshtml";AjaxOptions ajaxOpts = new AjaxOptions { UpdateTargetId = "tableBody" };}<h2>Get People</h2><table><thead><tr>First</tr><tr>Last</tr><tr>Role</tr></thead><tbody id="tableBody">@Html.Action("GetPeopleData",new { selectedRole = Model})</tbody></table>@using (Ajax.BeginForm("GetPeopleData", ajaxOpts)){<div>@Html.DropDownList("selectedRole",new SelectList(new[] { "ALL"}.Concat(Enum.GetNames(typeof(Role)))))<button type="submit">submit</button></div>}
MVC框架支持AJax表达的核心在于Ajax.BeginForm辅助器方法,它可以接受一个AjaxOptions对象作为其参数。可以在视图开始处,以Razor代码块的形式创建AjaxOptions对象,可以在调用Ajax.BeginForm时,内联的创建他们。以下是AjaxOptions属性
在上述示例中,将UpdateTargetId属性设置为tabledata ,当用户单击submit提交按钮时,会形成一个发送给GetPeopleData的动作方法的异步请求,所返回的HTML片段将用于替换tbody元素中的现有内容。运行结果如下图所示:
确保优雅降级
使用Ajax的一个问题时,如果用户禁用JavaScripts(或者使用不支持JavaScript的浏览器时),当用户提交表达时,浏览器会放弃当前的HTML页面,并用目标动作方法返回的片段代替它(于是失去了页面的主题部分,特别是布局的效果,只剩下了所返回的数据)。如下图所示:
解决这一个问题的最简单的办法是使用AjaxOptions.Url属性,以便制定异步请求的目标URL作为Ajax.BeginForm方法的参数,而不是以动作名称作为参数,如下图所示:
在Ajax请求期间给用户提供反馈
使用AJax的一个缺点是用户观察不到正在发生的事情,因为,发送给服务器的请求是在后台形成的。通过使用AJaxOptions.LoadingElementId和AjaxOptions.LoadingElementDuration属性,可以通知用户,此刻在执行一个请求。增阿基代码如下图所示:
LoadingElementDuration属性动画的持续时间,这是向用户显露loading元素的时间。这里的值是1000,即1秒,运行效果如下图所示:
请求之前对用户进行提示
AjaxOptions.Confirm属性可以用来指定一条消息,用来在每个异步请求之前对用户进行提示,代码如下图所示:
提示框如下图所示:
创建Ajax链接
除了表单之外,渐进式Ajax也可以用于创建异步执行的a元素。这一机制十分类似于Ajax表单的工作方式。如下图所示,在GetPeople.cshtml视图中添加AJax链接。
@using WebApplication1.Models@model string@{ViewBag.Title = "GetPeople";Layout = "/Views/Shared/_Layout.cshtml";AjaxOptions ajaxOpts = new AjaxOptions{ UpdateTargetId = "tableBody",Url = Url.Action("GetPeopleData"),LoadingElementId = "loading",LoadingElementDuration = 1000,Confirm = "Do you wish to request new data"};}<h2>Get People</h2><div id="loading" class="load" style="display:none"><p>Loading Data......</p></div><table><thead><tr>First</tr><tr>Last</tr><tr>Role</tr></thead><tbody id="tableBody">@Html.Action("GetPeopleData",new { selectedRole = Model})</tbody></table>@using (Ajax.BeginForm(ajaxOpts)){<div>@Html.DropDownList("selectedRole",new SelectList(new[] { "ALL"}.Concat(Enum.GetNames(typeof(Role)))))<button type="submit">submit</button></div>}<div>@foreach (string role in Enum.GetNames(typeof(Role))){<div class="ajaxLink"></div>@Ajax.ActionLink(role,"GetPeopleData",new { selectedRole = role},new AjaxOptions { UpdateTargetId = "tableBody"})}</div>
此例使用foreach循环,为Role枚举中定义的每一个值调用了Ajax.ActionLink辅助器,这创建了一组启用AJax的a元素,这里的a元素具有相同的属性标签,如下图所示:
运行效果如下图所示:
点击链接,进行跳转,结果如下图所示:
确保为链接优雅降级
启用AJax的链接与启用Ajax的表单具有相同的问题。当浏览器没有启用JavaScript的支持时,单击这些链接只会生成GetPeopleData动作方法的生成的HTML片段。
为了解决这一问题,可以使用AjaxOptions.Url属性来指定AJax请求的URL,如下图所示:
<div>@foreach (string role in Enum.GetNames(typeof(Role))){<div class="ajaxLink"></div>@Ajax.ActionLink(role, "GetPeople",new { selectedRole = role }, new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData", new { selectedRole = role })});}</div>
使用AJax回调
AjaxOption类定义了一组属性,能够在Ajax请求生命周期中的各个点上调用JavaScripts函数,如下图所示:
修改GetPeople.cshtml的代码如下图所示:
@using WebApplication1.Models@model string@{ViewBag.Title = "GetPeople";Layout = "/Views/Shared/_Layout.cshtml";AjaxOptions ajaxOpts = new AjaxOptions{ UpdateTargetId = "tableBody",Url = Url.Action("GetPeopleData"),LoadingElementId = "loading",LoadingElementDuration = 1000,Confirm = "Do you wish to request new data"};}<h2>Get People</h2><div id="loading" class="load" style="display:none"><p>Loading Data......</p></div><table><thead><tr>First</tr><tr>Last</tr><tr>Role</tr></thead><tbody id="tableBody">@Html.Action("GetPeopleData",new { selectedRole = Model})</tbody></table>@using (Ajax.BeginForm(ajaxOpts)){<div>@Html.DropDownList("selectedRole",new SelectList(new[] { "ALL"}.Concat(Enum.GetNames(typeof(Role)))))<button type="submit">submit</button></div>}<div>@foreach (string role in Enum.GetNames(typeof(Role))){<div class="ajaxLink"></div>@Ajax.ActionLink(role, "GetPeople",new { selectedRole = role }, new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleData", new { selectedRole = role }),OnBegin = "OnBegin",OnFailure = "OnFailure",OnComplete = "OnComplete",OnSuccess = "OnSuccess" });}</div><script type="text/javascript">function OnBegin() {alert("This is the On Begin Callback:")}function OnSuccess(data){alert("This is the OnSuccessCallback:" + data);}function OnFailure(request,error){alert("This is the On Failure Callback:" + error);}function OnComplete(request, status){alert("This is the OnComplete Callback:" + status);}</script>
运行后,效果如下:
使用JSON
到目前为止的所有示例中,服务器都是渲染HTML片段,并把它们发送给浏览器,这是一种完全可接受的技术。但是有点亢长(因为服务器随数据一起发送了HTML元素),而且它限制了浏览器端用这些数据可做的事情。
解决这个问题的一种方式是使用JSON(JavaScrpt Object Notaiton——JavaScripts对象表示法)格式,这是一种与语言无关的数据表示方法。它源自于JavaScript语言,但一直采取自己的生存方式,并被广泛接受。
修改PeopleController中的代码,如下图所示:
public class PeopleController : Controller{private Person[] personData = {new Person { FristName = "Admin",LastName = "FreeMan",Role = Role.Admin},new Person { FristName = "Jacqui",LastName = "Griffyth",Role = Role.User},new Person { FristName = "John",LastName = "Smith",Role = Role.User},new Person { FristName = "Anne",LastName = "Jones",Role = Role.Guset}};// GET: Peoplepublic ActionResult Index(){return View();}private IEnumerable<Person> GetData(string selectedRole){IEnumerable<Person> data = personData;if (selectedRole != "All"){Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);data = personData.Where(p => p.Role == selected);}return data;}public JsonResult GetPeopleDataJason(string selectedRole = "All"){IEnumerable<Person> data = GetData(selectedRole);return Json(data, JsonRequestBehavior.AllowGet);}public PartialViewResult GetPeopleData(string selectedRole = "All"){return PartialView(GetData(selectedRole));}public ActionResult GetPeople(string selectedRole = "All"){return View((object)selectedRole);}}
上图中,新添加了一个动作方法,其名称为GetPeopleDataJson,它返回一个JsonResult对象,修改GetPeople.cshtml中的代码,如下图所示:
@using WebApplication1.Models@model string@{ViewBag.Title = "GetPeople";Layout = "/Views/Shared/_Layout.cshtml";AjaxOptions ajaxOpts = new AjaxOptions{ UpdateTargetId = "tableBody",Url = Url.Action("GetPeopleData"),LoadingElementId = "loading",LoadingElementDuration = 1000,Confirm = "Do you wish to request new data"};}<h2>Get People</h2><div id="loading" class="load" style="display:none"><p>Loading Data......</p></div><table><thead><tr>First</tr><tr>Last</tr><tr>Role</tr></thead><tbody id="tableBody">@Html.Action("GetPeopleData",new { selectedRole = Model})</tbody></table>@using (Ajax.BeginForm(ajaxOpts)){<div>@Html.DropDownList("selectedRole",new SelectList(new[] { "ALL"}.Concat(Enum.GetNames(typeof(Role)))))<button type="submit">submit</button></div>}<div>@foreach (string role in Enum.GetNames(typeof(Role))){<div class="ajaxLink"></div>@Ajax.ActionLink(role, "GetPeopleData",new { selectedRole = role }, new AjaxOptions { UpdateTargetId = "tableBody", Url = Url.Action("GetPeopleDataJason", new { selectedRole = role }),OnSuccess = "processData" });}</div><script type="text/javascript">function OnBegin() {alert("This is the On Begin Callback:")}function OnSuccess(data){alert("This is the OnSuccessCallback:" + data);}function OnFailure(request,error){alert("This is the On Failure Callback:" + error);}function OnComplete(request, status){alert("This is the OnComplete Callback:" + status);}function processData(data){var target = $("#tableBody");target.empty();for (var i = 0; i < data.length; i++){var person = data[i];target.append("<tr><td>" + person.FirstName + "</td><td>" + person.LastName + "</td><td>" + person.Role + "</td><td>")}}</script>
注意:如果返回的数据是非私有的(private),才应该使用JsonRequestBehavior.AllowGet。由于许多web浏览器中的安全问题,第三方网站有可能截取响应GET请求返回的JSON数据,这是JsonResult 默认不响应Get请求的原因。
运行程序,返回的JSON字符串如下图所示:
[{"PersonId":0,"FristName":"Jacqui","LastName":"Griffyth","BirthDate":"\/Date(-62135596800000)\/","HomeAddress":null,"Role":1},{"PersonId":0,"FristName":"John","LastName":"Smith","BirthDate":"\/Date(-62135596800000)\/","HomeAddress":null,"Role":1}]
这看来有点乱,但是这种处理实际上是相当聪明的——虽然我们不需要包含所有字段。首先,Person类所定义的全部属性都标识成了JSON,但是在Person控制器中对一些属性未赋值。在某些情况下,使用的是某个类型的默认值(例如,IsApproved用的是false值),但是对其他的属性使用了null(例如HomeAddress),有些值已经被转换成易于有JavaScript解释的形式,例如birthDate,而其他的一些则未处理——例如,Role属性使用了0,而不是Admin.
可以使用如下方式来检测结果:
可以用如下匿名类的方式来去掉不需要关注的字段:,修改控制器中的代码:
public JsonResult GetPeopleDataJason(string selectedRole = "All"){var data = GetData(selectedRole).Select(p => new {First = p.FristName,LastName = p.LastName,Role = Enum.GetName(typeof(Role),p.Role)});return Json(data, JsonRequestBehavior.AllowGet);}
在动作方法中检测Ajax请求
修改People控制器中GetPeopleData动作方法的代码,如下图所示:
public ActionResult GetPeopleData(string selectedRole = "All"){IEnumerable<Person> data = personData;if (selectedRole != "All"){Role selected = (Role)Enum.Parse(typeof(Role), selectedRole);data = personData.Where(p => p.Role == selected);}if (Request.IsAjaxRequest()){var formattedData = data.Select(p => new{FirstName = p.FristName,LastName = p.LastName,Role = Enum.GetName(typeof(Role), p.Role)});return Json(formattedData, JsonRequestBehavior.AllowGet);}else{return PartialView(data);}}
以上代码中使用Request.IsAjaxRequest方法对 Ajax请求进行检测,并且在其结果为true的情况下,交付JSON数据。
修改GetPerson.cshtml中的代码如下图所示:
以上代码中, 将所有动作请求都采用了GetPeopleData动作方法,此方法会对是否采用AJax提交进行检测,从而返回指定的返回值:如果是AJax请求则返回Jason数据,否则返回部分视图。