【讀書筆記】WebApi 和 SPA(單頁應用)--knockout的使用


      Web API從MVC4開始出現,可以服務於Asp.Net下的任何web應用,本文將介紹Web api在單頁應用中的使用。什么是單頁應用?Single-Page Application最常用的定義:一個最初內容只包含html和JavaScript,后續操作通過Restful風格的web服務傳輸json數據來響應異步請求的一個web應用。SPA的優勢就是少量帶寬,平滑體驗,劣勢就是只用JavaScript這些平滑的操作較難實現,不像MVC應用,我們可以異步form,partview。不用擔心,我們有利器:knockoutjs。

 一、工程准備

       1.新建一個Api工程。

       

      2.創建模型和倉庫

        Reservation:    

  public class Reservation
    {
        public int ReservationId { get; set; }
        public string ClientName { get; set; }
        public string Location { get; set; }
    }

      ReservationRespository:在真實的項目中,倉庫都需要有接口和依賴注入,但是這里我們把重點放到后,將這個部分簡化。所以也沒有用數據庫,全部放到內存里面。

 public class ReservationRespository
    {
        private static ReservationRespository repo = new ReservationRespository();
        public static ReservationRespository Current
        {
            get
            {
                return repo;
            }
        }
        private List<Reservation> data = new List<Reservation> {
                new Reservation {
                ReservationId = 1, ClientName = "Adam", Location = "Board Room"},
                new Reservation {
                ReservationId = 2, ClientName = "Jacqui", Location = "Lecture Hall"},
                new Reservation {
                ReservationId = 3, ClientName = "Russell", Location = "Meeting Room 1"},
                };
        public IEnumerable<Reservation> GetAll()
        {
            return data;
        }
        public Reservation Get(int id)
        {
            return data.Where(r => r.ReservationId == id).FirstOrDefault();
        }

        public Reservation Add(Reservation item)
        {
            item.ReservationId = data.Count + 1;
            data.Add(item);
            return item;
        }
        public void Remove(int id)
        {
            Reservation item = Get(id);
            if (item != null)
            {
                data.Remove(item);
            }
        }
        public bool Update(Reservation item)
        {
            Reservation storedItem = Get(item.ReservationId);
            if (storedItem != null)
            {
                storedItem.ClientName = item.ClientName;
                storedItem.Location = item.Location;
                return true;
            }
            else
            {
                return false;
            }
        }

    }
View Code

     用來為我們的單頁應用提供數據的增刪改查。

     3.用Nuget添加Juqery,Bootstrap,Knockoutjs。

Install-Package jquery –version 1.10.2
Install-Package bootstrap –version 3.0.0
Install-Package knockoutjs –version 3.0.0

    4.創建Api控制器。

  public class WebController : ApiController
    {
        private ReservationRespository repo = ReservationRespository.Current;
        public IEnumerable<Reservation> GetAllReservations()
        {
            return repo.GetAll();
        }
        public Reservation GetReservation(int id)
        {
            return repo.Get(id);
        }

        [HttpPost]
        public Reservation PostReservation(Reservation item)
        {
            return repo.Add(item);
        }
        [HttpPut]
        public bool PutReservation(Reservation item)
        {
            return repo.Update(item);
        }
        public void DeleteReservation(int id)
        {
            repo.Remove(id);
        }
    }

二、Web Api  

  這個時候運行訪問 /api/web ,chrome下是xml數據,而ie下是json數據,這是因為Web api會根據請求中的http header 的接收類型來返回客戶端“喜歡”的格式。

   

Web api的路由和普通的mvc路由略有不同,它是可以根據請求的操作類型(get,post,delete)以及參數來匹配對應的方法。

 public static void Register(HttpConfiguration config)
        {
            // Web API 配置和服務
            // Web API 路由
            config.MapHttpAttributeRoutes();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

如果你還是想使用api/{controller}/{action}/{id}這樣的方式,加個action就好

 config.Routes.MapHttpRoute(
                  name: "ActionApi",
                  routeTemplate: "api/{controller}/{action}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );

WebApi的一些基本原理和操作大家可以移步 小牛之路 webapi  這里我就不再贅述了。 該文也詳細的介紹了使用ajax和Ajax.BeginForm的方法來進行交互。

三、Knockout

  SPA將更多的任務移到了瀏覽器上,這樣就需要保存應用的狀態,需要可以更新的數據模型,一系列用戶可以通過UI元素觸發的邏輯操作。這樣就意味着需要一個微型的MVC模式。而微軟為此提供的類庫就是Knockout,准確的說,Knockout是MVVM模式,下面就用它完成一個簡單的應用。

   1.在Shared文件夾中新增_Layout.cshtml,並引用相關類庫。

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/bootstrap.css" rel="stylesheet" />
    <link href="~/Content/bootstrap.min.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/bootstrap.min.js"></script>
    <script src="~/Scripts/knockout-3.0.0.js"></script>
</head>
    <body>
        @RenderSection("Body")
    </body>
@RenderSection("Scripts",false)
</html>

 2.添加HomeController。添加Index視圖。

 public class HomeController : Controller
    {
        public ViewResult Index()
        {
            return View();
        }
  }

控制器不需要其他的視圖和邏輯處理,因為這些都交給瀏覽器去處理了。

 Knockout的感覺和WPF很像,簡單的說,就是定義好模型和事件,綁定到元素上面就行了。我們先實現所有數據的讀取和刪除。

 1)定義模型

 var model = {
            reservations: ko.observableArray()
        };

ko.observableArray()相當於是集合.如果是單個模型,用ko.observable("")。例如:name: ko.observable("")。而reservations相當於是model的一個屬性。

2)定義異步方法。

   function sendAjaxRequest(httpMethod, callback, url) {
            $.ajax("/api/web" + (url ? "/" + url : ""), {
                type: httpMethod,
                success: callback
            });
        }

繼而定義一個獲取數據和刪除數據的方法

function getAllItems() {
            sendAjaxRequest("GET", function (data) {
                model.reservations.removeAll();
                for (var i = 0; i < data.length; i++) {
                    model.reservations.push(data[i]);//一個個添加進來
                }
            });

        }
        function removeItem(item) {
            sendAjaxRequest("DELETE", function () {
                getAllItems();
            }, item.ReservationId);
        }

這兩個方法只和數據相關,不用我們去關心dom的處理。

3)應用綁定。

  $(document).ready(function () {
            getAllItems();
            ko.applyBindings(model);
        });

全部腳本:

@section Scripts{
    <script>
        var model = {
            reservations: ko.observableArray()
        };

        function sendAjaxRequest(httpMethod, callback, url) {
            $.ajax("/api/web" + (url ? "/" + url : ""), {
                type: httpMethod,
                success: callback
            });
        }

        function getAllItems() {
            sendAjaxRequest("GET", function(data) {
                model.reservations.removeAll();
                for (var i = 0; i < data.length; i++) {
                    model.reservations.push(data[i]);
                }
            });

        }
        function removeItem(item) {
            sendAjaxRequest("DELETE", function () {
                getAllItems();
            }, item.ReservationId);
        }
        $(document).ready(function () {
            getAllItems();
            ko.applyBindings(model);
        });



    </script>
}
View Code

4)綁定到元素

綁定的表達式是

data-bind="type: expression" 

綁定到一個集合:

<tbody data-bind="foreach: model.reservations">

綁定到對象的屬性

<td data-bind="text: ReservationId"></td>

綁定到一個事件

<button class="btn btn-xs btn-primary"  data-bind="click: removeItem">Remove</button>

那全部的html的如下:

@section Body {
    <div id="summary" class="section panel panel-primary">
        @*@Html.Partial("Summary", Model)*@
        <div class="panel-body">
            <table class="table table-striped table-condensed">
                <thead>
                    <tr><th>ID</th><th>Name</th><th>Location</th><th></th></tr>
                </thead>
                <tbody data-bind="foreach:model.reservations">
                    <tr>
                        <td data-bind="text:ReservationId"></td>
                        <td data-bind="text:ClientName"></td>
                        <td data-bind="text:Location"></td>
                        <td>
                            <button class="btn btn-xs btn-primary"
                                    data-bind="click:removeItem">
                                Remove
                            </button>
                        </td>
                    </tr>

                </tbody>
            </table>

        </div>

    </div>
}
View Code

運行起來:

 

  點擊Remove馬上刪除。平滑的異步體驗。但是我們看removeitem方法就有點奇怪,是刪除之后又調用了一次getAllitems()來更新數據,那么我們也可以直接更新model

...
function removeItem(item) {
sendAjaxRequest("DELETE", function () {
for (var i = 0; i < model.reservations().length; i++) {
if (model.reservations()[i].ReservationId == item.ReservationId) { model.reservations.remove(model.reservations()[i]); break;
}
}
}, item.ReservationId);
}
...

但上面的KO語法有點奇怪,model.reservations()[i].ReservationId,調用集合中的某個對象時,要像函數一樣調用。KO試圖去維持標准的JavaScript語法,但還有些奇怪的地方,初次使用有些困惑,那也只能說,你會很快習慣的。

當然還有同學有疑問了。這和@Razor的方式有什么不同?主要有三個不同。

1.模型數據不再包含在html元素中了。換句話說就是在html加載之后,瀏覽器才使用異步請求更新數據。用F12打開,看不到任何數據。只有綁定的表達式。

 

2.當視圖被渲染的時候,數據在瀏覽器端得到處理,而不是在服務器上。

  換做是Razor語法,上面的語句就是這樣: 

<tbody> @foreach (var item in Model)
            {
                <tr>
                    <td>@item.ReservationId</td>
                    <td>@item.ClientName</td>
                    <td>@item.Location</td>
                    <td>
                        @Html.ActionLink("Remove","Remove",new{id=item.ReservationId},new{@class="btn btn-xs btn-primary"})
                    </td>
                </tr>
            }
        </tbody>

這都是在服務器端由Razor引擎渲染。

3.綁定的數據是“活”的,意味着數據模型的改變會馬上反應到有foreach和text綁定的內容中,F12,進入Console執行:model.reservations.pop() 取出一條數據,我們發現數據馬上更新了。

 

 所以Web api只需要提供基本的增刪改查就行了。而且在前端也讓程序員少寫了很多dom操作,而dom操作是腳本最為繁瑣和容易出錯的地方。接下來繼續完善這個demo。我們加入編輯部分。修改model如下

var model = {
reservations: ko.observableArray(),
editor: { name: ko.observable(""), location: ko.observable("") }
};

增加了一個editor,包含name和Location兩個屬性。

修改sendAjaxRequest 方法 增加傳輸的數據對象。

function sendAjaxRequest(httpMethod, callback, url, reqData) {
$.ajax("/api/web" + (url ? "/" + url : ""), {
type: httpMethod,
success: callback,
data: reqData
});
}

增加一個編輯的方法:

function handleEditorClick() {
sendAjaxRequest("POST", function (newItem) {
model.reservations.push(newItem);
}, null, {
ClientName: model.editor.name, Location: model.editor.location
});
}

這個方法的意思是,當提交的時候,用post方式將editor的name和Location傳遞給api,web api會根據post方式自動匹配到PostReservation方法(MVC的模型綁定會自動講這兩個值生成一個Reservation對象)。然后將返回的Reservation對象添加到model中。這個時候頁面就會更新。

html:

<div id="editor" class="section panel panel-primary">
<div class="panel-heading">
Create Reservation
</div>
<div class="panel-body">
<div class="form-group">
<label>Client Name</label>
<input class="form-control" data-bind="value: model.editor.name" />
</div>
<div class="form-group">
<label>Location</label>
<input class="form-control" data-bind="value: model.editor.location" />
</div>
<button class="btn btn-primary"
data-bind="click: handleEditorClick">Save</button>
</div>
</div>

然后運行,我們添加數據之后,馬上更新。相對於傳統的方法,我們需要獲取dom中的數據,然后提交到后台,然后得到返回的數據更新table。

進而我們可以控制元素的顯示。修改model。添加displaysummary。初始化為一個bool值。

      var model = {
            reservations: ko.observableArray(),
            editor: {
                name: ko.observable(""),//相當於兩個變量。
                location: ko.observable(""),
            },
            displaySummary: ko.observable(true)
        };

添加隱藏方法,修改handleCreateClick

function handleCreateClick() {
model.displaySummary(false);
}
function handleEditorClick() {
sendAjaxRequest("POST", function (newItem) {
model.reservations.push(newItem);
model.displaySummary(true);
}, null, {
ClientName: model.editor.name,
Location: model.editor.location
});
}

綁定到元素: 這里用到了if表達式。

<div id="summary" class="section panel panel-primary"
data-bind="if: model.displaySummary">
<div class="panel-heading">Reservation Summary</div>
...
</div>

運行,就可以自在切換了。

   

 

 小結:以上只是Knockout的簡單介紹,但是和Web api配合起來確實感覺不錯。更多Knockout的api請參考官網。knockoutjs  。 希望本文對你有幫助。

demo 下載: SPA

 參考書籍:Apress Pro Asp.Net MVC5   此書在我的讀書群里面有下載,q群:452450927。歡迎愛讀書,愛分享的朋友加入。

 參考文章:小牛之路 web api

 

 

 

         

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM