asp.net Web開發框架 (4) - 使用Vue.js進行前端資料繫結顯示(Template Rendering)
弱水三千,為何只取Vue.js?
一直以來,我覺得大部分的開發技術其實也是一種工具,也有生命週期。既然是一種工具,那開發技術當然不能只是拿來學習,而應該是拿來使用的。最近幾年,隨著開發技術的生命週期越來越短,不免讓人開始猶豫該如何選擇。如果你慢慢發現,學習某一門技術的時間並沒有遠低於使用它的時間,那就得回頭重新評估這個工具了…
但,千萬別誤會,我不是叫大家不要花時間去進修,或是減少學習的時間。這年頭,我們比過去以前任何一個時代都需要時時刻刻學習。但,正因為我們的時間越來越珍貴,越來越難以過去的大量快速學習來面對技術的變遷,因為現在不管學得再快,也無法趕上變化的腳步…
莫名其妙講了一堆,好像跟這個主題無關?
非也,我想說的是,最近幾年我在專案中選擇的工具(開發技術)時,都盡可能以不用花太多學習時間的為主。因為工具(技術或框架)最終的目的是幫你節省時間用的,倘若學習成本太高,可能會本末倒置。而這個理由,也恰巧是我們選擇Vue.js作為前端開發框架的原因。
當過去幾年我們在.net環境嘗試建構一個適當的開發架構時,三層式的SPA對我們來說是個能同時滿足行動裝置、Web、以及桌面應用的理想選擇,而要採用何者搭配SPA作為Web前端開發套件呢? 最終,我們選擇了Vue.js。
理由?
- 它的程式碼很簡潔,非常輕量級,執行速度也算快,一點都不讓人覺得累贅。
- 它很好學,還有中文的文件(唯一的遺憾是簡體),對於已經有前後端程式碼分離、SPA基本概念的人,用起來跟喝水差不多。
- 它在資料繫結這個部份的做法非常直覺,MVVM概念的實現很接近以前XAML的作法,同時,它採用的標記語法很簡單,也極不容易破壞HTML版面,和設計師(designer)很好合作。
以上是最主要的這幾點。
Demo的前置準備
我們前面說過Vue.js學起來很簡單,因此我們也不用談什麼初階進階的東西。待會的Demo就直接從伺服器端把一組資料傳到前端,然後透過Vue.js做一個Binding(Template Rendering),以表格的方式把從伺服器端傳來的JSON資料呈現出來。
(若需要完整的CRUD請參考本文最後)
如果你想跟我一起 hands-on 一下,請依照底下的操作步驟做好前置準備,別忘了,我們用的是SPA架構,把以前你熟悉的asp.net WebForms或asp.net MVC都先給擱在一邊吧。
補充: 底下的範例是透過WebForms PageMethod來寫SPA,如果你想看WebAPI的SPA版本,請參考Source code,UI的部分請看index.html,同時原本PageMethod相對應的改成了透過WebAPI呼叫BO,請參考Controller與Health.cs,而程式碼的說明則建議您先看完這篇之後,再接著看下一篇(WebAPI版)。
- 請先建立一個最基本的asp.net 應用程式(請用Empty範本,並勾選WebAPI與WebForms),然後只需直接引入一個Nuget套件isRock.Framework.Web.AllPackages,它會幫你引入其他所有需要的Nuget套件:
- 接著,請在頁面上建立一個default.aspx (如果你偏好WebAPI版本的SPA,可參考這邊自行傳換成建立WebApi,或待會看下一篇):
- 準備工作先到這邊,接著我們來看如何進行Vue資料繫結。
基本概念
開工前我們扼要說一下基本概念,我們接下來要透過Vue.js做一個最基本的資料繫結,整個動作發生於前端,也就是瀏覽器上(這和Silverlight類似),但和過去asp.net WebForms發生在後端的資料繫結有所不同。
所以你必須先知道一件事情,這裡所謂的資料繫結,指的是瀏覽器內的HTML與Object的繫結。也就是,將『從伺服器端透過AJAX呼叫,被以JSON格式傳遞到瀏覽器之後的JavaScript Object』,與『HTML UI』Template之間的資料繫結。
而非像是過去的asp.net webforms或MVC一樣,在伺服器端進行的資料庫與UI Rendering之間的繫結。
大部分的SPA架構,前端顯示的資料都是從伺服器端以JSON的方式傳到前端,所以我們這裡的資料繫結大多談的都是資料被以JSON格式傳到前端瀏覽器上『之後』的資料繫結。
因此,你必須有幾個概念:
- UI上的資料不會『直接』與後端資料庫互動,而是在前端瀏覽器的記憶體內與HTML UI互動。
- 資料在繫結前,得先從伺服器端(以JSON格式)傳過來。
- 倘若是表單繫結,UI上的資料被用戶修改而有所異動,也只是反應回前端瀏覽器內(記憶體中)的Object,並非直接回寫資料庫。
暫時先知道這些就可以了。
要Demo什麼?
我們先來看,如何從伺服器端取得JSON資料,然後透過Vue.js的template動態Render到HTML上的範例(後面會再介紹表單中的資料雙向繫結)。好的,我們待會要做的事情是這樣,畫面上有一個類似底下這樣的UI:
當按下左上角Get Data按鈕的時候,會從伺服器端撈一堆資料,以JSON的格式傳到前端瀏覽器上,透過Vue.js進行DataBinding,呈現出底下的結果:
如果用戶按下上圖中的清空鈕,則表格資料會清空,就這樣。
如何使用Vue.js的template syntax做HTML動態rendering
好,知道要做什麼之後,我們實際來嘗試一下,請在剛才建立好的aspx頁面的CodeBehind程式碼中,建立底下這個Method :
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.UI; | |
using System.Web.UI.WebControls; | |
using isRock.Framework.PageMethods; | |
namespace WebApplication16 | |
{ | |
//往前端傳遞的資料結構,可以視為View Data | |
public class StudentInfo | |
{ | |
public string StudentName { get; set; } | |
public float Height{ get; set; } | |
public float Weight { get; set; } | |
public float BMIValue { get; set; } | |
public string Memo { get; set; } | |
} | |
public partial class _default : System.Web.UI.Page | |
{ | |
//可以被前端JavaScript呼叫的PageMethod | |
[System.Web.Services.WebMethod(enableSession: true)] | |
public static PageMethodDefaultResult<List<StudentInfo>> GetData() | |
{ | |
//準備假資料 | |
List<StudentInfo> returnData = new List<WebApplication16.StudentInfo>(); | |
returnData.Add(new WebApplication16.StudentInfo { StudentName = "王曉明", Height = 170, Weight = 72 }); | |
returnData.Add(new WebApplication16.StudentInfo { StudentName = "張曉春", Height = 180, Weight = 75 }); | |
returnData.Add(new WebApplication16.StudentInfo { StudentName = "田豐盛", Height = 175, Weight = 60 }); | |
returnData.Add(new WebApplication16.StudentInfo { StudentName = "楊明山", Height = 165, Weight = 90 }); | |
returnData.Add(new WebApplication16.StudentInfo { StudentName = "令狐衝", Height = 172, Weight = 80 }); | |
//計算BMI | |
foreach (var item in returnData) | |
{ | |
var height = item.Height / 100; | |
item.BMIValue = item.Weight / (height * height); | |
if (item.BMIValue > 30) | |
item.Memo = "過重!"; | |
if (item.BMIValue < 20) | |
item.Memo = "太瘦!"; | |
} | |
//回傳給前端JavaScript | |
return new PageMethodDefaultResult<List<StudentInfo>>() | |
{ isSuccess = true, Data = returnData }; | |
} | |
//由於我們雖然用WebForms,但不搞postback這一套,因此Page_Load暫時沒用 | |
protected void Page_Load(object sender, EventArgs e) | |
{ | |
} | |
} | |
} |
細看程式碼其實很簡單,28-33行就是準備假資料,如果是正式環境,我會從資料庫抓資料,但因為這只是一個範例,我們不要弄得太複雜。注意我們可以直接在同一個頁面上定義ViewData Class,當然也可以獨立做成一個專案或寫在同一個專案的BO(Business Logic Object)資料夾底下,但對於WebForms開發人員我們很包容,暫時先寫在同一個頁面上就好。
35行的迴圈是計算BMI值,最後在46行回傳給前端JavaScript,就這樣。別問我為何那麼簡單,因為 1.它本來就很簡單 2.上面範例用了我們開發的框架,大部分前端AJAX程式碼以及後端資料回傳的類別都包裝過了。
寫好後端C#,待會接著我們來看前端。
後端的PageMethod負責提供資料,留意上面程式碼25行該PageMethod的宣告以及回傳型別,是public static PageMethodDefaultResult<List<StudentInfo>> GetData() {…},由於框架提供的功能,你可以在前端透過底下這樣的JavaScript直接呼叫後端的GetData()這個PageMethod,作法像是底下這樣:
CallPageMethod('GetData', null,
//when success
function (result) {
vm_StudentInfo.items = result.Data;;
}
);
透過呼叫CallPageMethod這個function,即可執行伺服器端這個名為GetData的PageMethod(由於該Method不需要任何參數,因此上面第二個參數我們給null),並且在成功呼叫之後,透過callback function取得回傳值result,這個result就是剛才PageMethod的回傳物件PageMethodDefaultResult<List<StudentInfo>>,而result.Data則是PageMethod回傳物件中的List<StudentInfo>,也就是實際上我們在前端需要的ViewModel資料,也就是五筆學生的身高體重與BMI值。
那…這個…
vm_StudentInfo.items = result.Data;
這行程式碼什麼意思呢?別急,我們來看前端頁面上完整的程式碼:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="WebApplication16._default" %> | |
<!DOCTYPE html> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head runat="server"> | |
<title>asp.net WebForms SPA Vue Binding </title> | |
<script src="Scripts/jquery-1.9.1.min.js"></script> | |
<script src="Scripts/vue.min.js"></script> | |
<script src="Scripts/isRockFx.js"></script> | |
<link href="Content/bootstrap.min.css" rel="stylesheet" /> | |
<script> | |
var vd_StudentInfo; //view data | |
var vm_StudentInfo; //vue instance | |
//設定資料繫結 | |
function SetBinding() { | |
//create vue instance | |
vm_StudentInfo = new Vue( | |
{ | |
el: '#tableBody', | |
data: { items: vd_StudentInfo }, | |
}); | |
} | |
//從伺服器端取得資料 | |
function GetData() { | |
CallPageMethod('GetData', null, | |
//success | |
function (result) { | |
vm_StudentInfo.items = result.Data; | |
} | |
); | |
} | |
//清空 | |
function Clear() { | |
vm_StudentInfo.items = undefined; | |
} | |
//ready | |
$(function () { | |
//設定DataBinding | |
SetBinding(); | |
//設定Button Event Hook | |
$('#ButtonGetData').click(GetData); | |
$('#ButtonClear').click(Clear); | |
}); | |
</script> | |
</head> | |
<body> | |
<form id="form1" runat="server"> | |
<div class="row"> | |
<div class="col-md-12" style="margin: 10px"> | |
<div class="panel panel-primary"> | |
<div class="panel-heading"> | |
範例 : Vue.js 資料表前端Template Rendering (.aspx版) | |
</div> | |
<div class="panel-body"> | |
<button type="button" id="ButtonGetData" class="btn btn-primary">Get Data</button> | |
<button type="button" id="ButtonClear" class="btn btn-primary">清空</button> | |
</div> | |
<br /> | |
<!-- 表格開始 --> | |
<div style="margin: 10px" id="tableBody"> | |
<table class="table table-striped"> | |
<thead> | |
<tr> | |
<th style="min-width: 45px">姓名</th> | |
<th style="min-width: 45px">身高</th> | |
<th style="min-width: 45px">體重</th> | |
<th style="min-width: 45px">BMI</th> | |
<th style="min-width: 45px">備註</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr v-for="item in items"> | |
<td> | |
<span>{{item.StudentName}}</span> | |
</td> | |
<td> | |
<span>{{item.Height}}</span> | |
</td> | |
<td> | |
<span>{{item.Weight}}</span> | |
</td> | |
<td> | |
<span>{{item.BMIValue}}</span> | |
</td> | |
<td> | |
<span>{{item.Memo}}</span> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
</form> | |
</body> | |
</html> |
請注意UI的部分只有50行之後,UI進行Template Rendering的部分只有64-94行中間的表格部分,而8-11行則是引用我們會用到的js與css。整段程式碼看過之後,我們來看關鍵的幾個部分。
- 首先,該頁面一被載入完畢,41行的Document Ready會先被執行,請留意43行的SetBinding設定。後面45,46只是Hook Button的Click事件而已。
- 43行呼叫的SetBinding做了什麼呢? 我們看16-23行。整段內容非常簡單,就是透過Vue做一個資料繫結,繫結什麼? 誰跟誰繫結? 就是頁面上的HTML Template與瀏覽器記憶體中的物件vd_StudentInfo。其中20行的el與21行的data,告訴Vue.js的Rendering/Binding engine,當指向vd_StudentInfo這個記憶體物件的items有所異動時,要自動更新(Rendering)頁面上TableBody這一塊的內容。
- 我們看TableBody,也就是第64行到94行這一塊div :
- 由於上面的TableBody區塊,是透過{{…}}這樣的語法所設置了的HTML樣板(Template),因此,每當vm_StudentInfo中,指向vd_StudentInfo的這個data(也就是items)有所異動時,el區塊(也就是TableBody)就會被更新。你可能會疑惑兩件事情,(1) items什麼時候會被異動? 以及 (2) el區塊會被怎麼更新?
- 首先,請看45行的Button,他被按下時會觸發26行的GetData,透過CallPageMethod呼叫是伺服器端的GetData方法回傳學生資料JSON到前端,透過result.Data傳回,然後我們丟給binding_StudentInfo.items,對,就是這個動作,更新了items,這解答了上面的(1)這個問題。
- el區塊會被怎麼更新呢? 因為我們透過了Vue.js設定的Template語法(也就是那些{{…}}),因此,Vue.js Render Engine發現了items有所異動,就會去拿vd_StudentInfo中的值,更新指定的el區塊(tableBody),也就是64-94行的HTML樣板(Template)。
- 更新時,會把vd_StudentInfo帶入items,然後請特別注意76行的<tr v-for="item in items">,這一段Vue的v-for語法,意思是接下來的tr區塊(76-92行),會被重複顯示(Render),重複的次數看items這個array的個數決定…別忘了,items就是vd_StudentInfo,而vd_StudentInfo是從伺服器端傳來的List<StudentInfo>…
- 由於伺服器端傳來的資料有五筆,因此這個v-for跑了五次,每一次帶入一個StudentInfo item(留意78,81,84…這幾行的繫結語法{{item.屬性名稱}}),資料被帶入表格,呈現出底下的結果:
沒了,就這樣。
這個Template Rendering與過去XAML當中的Binding Engine作法幾乎完全相同。也就是,透過設定Template語法,上面js程式碼中的new Vue(…),把UI Element與記憶體中的Data Object做一個關聯(繫結),一旦繫結綁定了之後,一端有任何變化,另一端也會跟著變化(在表單中使用的話,可透過v-model設為雙向繫結,容後介紹),而搭配上{{…}}這樣的Template顯示語法,在HTML中可以輕易的帶入要呈現的集合資料或變數。
不僅僅語法非常簡潔,HTML Tag的破壞也極少,這是我喜歡Vue.js的原因,他讓SPA可以輕易實現前端的Template Rendering或Data Binding,並且讓設計師(Designer)與程式設計師(Developer)可以開心的合作。
所以…
目前Vue.js幾乎是我們現在開發SPA時,在前端的首選甚至是唯一選擇。上面這整段程式碼是非常常見的應用,只要稍微熟悉Template Rendering或Data Binding概念,可以很快的pick up整個Vue.js的語法與使用,建議沒有使用過的開發人員可以試試看…
後面,我會抽空繼續介紹更進一步的Binding方式,例如在表格中加上Button按鈕之類的…
source code : https://github.com/isdaviddong/AspNetWebFormsSpaVueBinding
Again: 上面是透過PageMethod來寫,如果你想看WebAPI的版本,請參考上面的程式碼,UI的部分請看index.html,PageMethod則改成了透過WebAPI呼叫BO,請參考Controller與Health.cs ,並接著看下一篇。
更完整的WebAPI版本CRUD Source Code:
https://github.com/isdaviddong/AspNetWithVueBinding
--------------------------------------------
如果需要即時取得更多相關訊息,可按這裡加入FB專頁。若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。
留言
讓我受益良多
hope helps. :)
我下載你這個範例實際在跑的時候,會跳例外錯誤
請技術員查看ex物件 object object 的錯誤
請問是什麼原因造成呢?
因為如果設定CssClass屬性的話也可以同樣套用到GridView上想要的外觀。
謝謝您