整合Knockout與ASP.NET Web API設計CRUD網頁

by vivid 17. 七月 2013 10:22

.NET Magazine國際中文電子雜誌
者:許薰尹
稿:張智凱
文章編號:N130713802
出刊日期:2013/7/17

Knockout是一個開放源碼,套用Model-View-ViewModel (MVVM) 模式(pattern)的JavaScript函式庫,能夠利用宣告式的語法設定UI繫結(declarative bindings)。透過Knockout,我們可以使用View與declarative bindings定義使用者介面;使用ViewModel定義資料(Data)與行為(Behavior)。Knockout支援,dependency tracking自動在資料異動時,同步物件與設定繫結的標籤。官方網站在:http://knockoutjs.com/

Model-View-ViewModel模式的定義如下:

  • Model:代表應用程式儲存的資料,和UI獨立
  • ViewModel:代表UI上的資料與操作(operation),不用來儲存資料,而是用來暫存使用者正在操作的資料。通常是一個JavaScript物件,不需知道任何View的細節。
  • View:可視的使用者介面,用來顯示ViewModel中的資訊,可以因應使用者的互動,如點選按鈕或連結,傳送命令到ViewModel,並在ViewModel變動時,自動更新內容。

本篇文章將以step-by-step的方式,利用Knockout與ASP.NET Web API來設計網頁應用程式。範例看起來如下,上方的區塊用來新增資料;下方的清單顯示資料表目前的所有資料。清單中的項目都包含一個「Delete」按鈕用來刪除資料;「Edit」按鈕則用來切換到編輯模式,以修改資料,請參考下圖所示:

clip_image002

圖 1:整合Knockout與ASP.NET Web API設計CRUD網頁範例。

建立專案

從Visual Studio 2012開發工具 -「File」-「New」-「Project」,在「New Project」對話盒中選取程式語言,例如本範例選擇「Visual C#」,從「Web」分類中,選取「ASP.NET MVC 4 Web Application」,名稱設定為「KODemo」稱;按下「OK」鍵,請參考下圖所示:

clip_image004

圖 2:建立「ASP.NET MVC 4 Web Application」專案。

在「New ASP.NET MVC 4 Project」對話盒選取「Web API」類型專案,檢視引擎(View Engine)使用預設的「Razor」,按下「OK」鍵,請參考下圖所示:

clip_image006

圖 3:選取「Web API」類型專案。

完成這個步驟之後,就建立好一個Web API範本專案,在Visual Studio 2012開發工具按F5執行這個應用程式,Visual Studio 2012預設會使用IIS Express來執行你的網站應用程式。自動賦予此網站一個埠號來執行,此筆者目前的環境而言,網站路徑為「http://localhost: 51207」,代表使用51207埠。

http://localhost:51207/

網站應用程式執行後會啟動瀏覽器,首先會顯示ASP.NET Web API範例頁面,請參考下圖所示:

clip_image008

圖 4:ASP.NET Web API範例頁面。

從「方案總管(Solution Explorer)」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「ADO.NET Entity Data Model」,使用預設的「Model1.edmx」當作名稱,按「Add」按鈕,請參考下圖所示:

clip_image010

圖 5:新增「ADO.NET Entity Data Model」。

在「實體資料模型精靈(Entity Data Model Wizard)」視窗中,目前有兩種選項:「Generate from database」與「Empty model」。選擇模型內容為「Generate from database」,然後選「Next」按鈕,請參考下圖所示:

clip_image012

圖 6:「Generate from database」從資料庫建立模型。

接著設定資料連接,點選「New Connection」開啟「Connection Properties」視窗,請參考下圖所示:

clip_image014

圖 7:新增資料庫連線。

在「Connection Properties」視窗,設定「Data Source」為「Microsoft SQL Server」;「Server Name」則輸入您的資料庫伺服器名稱,本文使用「.\Sqlexpress」;「Select or enter a database name」則選取「Northwind」資料庫,然後按下「OK」按鈕,請參考下圖所示:

clip_image016

圖 8:連接到SQL Server。

選「Next」按鈕,勾選「Region」資料表,以及「Pluralize or singularize generated object names」然後按下「Finish」按鈕,請參考下圖所示:

clip_image018

圖 9:選取資料表。

選取Visual Studio 2012開發工具上方的「Build」-「Build Solution」編譯目前的方案。

新增Controller

從Visual Studio 2012開發工具-「Solution Explorer」- 你的專案-「Controllers」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」- 「Controller」。設定控制器名稱為「RegionsController」;在這個階段Template設定為「API controller with read/write actions, using Entity Framework」,Model Class選擇「Region」,Data context class選取「NorthwindEntities」,然後按下「Add 」按鈕,請參考下圖所示:

clip_image020

圖 10:新增Controller。

Visual Studio 2012會自動在Controllers目錄中,產生RegionsController.cs檔案,包含利用Entity Framework來新增、刪除、修改、查詢資料的程式碼:

namespace KODemo.Controllers {
  public class RegionsController : ApiController {
    private NorthwindEntities db = new NorthwindEntities( );

    // GET api/Regions
    public IEnumerable<Region> GetRegions( ) {
      return db.Regions.AsEnumerable( );
    }

    // GET api/Regions/5
    public Region GetRegion( int id ) {
      Region region = db.Regions.Find( id );
      if ( region == null ) {
        throw new HttpResponseException( Request.CreateResponse( HttpStatusCode.NotFound ) );
      }

      return region;
    }

    // PUT api/Regions/5
    public HttpResponseMessage PutRegion( int id , Region region ) {
      if ( !ModelState.IsValid ) {
        return Request.CreateErrorResponse( HttpStatusCode.BadRequest , ModelState );
      }

      if ( id != region.RegionID ) {
        return Request.CreateResponse( HttpStatusCode.BadRequest );
      }

      db.Entry( region ).State = EntityState.Modified;

      try {
        db.SaveChanges( );
      } catch ( DbUpdateConcurrencyException ex ) {
        return Request.CreateErrorResponse( HttpStatusCode.NotFound , ex );
      }

      return Request.CreateResponse( HttpStatusCode.OK );
    }

    // POST api/Regions
    public HttpResponseMessage PostRegion( Region region ) {
      if ( ModelState.IsValid ) {
        db.Regions.Add( region );
        db.SaveChanges( );

        HttpResponseMessage response = Request.CreateResponse( HttpStatusCode.Created , region );
        response.Headers.Location = new Uri( Url.Link( "DefaultApi" , new { id = region.RegionID } ) );
        return response;
      }
      else {
        return Request.CreateErrorResponse( HttpStatusCode.BadRequest , ModelState );
      }
    }

    // DELETE api/Regions/5
    public HttpResponseMessage DeleteRegion( int id ) {
      Region region = db.Regions.Find( id );
      if ( region == null ) {
        return Request.CreateResponse( HttpStatusCode.NotFound );
      }

      db.Regions.Remove( region );

      try {
        db.SaveChanges( );
      } catch ( DbUpdateConcurrencyException ex ) {
        return Request.CreateErrorResponse( HttpStatusCode.NotFound , ex );
      }

      return Request.CreateResponse( HttpStatusCode.OK , region );
    }

    protected override void Dispose( bool disposing ) {
      db.Dispose( );
      base.Dispose( disposing );
    }
  }
}


 

 

讀取資料

預設建立Web API專案時,Scripts目錄下,已經包含了jQuery與Knockout函式庫,您可以在專案中直接使用它們,請參考下圖所示:

clip_image022

圖 11:ASP.NET Web API專案預設引用jQuery、knockout函式庫。

預設建立Web API專案時,Controllers目錄中包含了HomeController,其中包含Index Action:

 

namespace KODemo.Controllers {
  public class HomeController : Controller {
    public ActionResult Index( ) {
      return View( );
    }
  }
}

 

專案中「Views」-「Home」目錄下,包含Index.cshtml,此為網站的首頁。刪掉Index.cshtml所有內容,只留下id為「body」的<div>標籤

<div id = "body">

</div>

我們要利用jQuery的$.ajax方法來讀取Web API的資料,在<div>標籤之後加入以下程式碼(url中的51207埠號,請記得修改成正確的):

 

@section scripts{
    <script>
            var url = "http://localhost:51207/api/Regions/";
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                  alert( data.length);
                },
                error: function (e) {
                    alert(e);
                }
            });
    </script>
}

 

$.ajax方法傳入一個settings物件,設定:

  • url:為ASP.NET Web API所在網址。
  • type:使用HTTP GET。
  • dataType:設定為「json」,表示預期從伺服端取回JSON物件。
  • success:請求成功執行時,要繼續執行的回呼方法(callback function),從data參數的length屬性,顯示回傳的資料筆數。
  • error:請求執行失敗時,要繼續執行的回呼方法。

在Visual Studio 2012開發工具按F5執行這個應用程式,若讀取資料成功,便會顯示一個對話方塊,顯示目前取回4筆資料,請參考下圖所示:

clip_image024

圖 12:取回Region資料表資料筆數。

使用Knockout函式庫

修改Home目錄下的Index.cshtml檔案,在「@section scripts」區段的第一行引用Knockout函式庫:

 

<script src = "~/Scripts/knockout-2.2.0.js"> </script>

 

定義Region

在<script>標籤中,宣告url變數的程式碼下方加上Region 類別代表一筆Region資料,從建構函式傳入regionID、與regionDescription參數來初始化Region中的RegionID與RegionDescription屬性:

 

var url = "http://localhost:51207/api/Regions/";
  function Region(regionID, regionDescription) {
      var self = this;
      self.RegionID = regionID;
      self.RegionDescription = regionDescription;
  }

在Region中,宣告一個self變數來代表物件本身(this),這是防止this被其他程式重新定義,或參考到別的物件。

 

定義ViewModel

下一步在Region宣告的下方,定義regionsViewModel,其中包含一個regions屬性,屬性初始化為observableArray,以便後續在異動其中的項目時,能夠自動進行追蹤。此外,將呼叫jQuery .ajax()方法取回資料的程式放到ViewModel:

 

function regionsViewModel() {
       var self = this;
       self.regions = ko.observableArray([]);
       $.ajax({
           url: url,
           type: 'GET',
           dataType: 'json',
           success: function (data) {
               var mappedRegions = $.map(data, function (item) { return new Region(item.RegionID, item.RegionDescription); });
               self.regions( mappedRegions );
           },
           error: function (e) {
               alert(e);
           }
       });
   }

 

修改success中的方法,叫用jQuery的$.map方法,將data陣列中的項目,轉換成Region物件,然後放到regionsViewModel的regions屬性,因為regions屬性是observableArray,所以要使用方法的語法來設定regions屬性值。

建立繫結

最後在</script>上方,加上以下程式碼,利用knockout applyBindings方法設定繫結,傳入regionsViewModel物件:

ko.applyBindings( new regionsViewModel() );

 

定義View

我們需要定義View來呈現ViewModel中的資料,在id為「body」的<div>標籤之中,加入以下HTML標籤:

<div id = "body">
    <h2> KnockoutDemo </h2>
    <table>
        <thead>
            <tr>
                <th> RegionID </th>
                <th> RegionDescription </th>
                <th> </th>
            </tr>
        </thead>
        <tbody data-bind = "foreach: regions">
            <tr>
                <td data-bind = "text: RegionID"> </td>
                <td data-bind = "text: RegionDescription"> </td>
            </tr>
        </tbody>
    </table>
</div>

 

<tbody>開頭標籤中設定「data-bind="foreach: regions"」,利用knockout foreach binding取出regionsViewModel中regions陣列內的Region物件做繫結。因為目前只需呈現唯讀的資料,我們利用knockout text binding在<td>標籤中呈現RegionID���RegionDescription的值。

完整Index.cshtml

截至目前為止,Index.cshtml完整程式看起來如下:

<div id = "body">
    <h2> KnockoutDemo </h2>
    <table>
        <thead>
            <tr>
                <th> RegionID </th>
                <th> RegionDescription </th>
                <th> </th>
            </tr>
        </thead>
        <tbody data-bind = "foreach: regions">
            <tr>
                <td data-bind = "text: RegionID"> </td>
                <td data-bind = "text: RegionDescription"> </td>
            </tr>
        </tbody>
    </table>

</div>
@section scripts{
    <script src="~/Scripts/knockout-2.2.0.js"></script>
    <script>
        var url = "http://localhost:51207/api/Regions/";
        function Region(regionID, regionDescription) {
            var self = this;
            self.RegionID = regionID;
            self.RegionDescription = regionDescription;
        }

        function regionsViewModel() {
            var self = this;
            self.regions = ko.observableArray([]);
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    var mappedRegions = $.map(data,
                        function (item) {
                            return new Region(item.RegionID, item.RegionDescription);
                        });
                    self.regions(mappedRegions);
                },
                error: function (e) {
                    alert(e);
                }
            });
        }
        ko.applyBindings(new regionsViewModel());
    </script>
}

 

測試

在Visual Studio 2012開發工具按F5執行這個應用程式,若讀取資料成功,便會以清單的方式,顯示從ASP.NET Web API取回的資料庫資料,請參考下圖所示:

clip_image026

圖 13:顯示從ASP.NET Web API取回的資料庫資料。

新增資料

修改regionsViewModel程式碼,在宣告regions屬性程式下,新增一個createRegionForm方法,這個方法將傳入Form項目,我們利用jQuery .serialize()方法,將表單中的項目序列化成文字格式,以便傳送到伺服端。接著叫用jQuery .post()方法以HTTP POST送出請求,以新增一筆資料:

 

self.createRegionForm = function (formElement) {
     $.post(url, $(formElement).serialize(), "json")
             .done(function (newRegion) {
                 var r = new Region(newRegion.RegionID,newRegion.RegionDescription);
                 self.regions.push(r);
             });
}

 

.post()方法第一個參數ASP.NET Web API所在的URL位址;第二個參數為表單序列化完的字串資料;第三個參數設為「json」,表示預期從伺服端取得JSON格式的資料。.post()方法呼叫完成後,再叫用.done()方法,將伺服端傳回的新Region資料取出,以這些資料建立Region物件,然後叫用push方法,加到regions屬性中。

 

建立AddRegion View

在這個範例中,我們將要建立一個部分檢視(Partial View)來當新增資料的介面。從Visual Studio 2012開發工具-「Solution Explorer」- 你的專案-「Views\Home」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」- 「View」。設定View名稱為「AddRegion」;勾選「Create a strongly-typed view」;Model Class選取「Region」;Scaffold template選取「Create」,勾選「Create as a partial view」然後按下「Add 」按鈕,請參考下圖所示:

clip_image028

圖 14:建立AddRegion View。

刪除AddRegion.cshtml檔案最下方的標籤:

<div>

@Html.ActionLink("Back to List", "Index")

</div>

Visual Studuio 2012不會顯示RegionID欄位的標籤,因為Northwind資料庫Region資料表中,RegionID欄位並不是自動編號欄位,我們需要手動修改AddRegion.cshtml,顯示RegionID欄位以供編輯。選取工具為我們產生的兩個顯示RegionDescription介面的<div>標籤,按下滑鼠右鍵,從快捷選單中選取「Copy」,請參考下圖所示:

clip_image030

圖 15:複製標籤。

在<legend>下方按CTRL + V貼上標籤,修改標籤如下,呈現RegionID編輯畫面:

 

<div class = "editor-label">
            @Html.LabelFor(model => model.RegionID)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.RegionID)
            @Html.ValidationMessageFor(model => model.RegionID)
        </div>

 

 

此外我們希望產生出來的<form>標籤可以加上knockout submit binding:「data-bind="submit: createRegionForm"」。

<form data-bind = "submit: createRegionForm">

submit binding指定一個JavaScript事件處理常式(createRegionForm),當表單提交後,knockout會擋掉預設表單的提交行為,不將表單送到伺服器,而叫用createRegionForm方法。

修改AddRegion.cshtml檔案中,「@using Html.BeginForm」這行程式如下,就可以產生我們需要的data-bind attribute:

@using ( Html.BeginForm( "" , "" ,
    FormMethod.Post ,
    new Dictionary<string , object> { { "data-bind" , "submit:createRegionForm" } } ) ) {

 

修改View

在Index.cshtml上方,<h2>標籤下加入以下程式碼,利用Html.Partial方法,插入AddRegion View:

<p>
    @Html.Partial("AddRegion")
</p>

 

測試

在Visual Studio 2012開發工具按F5執行這個應用程式,試著新增一筆資料,按下「Create」按鈕,新增的資料便會顯示在畫面清單中,請參考下圖所示:

clip_image032

圖 16:新增一筆資料。

因為資料繫結的關係,新增完文字方塊的資料不會清除,若要清除文字方塊內容,可以

修改ViewModel createRegionForm方法,利用val()方法清空文字方塊的內容:

self.createRegionForm = function (formElement) {
     $.post(url, $(formElement).serialize(), "json")
             .done(function (newRegion) {
                 var r = new Region(newRegion.RegionID,newRegion.RegionDescription);
                 self.regions.push(r);
                $(".editor-field #RegionID").val("");
                 $(".editor-field #RegionDescription").val("")
;                
             });
}

這樣資料新增完成之後,文字方塊的內容便會清空,請參考下圖所示:

clip_image034

圖 17:新增一筆資料完成清空畫面。

Index.cshtml完整程式

截至目前為止,Index.cshtml完整程式看起來如下:

<div id = "body">
    <h2> KnockoutDemo </h2>
    <p>
       @Html.Partial("AddRegion")
    </p>

    <table>
        <thead>
            <tr>
                <th> RegionID </th>
                <th> RegionDescription </th>
                <th> </th>
            </tr>
        </thead>
        <tbody data-bind="foreach: regions">
            <tr>
                <td data-bind="text: RegionID"></td>
                <td data-bind="text: RegionDescription"></td>
            </tr>
        </tbody>
    </table>

</div>
@section scripts{
    <script src="~/Scripts/knockout-2.2.0.js"></script>
    <script>
        var url = "http://localhost:51207/api/Regions/";
        function Region(regionID, regionDescription) {
            var self = this;
            self.RegionID = regionID;
            self.RegionDescription = regionDescription;
        }

        function regionsViewModel() {
            var self = this;
            self.regions = ko.observableArray([]);
            self.createRegionForm = function (formElement) {
                $.post(url, $(formElement).serialize(), "json")
                        .done(function (newRegion) {
                            var r = new Region(newRegion.RegionID, newRegion.RegionDescription);
                            self.regions.push(r);
                            $(".editor-field #RegionID").val("");
                            $(".editor-field #RegionDescription").val("");
                        });
            }

            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    var mappedRegions = $.map(data,
                        function (item) {
                            return new Region(item.RegionID, item.RegionDescription);
                        });
                    self.regions(mappedRegions);
                },
                error: function (e) {
                    alert(e);
                }
            });
        }
        ko.applyBindings(new regionsViewModel());
    </script>
}


 

刪除資料

下一步進行資料刪除的設計。首先修改ViewModel,在createRegionForm方法定義下方,加入deleteRegion方法,利用ajax方法送出HTTP DELETE,id則為欲刪除的RegionID值,若叫用完成,則從regions陣列中移除此Region物件:

self.deleteRegion = function (r) {
              var id = r.RegionID;
              $.ajax({ type: "DELETE", url: url + id })
                  .done(function () {
                      self.regions.remove(r);
                  });
          }

 

修改View,在<tbody>-<tr>內加入第三個<td>,其中包含一個連結,利用knockout click binding繫結到regionViewModel的deleteRegion方法,我們利用$root來找到foreach外層的regionViewModel:

<td>

<a href="#" data-bind="click: $root.deleteRegion">Delete</a>

</td>

在Visual Studio 2012開發工具按F5執行這個應用程式執行測試,可以正確地刪除資料。

 

刪除確認

若要防止使用者誤點選「Delete」連結誤刪資料,我們可以加上刪除確認的動作,修改deleteRegion方法,利用confirm顯示確認刪除的方塊:

self.deleteRegion = function (r) {
                var id = r.RegionID;

                var sure = confirm("Are you sure?");
                if (!sure)
                    return;
               
                $.ajax({ type: "DELETE", url: url + id })
                    .done(function () {
                        self.regions.remove(r);
                    });
            }


在Visual Studio 2012開發工具按F5執行這個應用程式執行測試,可以看到確認刪除的對話盒,請參考下圖所示:

clip_image036

圖 18:確認刪除的對話盒。

Index.cshtml完整程式

截至目前為止,Index.cshtml完整程式看起來如下:

<div id="body">
    <h2> KnockoutDemo </h2>
    <p>
       @Html.Partial("AddRegion")
    </p>

    <table>
        <thead>
            <tr>
                <th>RegionID </th>
                <th> RegionDescription </th>
                <th> </th>
            </tr>
        </thead>
        <tbody data-bind="foreach: regions">
            <tr>
                <td data-bind="text: RegionID"></td>
                <td data-bind="text: RegionDescription"></td>
                <td>
                       <a href="#" data-bind="click: $root.deleteRegion">Delete</a>
                </td>
            </tr>
        </tbody>
    </table>

</div>
@section scripts{
    <script src="~/Scripts/knockout-2.2.0.js"></script>
    <script>
        var url = "http://localhost:51207/api/Regions/";
        function Region(regionID, regionDescription) {
            var self = this;
            self.RegionID = regionID;
            self.RegionDescription = regionDescription;       
        }

        function regionsViewModel() {
            var self = this;
            self.regions = ko.observableArray([]);
            self.createRegionForm = function (formElement) {
                $.post(url, $(formElement).serialize(), "json")
                        .done(function (newRegion) {
                            var r = new Region(newRegion.RegionID, newRegion.RegionDescription);
                            self.regions.push(r);
                            $(".editor-field #RegionID").val("");
                            $(".editor-field #RegionDescription").val("");
                        });
            }
            self.deleteRegion = function (r) {
                var id = r.RegionID;

                var sure = confirm("Are you sure?");
                if (!sure)
                    return;
               
                $.ajax({ type: "DELETE", url: url + id })
                    .done(function () {
                        self.regions.remove(r);
                    });
            }
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    var mappedRegions = $.map(data,
                        function (item) {
                            return new Region(item.RegionID, item.RegionDescription);
                        });
                    self.regions(mappedRegions);
                },
                error: function (e) {
                    alert(e);
                }
            });
        }
        ko.applyBindings(new regionsViewModel());
    </script>
}

 

編輯資料

最後我們來談談資料的編輯動作,首先修改Region Model,RegionDescription屬性改用observable,新增一個oriRegionDescription屬性,儲存資料修改前的原始值,若使用者取消編輯,就可以還原資料。editMode屬性則用來記錄目前是否處於編輯模式;showEditPanel方法則用來顯示編輯介面:

function Region(regionID, regionDescription) {
            var self = this;
            self.RegionID = regionID;
           self.RegionDescription = ko.observable(regionDescription);

            self.oriRegionDescription = regionDescription;
            self.editMode = ko.observable(false);
            self.showEditPanel = function (event) {
                self.oriRegionDescription = self.RegionDescription();
                self.editMode(true);
            }
            self.cancelEdit = function () {
                self.RegionDescription(self.oriRegionDescription);
                self.editMode(false);
            }
            self.editRegion = function (aRegion) {
                var self = this;
                var r = ko.toJS(aRegion);
                var json = JSON.stringify(r);
                var id = r.RegionID;        
                $.ajax({
                    url: url + id,
                    cache: false,
                    type: 'PUT',
                    contentType: 'application/json; charset=utf-8',
                    data: json,
                    success: function () {
                        self.editMode(false);
                    }
                });
            }

        }

 

修改View

在「Delete」連結下,加入一個「Edit」連結,利用knockout click binding繫結到showEditPanel方法,當使用者點選「Edit」連結,便顯示編輯介面。在<tbody>中加入第二個<tr>,利用knockout visible binding繫節到regionViewModel的editMode屬性,在進入編輯模式時,才顯示此<tr>內容。<tr>內使用文字方塊搭配knockout value binding來顯示RegionDescription內容。<tr>中還包含兩個<a>分別繫結到editRegion、cancelEdit方法,用來儲存與取消編輯。

<div id="body">
    <h2>KnockoutDemo</h2>
    <p>
        @Html.Partial( "AddRegion" )
    </p>

    <table>
        <thead>
            <tr>
                <th>RegionID</th>
                <th>RegionDescription</th>
                <th></th>
            </tr>
        </thead>
        <tbody data-bind="foreach: regions">
            <tr>
                <td data-bind="text: RegionID"></td>
                <td data-bind="text: RegionDescription"></td>
                <td>
                    <a href="#" data-bind="click: $root.deleteRegion">Delete</a>
                   <a href="#" data-bind="click: showEditPanel">Edit</a>
                </td>
            </tr>
            <tr data-bind="visible: editMode" style="background-color: lightblue">
                <td data-bind="text: RegionID"></td>
                <td>RegionDescription:<input data-bind="value: RegionDescription" />
                <td>
                    <a href="#" data-bind="click: editRegion">Save</a>
                    <a href="#" data-bind="click: cancelEdit">Cancel</a>
                </td>
            </tr>
        </tbody>
    </table>
</div>

 

測試

在Visual Studio 2012開發工具按F5執行這個應用程式,試著點選任一筆資料的「Edit」按鈕,修改一筆資料,按下「Save」連結,便將資料儲存到資料庫,請參考下圖所示:

clip_image038

圖 19:編輯資料。

Index.cshtml完整程式

最後Index.cshtml完整程式看起來如下:

<div id="body">
    <h2>KnockoutDemo</h2>
    <p>
        @Html.Partial( "AddRegion" )
    </p>

    <table>
        <thead>
            <tr>
                <th> RegionID </th>
                <th> RegionDescription </th>
                <th> </th>
            </tr>
        </thead>
        <tbody data-bind="foreach: regions">
            <tr>
                <td data-bind="text: RegionID"></td>
                <td data-bind="text: RegionDescription"></td>
                <td>
                    <a href="#" data-bind="click: $root.deleteRegion">Delete</a>
                    <a href="#" data-bind="click: showEditPanel">Edit</a>
                </td>
            </tr>
            <tr data-bind="visible: editMode" style="background-color: lightblue">
                <td data-bind="text: RegionID"></td>
                <td>RegionDescription:<input data-bind="value: RegionDescription" />
                <td>
                    <a href="#" data-bind="click: editRegion">Save</a>
                    <a href="#" data-bind="click: cancelEdit">Cancel</a>
                </td>
            </tr>
        </tbody>
    </table>
</div>

@section scripts{
    <script src="~/Scripts/knockout-2.2.0.js"></script>
    <script>
        var url = "http://localhost:51207/api/Regions/";
       
        function Region(regionID, regionDescription) {
            var self = this;
            self.RegionID = regionID;
            //self.RegionDescription = regionDescription;       
            self.RegionDescription = ko.observable(regionDescription);
            self.oriRegionDescription = regionDescription;
            self.editMode = ko.observable(false);
            self.showEditPanel = function (event) {
                self.oriRegionDescription = self.RegionDescription();
                self.editMode(true);
            }
            self.cancelEdit = function () {
                self.RegionDescription(self.oriRegionDescription);
                self.editMode(false);
            }
            self.editRegion = function (aRegion) {
                var self = this;
                var r = ko.toJS(aRegion);
                var json = JSON.stringify(r);
                var id = r.RegionID;        
                $.ajax({
                    url: url + id,
                    cache: false,
                    type: 'PUT',
                    contentType: 'application/json; charset=utf-8',
                    data: json,
                    success: function () {
                        self.editMode(false);
                    }
                });
            }

        }

        function regionsViewModel() {
            var self = this;
            self.regions = ko.observableArray([]);
            self.createRegionForm = function (formElement) {
                $.post(url, $(formElement).serialize(), "json")
                        .done(function (newRegion) {
                            var r = new Region(newRegion.RegionID, newRegion.RegionDescription);
                            self.regions.push(r);
                            $(".editor-field #RegionID").val("");
                            $(".editor-field #RegionDescription").val("");
                        });
            }
            self.deleteRegion = function (r) {
                var id = r.RegionID;

                var sure = confirm("Are you sure?");
                if (!sure)
                    return;
               
                $.ajax({ type: "DELETE", url: url + id })
                    .done(function () {
                        self.regions.remove(r);
                    });
            }
            $.ajax({
                url: url,
                type: 'GET',
                dataType: 'json',
                success: function (data) {
                    var mappedRegions = $.map(data,
                        function (item) {
                            return new Region(item.RegionID, item.RegionDescription);
                        });
                    self.regions(mappedRegions);
                },
                error: function (e) {
                    alert(e);
                }
            });
        }
        ko.applyBindings(new regionsViewModel());
    </script>
}

目前評分 5.0 , 共有 1 人參與

  • Currently 5.0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

.NET Magazine國際中文電子雜誌 | 許薰尹Vivid Hsu | ASP.NET Web API | Knockout

評論 (35) -

cours de theatre
cours de theatre United States
2017/9/30 上午 10:42:46 #

Thanks so much for the post.Really looking forward to read more.

Osimi sea view
Osimi sea view United States
2017/10/9 下午 07:46:53 #

Really appreciate you sharing this blog post.Much thanks again. Will read on...

buy hacklink google
buy hacklink google United States
2017/10/12 下午 10:25:01 #

I really liked your post.Thanks Again. Cool.

see
see United States
2017/10/14 下午 05:41:31 #

Appreciate you sharing, great blog article.Thanks Again. Much obliged.

dragon city hack unlimited gems apk
dragon city hack unlimited gems apk United States
2017/10/15 下午 05:12:11 #

Thanks for sharing, this is a fantastic article. Really Cool.

read this article
read this article United States
2017/10/17 下午 04:30:34 #

I value the article post.Really thank you! Keep writing.

sletrokor review
sletrokor review United States
2017/10/17 下午 10:01:57 #

Fantastic blog.

VigRx
VigRx United States
2017/10/19 上午 09:05:49 #

Im grateful for the blog article. Want more.

Get More Info
Get More Info United States
2017/10/19 下午 08:11:56 #

This is one awesome article post. Great.

carte grise en ligne
carte grise en ligne United States
2017/10/21 上午 09:11:09 #

Very informative post. Fantastic.

sciatica home remedies
sciatica home remedies United States
2017/11/15 上午 11:18:10 #

Fantastic blog. Really Cool.

I value the blog article.Thanks Again. Fantastic.

bikinis
bikinis United States
2017/11/24 上午 01:27:25 #

Thanks so much for the blog article.Much thanks again.

porno
porno United States
2017/12/1 下午 08:13:09 #

Really informative blog.Really thank you! Great.

Business Credit For Small Business Loan
Business Credit For Small Business Loan United States
2017/12/3 上午 08:21:42 #

I really liked your blog article.Really thank you! Keep writing.

mobile porno
mobile porno United States
2017/12/5 下午 01:12:29 #

Very good article. Much obliged.

Muchos Gracias for your post.Really thank you!

I really liked your blog post. Keep writing.

Lynne Asad
Lynne Asad United States
2017/12/14 下午 12:50:48 #

I really enjoy the blog article.Really thank you! Will read on...

Hanukkah
Hanukkah United States
2017/12/15 上午 02:24:34 #

I really liked your blog post.Really thank you! Want more.

green coffee bean for weight loss
green coffee bean for weight loss United States
2017/12/17 上午 03:51:15 #

I am so grateful for your article.Much thanks again. Fantastic.

This is one awesome blog post.Really thank you! Really Cool.

Processes
Processes United States
2017/12/17 下午 08:59:15 #

Very informative article.Much thanks again. Fantastic.

driver canon
driver canon United States
2017/12/23 下午 01:39:37 #

Im grateful for the article post.Thanks Again. Really Great.

I loved your blog article. Great.

SOCCER HIGHLIGHTS
SOCCER HIGHLIGHTS United States
2017/12/26 下午 02:23:48 #

Enjoyed every bit of your article.Really thank you! Want more.

canon printer series
canon printer series United States
2017/12/27 下午 02:56:49 #

Great post.Really looking forward to read more. Keep writing.

hp drivers
hp drivers United States
2018/1/2 上午 05:42:00 #

I really liked your blog post.Really looking forward to read more. Really Cool.

this link
this link United States
2018/1/2 下午 01:38:58 #

Great, thanks for sharing this blog article.Really looking forward to read more.

Muchos Gracias for your blog.Really looking forward to read more. Will read on...

canon drivers
canon drivers United States
2018/1/3 上午 04:57:50 #

Really appreciate you sharing this blog article.Really looking forward to read more.

online casinos for real money usa
online casinos for real money usa United States
2018/1/4 下午 03:29:39 #

Thank you for your article post.Thanks Again. Keep writing.

hp driver
hp driver United States
2018/1/5 下午 08:27:18 #

Fantastic article.Thanks Again. Really Cool.

FBA
FBA United States
2018/1/6 下午 12:31:23 #

I think this is a real great article.Much thanks again. Will read on...

web hosting
web hosting United States
2018/1/10 下午 02:30:40 #

I appreciate you sharing this blog.Really looking forward to read more. Fantastic.

NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List