asp.net Web開發框架 (3) - 如何讓asp.net WebForms也能搭配Vue.js和bootstrap並享有SPA開發架構?
你沒聽錯,事實上asp.net WebForms也能走SPA(Single Page Application)的開發架構,並且跟在前端的Bootstrap與Vue.js框架搭配得很好。
技術的使用存乎一心
記得曾經對學員說過,不管你用哪一種開發方式,只要對開發技術有足夠的熟悉,並且瞭解基本原理與前因後果(再說一次,學習時,為何永遠比如何重要),那實在無需追逐潮流,你依舊可以在技術變遷中掌握到重點。
曾經聽過有些開發人員從WebForms轉換到MVC走了一條坎坷的路,並且轉換的理由很是讓人莞爾,這邊略過不提。但我想說的是,asp.net的MVC和WebForms走的是兩條截然不同的架構路徑,兩者並沒有取代性的問題,千萬別誤以為MVC主要目的就是用來取代WebForms用的。
順帶一提,那我認為什麼是真的有取代性的? 舉個例子,物件導向程式設計幾乎取代了結構化程式設計;資料庫存取的ORM技術,幾乎取代了ADO.NET等非ORM的資料存取技術;C#中的Linq取代了傳統的物件查找方式。這些,則是有取代性的…
所以,這一路上曾經聽過有些開發人員從WebForm轉換到MVC卻走了不少冤枉路,然後換到MVC之後卻用和傳統ASP一樣的思維在開發系統,這樣換比不換更慘。
如何在asp.net WebForms中實現SPA?
警語 : 接下來的內容很是驚悚,如果你對於asp.net WebForm或是MVC的『標準寫法』有著異於常人的堅持,不容妥協,那請自行斟酌,小心閱讀這篇。
還記得我們先前曾經說過的嗎?SPA對我們團隊來說的價值是『沒有任何從伺服器端Render HTML到前端的行為』,這麼做有哪些好處,在前面我們已經多次的提到過。
但整個WebForms的架構根本就是從伺服器端Render一堆的HTML到前端啊,這架構怎麼會符合SPA呢? 是的,這沒辦法,因為WebForms生來就不是走SPA路線的,在WebForms誕生的那個年代,根本還沒有SPA的影子。整個WebForms的行為都是在模擬傳統的Windows物件導向程式設計,因此任何的WebControls,舉凡Button、InputBox、DropdownList…全都是透過後端的HTML Render Engine動態產生的。
這導致,如果你用了任何WebControls,那根本不可能走SPA。所以,不要懷疑,如果你想得到SPA的種種好據,我們的第一個建議是,在專案中請用HTML Controls(加了runat=server的純HTML Tag)取代WebControls。甚至,你完全用純HTML Tag而不用任何的WebControls或HTMLControls我都覺得很OK。(是的,這代價不小,或失去一些WebForms的特性,但會保留MasterPage和ServerSite Events,請自行斟酌)
那這樣怎麼寫程式呢? 別急,請繼續看下去…
我們用實作來介紹這個特異獨行的WebForms SPA架構。
- 請先建立一個基本的Empty Web Application專案,請記得勾選WebForms:
- 請安裝Bootstrap、Vue、isRock.Framework.Web(重點當然還是這個)這幾個nuget套件:
- 接著,請建立一個default.aspx WebForms頁面,並撰寫底下的前端程式碼,我建議讀者對照前面我們介紹過用WebAPI所撰寫的SPA,該專案的前端是純html頁面,而這個default.aspx除了附檔案是.aspx之外,也幾乎是純html頁面:This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="WebFormsSPA._default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script src="Scripts/jquery-1.9.1.min.js"></script> <link href="Content/bootstrap.min.css" rel="stylesheet" /> <script src="Scripts/isRockFx.js"></script> <script> $(function () { $('#ButtonCal').click( function () { //取得用戶輸入的參數 var para = { 'height': $('#txbHeight').val(), 'weight': $('#txbWeight').val() }; //呼叫API CallPageMethod('BMI', para, function (result) { alert(result.Data); } ); } ); }); </script> </head> <body> <form id="form1" runat="server"> <div class="row" style="margin:10px"> <div class="col-md-12"> <div class="form-group"> 身高: <input id="txbHeight" class="form-control" placeholder="請輸入身高" /> 體重: <input id="txbWeight" class="form-control" placeholder="請輸入體重" /> <br /> <button type="button" class="btn btn-primary" id="ButtonCal">計算</button> </div> </div> </div> </form> </body> </html>
請留意UI的部分是32-42行,這裡除了form tag之外幾乎都是純HTML,而上面的JavaScript和先前我們介紹過的WebAPI SPA也幾乎完全相同,唯一的差異是呼叫Service Layer API的JavaScript Function換成了CallPageMethod(注意19行),這是指要呼叫一個名為BMI的PageMethod(並且把身高體重para傳入當作參數)… - 等等? PageMethod是啥玩意兒? 請開啟default.aspx.cs撰寫底下程式碼(注意9-20行):This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public partial class _default : System.Web.UI.Page { /// <summary> /// 計算BMI /// </summary> /// <param name="height">身高</param> /// <param name="weight">體重</param> /// <returns></returns> [System.Web.Services.WebMethod(enableSession: true)] public static PageMethodDefaultResult<float> BMI(float height, float weight) { height = (height) / 100; var bmi = weight / (height * height); var result = new PageMethodDefaultResult<float>() { Data = bmi, isSuccess = true }; return result; } protected void Page_Load(object sender, EventArgs e) { } }
請讀者留意,這是一個WebForms的code behide頁面,我特意把Page_Load方法留了下來(雖然裡面沒有撰寫任何指令)。重點在於9-10行的PageMethod。PageMethod是asp.net中很特別的一個存在,它可以被前端HTML頁面上的JavaScript直接呼叫,並且回傳JSON…這…這行為…不就跟我們要的SPA一模一樣嗎? 是的,正是如此。
至於這個名為BMI的Method,其中的指令碼應該不用多解釋,基本上就是跟先前介紹WebAPI版本SPA的Business Logic一樣,計算出BMI並且回傳。唯一的差別是回傳型別,這裡使用的是PageMethodDefaultResult<T>。 - 執行的結果如下,和WebAPI撰寫的SPA一模一樣,頁面不會submit,而是透過AJAX進行所有與伺服器端的互動:
- 等一下等一下,那Services Layer呢? 說好的Business Logic分離呢?
其實,這個PageMethod本身就是API形式的存在,你完全可以把他當作WebAPI一模一樣的方式來看待(也就是Service Layer),但差別在於PageMethod的Route(嚴格說起來是endpoint)是跟著.aspx頁面的,也就是說,每一個.aspx頁面上的PageMethod,其呼叫路徑跟WebApi的/api/ModuleName/{MethodName}不同,而是/{PageName}.aspx/{MethodName},而框架中寫的的javaScript Function 『CallPageMethod』則是幫開發人員簡化了這個呼叫,因此注意開發人員可以在default.aspx中透過javaScript(default.aspx第19行)直接呼叫CallPageMethod即可以AJAX方式調用這個伺服器端的API。 - 那…Business Logic呢? 不是應該抽離Service Layer嗎? 上面範例中的PageMethod(BMI)不是把商業邏輯給寫進去了?
是的,如果你堅持在PageMethod中,不要撰寫資料庫存取或是商業邏輯,把PageMethod中的程式碼改寫成和WebAPI中一樣(只呼叫另外一個Business Logic Component),我相當贊成。然而,這堅持很難,我們實務上也沒這麼做。這跟WebForms(.aspx)的原始設計行為有關。由於WebForms的每一個.aspx頁面本來就是為了呈現某種功能,因此相關的商業邏輯程式碼如果重用性不高,直接放在PageMethod中我覺得尚且可以接受,這個妥協並不會導致後續的維護和交接變得複雜(但最好放在同一個Region區塊中),這樣做等於某個頁面的所有相關程式碼,都在該頁面中,我覺得管理上甚至更為方便。
但,如果考慮到重用性,你確實應該跟先前WebAPI的範例一樣,額外建立一個BO,成為獨立的Class或Project,然後再在PageMethod中呼叫調用。這樣的架構更為完善,而且日後很容易可以轉變成為用WebAPI的SPA專案。但基於對傳統的.aspx開發人員來說,要養成這個習慣不容易,因此實務專案上這部分我沒有堅持。
OK,看到這邊有沒有發現,如果透過PageMethod,傳統WebForms專案要走SPA其實可能比asp.net MVC還來的方便,也容易理解。基本上就是透過前端頁面上的JavaScript呼叫同一個頁面上,撰寫成PageMethod的C#後端API, 然後忍住不用WebControls,不要Postback(Submit)就可以了。
每一個PageMethod也可以做Unit Test,將來抽出Business Logic Component(或當下就抽出Business Logic Component,在PageMethod中不要撰寫商業邏輯,只調用Business Logic Component)都非常容易。
這一個專案架構有一個好處,可以輕易的讓舊有的WebForms專案與新架構並存混用,不需要一次全部打掉翻掉,只需要讓新的功能透過新開發架構開發,逐漸把舊的WebForms轉換成SPA架構即可。
用WebForms走SPA有何好處?
- 首先,你不用拋棄原本的WebForms專案,卻可以用新的前端開發套件或效果(光這點就很吸引人)
因為太多asp.net舊的專案還活得非常好,為了新的架構需要拋棄舊的專案,這得不償失,透過這樣的方式,新頁面可以跟舊頁面並存混搭,逐漸移轉,同時新頁面也可以享有新的前端套件(像是toastr、sweetAlert、眾多的前端js圖表特效…etc.) - 讓開發團隊成員開始熟悉SPA
SPA肯定會是未來的Web開發走向(好吧,我說了不賭未來,這個『肯定』請讀者自己判斷),不管以後用MVC、WebForms、或是任何其他開發方式,前後端分離的很徹底的SPA,都是較為漂亮的架構,團隊成員越早熟悉越好 - 和設計師(designer)可以密切合作(我們自己很在意這部分)
如同先前透過WebAPI走SPA的方式相同,你會發現這樣設計出的網頁,設計師和程式開發人員可以持續合作,共同完成一個前端頁面,不會有設計師改過開發人員就跳腳,或開發人員改完設計師就罷工的情形出現… - 所有前端套件都可以盡情使用
Bootstrap,Vue…任何fancy的前端套件,在以往的WebForms專案中幾乎難以相容,走SPA架構的WebForms,跟任何前端套件都不互相衝突,讓開發人員能夠大展身手… - 兼顧開發速度與後續維護
WebForms先前最讓人詬病的部分,包含重複的postback影響執行效能(PageMethod的效能不可能比優化過的WebAPI好,但會比傳統的postback好很多)、臃腫的ViewState、WebControls動態生成的HTML難以維護,與前端js框架不相容…etc,在上面這樣的架構中完全不會發生,但開發人員卻又可以同時運用Page_Load, Page Exception等伺服器端事件,也可以輕易地透過MasterPage機制處理重複使用的頁面區塊,搭配上PageMethod可以很輕鬆的撰寫新型態的SPA Web應用程式,當你用熟了這種開發方式,你會發現在測試、維護、交接、執行效能上一點都不輸asp.net MVC,雖然比走WebAPI的SPA來得稍慢,但絕對是優於傳統的WebForms。
這種開發方式是為了與早期WebForms專案(與開發人員)兼容的一種變形,能夠在最少學習成本的狀況下,讓WebForms開發人員直接踏入SPA架構領域,非常有趣。
好玩嗎?
我知道很多開發人員在WebForms和MVC之間很是掙扎,但透過上面的範例,你不難發現,其實駕駛的好,AE86也能跑贏GT-R,不是嗎?
後記 : 還是得再說一次,每一種做法的背後都有著不同的需求與原因,無法放諸四海皆準,上述做法是一種折衷和妥協,也和很多人習慣的WebForms開發方式不見得相同,提供讀者參考,使用的情境必須與你的實際需求和系統環境有所搭配或取捨才行。
範例: https://github.com/isdaviddong/WebFormsSPA
口說無憑,要更多實例? 下一篇,立刻來看看如何配合Vue.js輕易完成Data Binding…
本系列索引http://studyhost.blogspot.tw/2017/01/blog-post.html
--------------------------------------------------------------------------------------------
如果需要即時取得更多相關訊息,可按這裡加入FB專頁。若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。
留言
而且ORM也不是只有Entity Framework,有些ORM也還是讓你放純SQL,只是做data mapping
不影響喔,我們在用master page的時候,PageMethod是寫在details page裡面的,因此route完全一樣,不建議把pagemethod寫在master page中。
@匿名
有些team堅持用傳統的ADO.NET,我也不反對,技術就是有利有弊,但我寧願犧牲效能以換取開發的便利性,此外,使用EF效能一定就不好,也不一定,因為team裡面總是會有人SQL寫得很爛,爛到效能很差的也有,而EF是優化的SQL,雖然多包了一層,但是否就一定會慢很多,也難說...
反倒是,我自己的團隊打死也不讓他們用SQL,一律用ORM原因在於看重安全性高於效能。有太多人隨手寫的SQL充滿著sql injection的可能...
@Rico,
的確,我喜歡ORM的重點也是data mapping和開發時候的intellisense,並且維持對於物件導向程式設計的一致性。
我猜少了js的引用吧。
但我又出現一個問題,就是按下按鈕後都alert一個訊息:ERROR:OK
這是甚麼問題呢?
我在實作時也發生了跟你一樣的ERROR:OK的問題
後來抽絲剝繭, 找到問題點, 提供我解決的方式給你
因為我有用到FriendlyUrls,
參考網路文章所述
https://dotblogs.com.tw/joysdw12/2014/01/29/asp-net-friendlyurls-ajax-webmethod
1.
需要把RouteConfig.cs中的settings.AutoRedirectMode 設為off
2.
在isRockFx.js這個js中
loc = (loc.substr(loc.length - 1, 1) == "/") ? loc + "default.aspx" : loc 這一行改為
loc = (loc.substr(loc.length - 1, 1) == "/") ? loc + "default.aspx" : loc + ".aspx"
我不確定這樣是不是最好最完整的改法, 但至少在原來的程式中這樣改是可以work的
3. 這是我自己的情境, 因為我是登入時用到這個page method, 我的專案有設form驗證
如果不設form驗證上面的解法就可以過了, 可是我專案必須要設form驗證
所以可能登入頁面我必須要用寫web api的方式去抓才行...才能另外設權限我想
我想請問一下, pagemethod如果設定為傳回一個json字串而不是List之類的
是不是會有長度限制, 因為我測試時傳回了一個超過一千筆的json字串
結果發生這樣的錯誤, 不知如何解決.....
看起來是isRockFx.js接收時就已經出錯了...
{"Message":"使用 JSON JavaScriptSerializer 序列化或還原序列化期間發生錯誤。字串的長度超過在 maxJsonLength 屬性上設定的值。","StackTrace":"
於 System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Object obj, StringBuilder output, SerializationFormat serializationFormat)\r\n 於 System.Web.Script.Serialization.JavaScriptSerializer.Serialize(Object obj, SerializationFormat serializationFormat)\r\n 於 System.Web.Script.Services.RestHandler.InvokeMethod(HttpContext context, WebServiceMethodData methodData, IDictionary`2 rawParams)\r\n 於 System.Web.Script.Services.RestHandler.ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)","ExceptionType":"System.InvalidOperationException"}
參考http://no2don.blogspot.com/2016/08/aspnet-webservice-ajax-json.html
這篇的改了web.config