【讀書筆記】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。css

 1、工程準備html

       1.新建一個Api工程。前端

       

      2.建立模型和倉庫jquery

        Reservation:    程序員

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

      ReservationRespository:在真實的項目中,倉庫都須要有接口和依賴注入,可是這裏咱們把重點放到後,將這個部分簡化。因此也沒有用數據庫,所有放到內存裏面。web

 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

     用來爲咱們的單頁應用提供數據的增刪改查。ajax

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

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);
        }
    }

2、Web Api  express

  這個時候運行訪問 /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的方法來進行交互。

3、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

 

相關文章
相關標籤/搜索