Asp.Net MVC4系列--進階篇之AJAX

mybwu_com發表於2014-04-16

本章將開始介紹MVC中Ajax的使用

以一個非Ajax版本開始

Controller

public class PeopleController : Controller
    {
       private readonly Person[] _personData = {
new Person {FirstName = "Iori",LastName = "Lan", Role = Role.Admin},
new Person {FirstName = "Edwin", LastName= "Sanderson", Role = Role.Admin},
new Person {FirstName = "John",LastName = "Griffyth", Role = Role.User},
new Person {FirstName = "Tik",LastName = "Smith", Role = Role.User},
new Person {FirstName = "Anne",LastName = "Jones", Role = Role.Guest}
};
       public ActionResult Index()
       {
           return View("List");
       }
       public ActionResult GetPeople()
       {
           return View("List",_personData);
       }
       [HttpPost]
       public ActionResult GetPeople(string selectedRole)
       {
           if (selectedRole == null || selectedRole == "All")
           {
                returnView("List",_personData);
           }
           var selected = (Role)Enum.Parse(typeof(Role), selectedRole);
           return View("List",_personData.Where(p => p.Role ==selected));
       }
}


Model


 public class Person
    {
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public Role Role { get; set; }
    }
   public enum Role
    {
       Admin,
       User,
       Guest
    }


View

@{
   Layout = null;
}
 
@using MVCAjax.Models
@model IEnumerable<Person>
@{
ViewBag.Title = "GetPeople";
}
<h2>Get People</h2>
<table>
<thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead>
<tbody>
@foreach (var p in Model) {
<tr>
<td>@p.FirstName</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>
}


測試:


驗證請求型別

在IE中開啟F12->Network ,我們可以看到請求的發起者是click操作,因為不是xmlHttpRequest,因而不是ajax請求


使用Ajax重構程式碼

配置Unobstrusiveajax

開啟web.config

確保這一行在appconfig節點中:

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


開啟App_Start/BundleConfig.cs,確保已新增(預設已新增):

bundles.Add(newScriptBundle("~/bundles/jquery").Include(
                       "~/Scripts/jquery-{version}.js"));
 
           bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                       "~/Scripts/jquery-ui-{version}.js"));
 
           bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                       "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));


原因:我們需要的是jquery.1.7.1.js 和jquery.unobstrucsive-ajax.min.js,這兩個包已經包含了,在layout中render就可以了。

開啟_layout.cshtml

在<head>中render 這兩個包:

   @Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")


注意:在View確保沒有把@{layout=null},否則layout沒有應用導致沒有renderbundle,以致於無法render需要的script。

Controller 新增Action:

public ActionResult AjaxGetPeople()
        {
            return View("AjaxList");
        }
        public PartialViewResult GetPeoplePartial(string selectedRole = "All")
        {
           IEnumerable<Person> data = _personData;
            if(selectedRole != "All")
            {
                var selected = (Role)Enum.Parse(typeof(Role), selectedRole);
                data =_personData.Where(p => p.Role == selected);
            }
            return PartialView("PeoplePartialList",data);
        }


新增PartialViewPeoplePartialList.cshtml :

@using MVCAjax.Models
@model IEnumerable<Person>
@foreach (Person p in Model) {
<tr>
<td>@p.FirstName</td>
<td>@p.LastName</td>
<td>@p.Role</td>
</tr>
}


新增View: AjaxList.cshtml :

@using MVCAjax.Models
@model string
@{
ViewBag.Title = "GetPeople";
var ajaxOpts = new AjaxOptions {
UpdateTargetId = "tableBody"
};
}
<h2>Get People</h2>
<table>
<thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead>
<tbody id="tableBody">
@Html.Action("GetPeoplePartial", new {selectedRole= Model })
</tbody>
</table>
@using(Ajax.BeginForm("GetPeoplePartial",ajaxOpts)) {
<div>
@Html.DropDownList("selectedRole", new SelectList(
new []{"All"}.Concat(Enum.GetNames(typeof(Role)))))
<button  type="submit">Submit</button>
</div>
}


執行:


F12->Network 檢視請求


可以看到,initiator 為XMLHttpRequest ,驗證了請求確實是Ajax。

分析Obstrusivejavascript 工作原理

<form action="/People/GetPeoplePartial" data-ajax="true" data-ajax-mode="replace" data-ajax-update="#tableBody" id="form0" method="post">


這是MVC Framework生成的form tag,對於unobsrutive javascript 來說,他關心的是data-ajax=”true”和data-ajax開頭的attribute,首先找到所有data-ajax=”true”的form,然後根據data-ajax-mode找到data-ajax-update的id(注意,unobstrusivejs 基於Jquery,需要加#)完成區域性重新整理。

Ajax Options

以下列出一些常用的AjaxOptions :

Confirm

發起請求前彈出確認框,指定確認框的文字

HttpMethod

請求型別:POST,GET,PUT,DELETE等等

InsertionMode

Replace,Before,After, 預設為Replace

LoadingElementId

請求發出後,收到Server迴應前彈出一個loading的div,這裡指定div id

LoadingElementDuration

設定loading Div最多顯示多少秒

UpdateTargetId

要區域性更新的container id

Url

發請求的url

Loading 和Confirmation

現在稍作改動,給例子加一個Confirmation和loading

準備loading的div和css

Css:

<style type="text/css">
    .popup_background {
        z-index: 10;
        background-color: grey;
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        opacity: 0.4;
    }
    .popup_loading {
        z-index: 11;
        font-size: 50px;
        top: 400px;
        left: 600px;
        position: absolute;
    }
</style>


僅作為演示,我hard-code了loading div的位置,實際專案中需要調整

Html:

<div id="loading" class="popup_background" style="display:none">
    <div class="popup_loading">
        <p>Loading...</p>
    </div>
</div>


在AjaxOption中指定Id

var ajaxOpts = new AjaxOptions {
UpdateTargetId = "tableBody" ,
Confirm = "Sure?",
LoadingElementDuration = 2000,
LoadingElementId = "loading"
};


在Controller中模擬一個長時間的任務

Sleep 3秒

private void MockLongTimeProcessing()
        {
            Thread.Sleep(3000);
        }


在partialview中呼叫一下

public PartialViewResult GetPeoplePartial(string selectedRole ="All")
        {
            MockLongTimeProcessing();
...
}


執行,檢視效果:


Ajax連結

Ajax連結的生成很簡單。

稍作改動,看一下ajax連結的使用方法,在View(AjaxList.cshtml)中新增:

<div>
@foreach (string role in Enum.GetNames(typeof(Role))) {
<div class="ajaxLink">
@Ajax.ActionLink(role,"GetPeoplePartial",
new {selectedRole= role},
ajaxOpts)
</div>
}
</div>


這樣就可以了,其實就是呼叫Ajax.ActionLink函式,設定連結的文字,Action,以及引數,最後一個是AjaxOption,我們用的是前面一個demo的。

新增對Json的支援

改動Controller

和前面的步驟一樣,新增兩個Action,一個是服務第一次載入頁面的 請求,我們返回View,一個是Ajax過來的,我們需要返回JsonResult:

    public ActionResult  JsonList()
        {
            return View("JsonList");
        }
        public JsonResult GetPeopleJson(string selectedRole = "All")
        {
            IEnumerable<Person> data = _personData;
            if (selectedRole !="All")
            {
                var selected =(Role)Enum.Parse(typeof(Role), selectedRole);
                data =_personData.Where(p => p.Role == selected);
            }
            return Json(data,JsonRequestBehavior.AllowGet);
        }

新增View:

JsonList.cshtml

@using MVCAjax.Models
@model string
@{
ViewBag.Title = "GetPeople";
var ajaxOpts = new AjaxOptions {
UpdateTargetId = "tableBody",
 
};
}
<script type="text/javascript">
    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></tr>");
        }
    }
</script>
<h2>Get Json List</h2>
 
<table>
<thead><tr><th>First</th><th>Last</th><th>Role</th></tr></thead>
<tbody id="tableBody">
@Html.Action("GetPeoplePartial", new {selectedRole = Model })
</tbody>
</table>
 
<div>
@foreach (string role in Enum.GetNames(typeof(Role))) {
<div class="ajaxLink">
@Ajax.ActionLink(role, "GetPeople",
new {selectedRole = role},
new AjaxOptions {
Url = Url.Action("GetPeopleJson", new {selectedRole = role}),
OnSuccess = "processData"
})
</div>
}
</div>


為了簡便演示,僅僅展示Json部分,筆者拿掉了Loading和Confirm的部分。

程式碼分析

程式碼的核心在於OnSuccess = "processData" 。

當Ajax請求成功時,指向processData函式,在這個函式裡面,我們拿到id(JqueryId)為#tableBody的表單,清空,手動拼html,並用jquery新增到這個html control中。這部分可以優化,可以選擇handlebar.js,knockout.js 等js template。

在table body中呼叫了@Html.Action("GetPeoplePartial",new {selectedRole = Model }),和前面的原因一樣,為了第一次載入時顯示出所有人員列表。

另外,AjaxOptions中還有其他Callback型別;

OnBegin : 發請求前

OnFailure :請求失敗

OnSuccess :請求成功

OnComplete :請求完畢

相關文章