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架構。

  1. 請先建立一個基本的Empty Web Application專案,請記得勾選WebForms:
  2. 請安裝Bootstrap、Vue、isRock.Framework.Web(重點當然還是這個)這幾個nuget套件:
  3. 接著,請建立一個default.aspx WebForms頁面,並撰寫底下的前端程式碼,我建議讀者對照前面我們介紹過用WebAPI所撰寫的SPA,該專案的前端是純html頁面,而這個default.aspx除了附檔案是.aspx之外,也幾乎是純html頁面:

    請留意UI的部分是32-42行,這裡除了form tag之外幾乎都是純HTML,而上面的JavaScript和先前我們介紹過的WebAPI SPA也幾乎完全相同,唯一的差異是呼叫Service Layer API的JavaScript Function換成了CallPageMethod(注意19行),這是指要呼叫一個名為BMI的PageMethod(並且把身高體重para傳入當作參數)…
  4. 等等? PageMethod是啥玩意兒? 請開啟default.aspx.cs撰寫底下程式碼(注意9-20行):

    請讀者留意,這是一個WebForms的code behide頁面,我特意把Page_Load方法留了下來(雖然裡面沒有撰寫任何指令)。重點在於9-10行的PageMethod。PageMethod是asp.net中很特別的一個存在,它可以被前端HTML頁面上的JavaScript直接呼叫,並且回傳JSON…這…這行為…不就跟我們要的SPA一模一樣嗎? 是的,正是如此。
    至於這個名為BMI的Method,其中的指令碼應該不用多解釋,基本上就是跟先前介紹WebAPI版本SPA的Business Logic一樣,計算出BMI並且回傳。唯一的差別是回傳型別,這裡使用的是PageMethodDefaultResult<T>。
  5. 執行的結果如下,和WebAPI撰寫的SPA一模一樣,頁面不會submit,而是透過AJAX進行所有與伺服器端的互動:
  6. 等一下等一下,那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。
  7. 那…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有何好處?
  1. 首先,你不用拋棄原本的WebForms專案,卻可以用新的前端開發套件或效果(光這點就很吸引人)
    因為太多asp.net舊的專案還活得非常好,為了新的架構需要拋棄舊的專案,這得不償失,透過這樣的方式,新頁面可以跟舊頁面並存混搭,逐漸移轉,同時新頁面也可以享有新的前端套件(像是toastr、sweetAlert、眾多的前端js圖表特效…etc.)
  2. 讓開發團隊成員開始熟悉SPA
    SPA肯定會是未來的Web開發走向(好吧,我說了不賭未來,這個『肯定』請讀者自己判斷),不管以後用MVC、WebForms、或是任何其他開發方式,前後端分離的很徹底的SPA,都是較為漂亮的架構,團隊成員越早熟悉越好
  3. 和設計師(designer)可以密切合作(我們自己很在意這部分)
    如同先前透過WebAPI走SPA的方式相同,你會發現這樣設計出的網頁,設計師和程式開發人員可以持續合作,共同完成一個前端頁面,不會有設計師改過開發人員就跳腳,或開發人員改完設計師就罷工的情形出現…
  4. 所有前端套件都可以盡情使用
    Bootstrap,Vue…任何fancy的前端套件,在以往的WebForms專案中幾乎難以相容,走SPA架構的WebForms,跟任何前端套件都不互相衝突,讓開發人員能夠大展身手…
  5. 兼顧開發速度與後續維護
    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專頁。若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

留言

Jacky寫道…
請教若aspx有Master.Page那 /{PageName}.aspx/{MethodName}的Route應該是如何才對 ? 謝謝 !
匿名表示…
ORM技術真的有取代ADO.NET嗎?公司DBA還明訂專案不可以用EF,避免難以找到效能問題語句
RicoYang表示…
如果無法讓dba聽話,表示是自己功力不足

而且ORM也不是只有Entity Framework,有些ORM也還是讓你放純SQL,只是做data mapping
isDavid寫道…
@Liu Jacky,

不影響喔,我們在用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,並且維持對於物件導向程式設計的一致性。
阿雄寫道…
您好,我照了您的範例去做卻出現CallPageMethod這個方法不存在,是少了甚麼嗎?
isDavid寫道…
@阿雄,

我猜少了js的引用吧。
阿雄寫道…
您好,的確是少了引用哈哈
但我又出現一個問題,就是按下按鈕後都alert一個訊息:ERROR:OK
這是甚麼問題呢?
DarrenHsu寫道…
作者已經移除這則留言。
DarrenHsu寫道…
@大雄

我在實作時也發生了跟你一樣的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的方式去抓才行...才能另外設權限我想





DarrenHsu寫道…
大偉老師您好
我想請問一下, 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"}
DarrenHsu寫道…
不好意思找到解決方法了
參考http://no2don.blogspot.com/2016/08/aspnet-webservice-ajax-json.html
這篇的改了web.config

這個網誌中的熱門文章

使用 Airtable 在小型需求上取代傳統資料庫

精彩(且驚人)的Semantic Kernel入門範例

使用Semantic Kernel 建立自然語言請假系統

在 LINE Bot 開發中使用Semantic Kernel建立自然語言請假系統

專業的價值...