1. 先說結論
我們為 ASP.NET Core 帶來了全新的 WebForms 開發模式,可以讓 20 年前的 WebForms 業務程式碼在最新的 ASP.NET Core 框架中執行,程式碼相似度99%!
一圖勝萬言!
2. 為什麼要升級到ASP.NET Core?
將十幾年依賴於 WebForms 和 .Net Framework 的專案移植到 ASP.NET Core 將是一項艱鉅的任務,特別是對於企業管理系統而言,數百個頁面可不是鬧著玩的。
經典WebForms已經不再更新
為什麼要遷移到 ASP.NET Core?
雖然 ASP.NET Core 非常優秀,但最根本的問題卻是 WebForms 已經不再更新。
隨著時間的推移,WebForms 專案將面臨越來越多的安全風險,因此容易受到攻擊,維護成本也會越來越高,因為想找到一個熟悉過時技術的開發人員也會越來越難。及時將自己的專案升級到最新的技術是減少系統風險的不二法門。
ASP.NET Core的效能好是公認的
值得一提的是,ASP.NET Core 效能好是公認的,有報導稱 Microsoft Teams 從 .NET Framework 4.6.2 遷移到 .NET Core 3.1,CPU 效能提升 25%。
- Microsoft Teams' journey to .NET Core | .NET
- OneService Journey to .NET 6 - .NET Blog
另有報導,ASP.NET Core效能已經 10 倍於 Node.js,甚至比 Go, C++, Java都要快。
How fast is ASP.NET Core?
小結
總的來說,ASP.NET Core足夠優秀來支撐這次升級:
1. ASP.NET Core開源免費(MIT),信創產品適用。
2. ASP.NET Core跨平臺,Linux、Windows、Mac都可以開發和執行。
3. 可以使用最新的 C# 特性,以及最新 VS 帶來的效率提升。
4. 更好的效能,意味著更快的訪問速度。
5. 更好的安全性。
3. 簡化開發工作,我們一直在努力!
為了減少大家從 WebForms 升級到最新的 ASP.NET Core 的工作量,我們一直在努力。
ASP.NET Core - MVC開發模式
2017-12-06,我們正式釋出了支援跨平臺開發和部署的FineUICore,此時只有經典的Model-View-Controller模式,並且前臺頁面是Razor函式的寫法。如果你當時要從FineUIPro升級到FineUICore,工作量還是蠻大的,來看下直觀的對比。
由於 ASP.NET Core Razor檢視的寫法和標籤的寫法完全不同,所以前臺程式碼的相似度幾乎為零!僅有部分後臺業務邏輯是一樣的。
ASP.NET Core - RazorPages開發模式
2019-06-20,我們推出了支援 Razor Pages 和 Tag Helpers的 FineUICore,可以方便的遷移之前的WebForms應用,這個版本儘量保證 .cshtml 檢視檔案和 WebForms 的 .aspx 的一致性,可以減輕升級的工作量。
我們專門寫了一篇文章詳細描述升級過程,可以參考:【FineUICore】全新ASP.NET Core,比WebForms還簡單! - 三生石上(FineUI控制元件) - 部落格園
ASP.NET Core - WebForms開發模式
2024年的今天,我們推出支援WebForms開發模式的 FineUICore,不僅可以做到前臺頁面的高度相似,而且後臺業務程式碼也可以做到99%的相似度。
小結
十幾年如一日,我們初心不變,始終恪守如下三個原則,為提升大家的開發體驗而不懈努力:
1. 一切為了簡單。
2. 用心實現 80% 的功能。
3. 創新所以獨一無二。
4. 為什麼引入 WebForms 開發模式?
自從 2019年推出支援 RazorPages 的FineUICore以來,我們不斷收到使用者反饋,吐槽 ASP.NET Core 的使用複雜,沒有之前的 WebForms好用。
我簡單總結了一下,有人吐糟傳遞引數麻煩,還要自己寫JavaScript程式碼;有人吐槽後臺程式碼的一致;還有人搞不清楚UIHelper該什麼時間使用,以及建立的控制元件和頁面上的控制元件例項有啥關係。
初始化資料的方式不同
在 ASP.NET Core 中,我們需要在 OnGet 函式中初始化資料,然後透過 ViewData 傳入檢視檔案:
public void OnGet() { LoadData(); } private void LoadData() { var recordCount = DataSourceUtil.GetTotalCount(); // 1.設定總項數(特別注意:資料庫分頁初始化時,一定要設定總記錄數RecordCount) ViewBag.Grid1RecordCount = recordCount; // 2.獲取當前分頁資料 ViewBag.Grid1DataSource = DataSourceUtil.GetPagedDataTable(pageIndex: 0, pageSize: 5); }
而在WebForms的 Page_Load 中,我們可以直接獲取表格控制元件進行資料繫結:
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { BindGrid(); } } private void BindGrid() { // 1.設定總項數(特別注意:資料庫分頁一定要設定總記錄數RecordCount) Grid1.RecordCount = GetTotalCount(); // 2.獲取當前分頁資料 DataTable table = GetPagedDataTable(Grid1.PageIndex, Grid1.PageSize); // 3.繫結到Grid Grid1.DataSource = table; Grid1.DataBind(); }
向後臺傳遞資料的方式不同
在 ASP.NET Core 中,所有後臺拿到的資料都需要在檢視程式碼中透過JavaScript的方式獲取:
<f:Button ID="btnSubmit" CssClass="marginr" ValidateForms="SimpleForm1" Text="登入" OnClick="@Url.Handler("btnSubmit_Click")" OnClickParameter1="@(new Parameter("userName", "F.ui.tbxUserName.getValue()"))" OnClickParameter2="@(new Parameter("password", "F.ui.tbxPassword.getValue()"))"> </f:Button>
比如這個示例向後臺傳遞了兩個引數userName和password,後臺透過函式引數的方式接受:
public IActionResult OnPostBtnSubmit_Click(string userName, string password) { UIHelper.Label("labResult").Text("使用者名稱:" + userName + " 密碼:" + password); return UIHelper.Result(); }
這個示例有兩個難點:
- 程式碼抽象不好理解:透過UIHelper.Label函式拿到的控制元件是一個在記憶體中新建的例項(其目的是為了向前臺輸出一段改變標籤控制元件文字的JavaScript指令碼),和頁面初始化時的那個Label控制元件沒有任何關係。
- 不小心寫錯引數名稱的話,編譯不會報錯,執行時不能正確獲取傳入的引數值。
而在WebForms中,可以直接在後臺獲取控制元件的屬性,無需任何特殊處理:
<f:Button ID="btnSubmit" CssClass="marginr" runat="server" OnClick="btnSubmit_Click" ValidateForms="SimpleForm1" Text="登入"> </f:Button>
後臺直接透過控制元件例項的屬性獲取,可以直接透過智慧提示快速輸入屬性名稱,而且有編譯時提示:
protected void btnSubmit_Click(object sender, EventArgs e) { labResult.Text = "使用者名稱:" + tbxUserName.Text + " 密碼:" + tbxPassword.Text; }
回發時資料處理方式不同
在 ASP.NET Core 中,後臺更新表格資料需要一套單獨的程式碼(因為頁面初始化時使用ViewData進行資料傳遞,所以無法和回發時的資料繫結共用一套程式碼):
public IActionResult OnPostGrid1_PageIndexChanged(string[] Grid1_fields, int Grid1_pageIndex) { var grid1 = UIHelper.Grid("Grid1"); var recordCount = DataSourceUtil.GetTotalCount(); // 1.設定總項數(資料庫分頁回發時,如果總記錄數不變,可以不設定RecordCount) grid1.RecordCount(recordCount); // 2.獲取當前分頁資料 var dataSource = DataSourceUtil.GetPagedDataTable(pageIndex: Grid1_pageIndex, pageSize: 5); grid1.DataSource(dataSource, Grid1_fields); return UIHelper.Result(); }
而在WebForms中,頁面回發時重新繫結表格資料和頁面初始化時共用一套程式碼:
protected void Grid1_PageIndexChange(object sender, GridPageEventArgs e) { BindGrid(); }
小結
經過前面的對比,我們能明顯感覺到WebForms的程式碼更加直觀,更加容易理解,並且WebForms的程式碼量更少,易於維護。
5. 全新WebForms開發模式(全球首創)
全球首創,實至名歸
為了解決上述問題,讓開發人員在享受 ASP.NET Core 免費開源跨平臺速度快的優點同時,還能擁有WebForms比較高的開發效率,我們為 ASP.NET Core 引入了 WebForms 模式。
截止目前,能真正將 WebForms 引入 ASP.NET Core 的控制元件庫廠商僅此一家,別無分店。我們也誠摯的邀請你來試用,相信你一定會喜歡這個全球首創的創新功能。
檢視檔案+頁面模型檔案+自動生成的設計時檔案
首先從一個最簡單的頁面入手,我們來看下啟用WebForms的 ASP.NET Core 到底是個什麼樣子?
一個簡單的模擬登入頁面,使用者輸入指定的使用者名稱和密碼之後,彈出登入成功提示框。
在 ASP.NET Core RazorPages專案中,我們需要新建一個頁面檔案以及後臺程式碼檔案(或者稱之為頁面模型):
注意,在登入按鈕的點選事件中,可以直接讀取輸入框的 tbxUserName 的 Text 屬性,這個就是 FineUICore 黑魔法,我們會將控制元件的一些關鍵屬性回發到後臺,並自動繫結到相應的控制元件例項。
而這個控制元件例項(tbxUserName)是在一個名為 Login.cshtml.designer.cs 檔案中宣告的,FineUICore會在頁面回發時自動初始化這個例項,並繫結關鍵屬性值。
注:我們會提供一個Visual Studio外掛自動生成這個檔案,無需開發人員手工編寫。
小結
如果上述程式碼讓你想起了20年前的WebForms,那就對了。業務程式碼99%的相似度是實打實的,這也就為經典WebForms的專案遷移到最新的ASP.NET Core奠定了紮實的基礎。
讓我們用工具對比下實現相同功能的經典WebForms和FineUICore(開啟WebForms模式)程式碼。
6. 哪些所謂的WebForms缺點怎麼辦?
WebForms的缺點已經不復存在!
20年前大家所詬病的WebForms的缺點之一(網路傳輸量大)已經不復存在,而WebForms的快速開發特性(Rapid Application Development - RAD)卻越來越重要。
有報告顯示,今天的主流網站的網頁過於臃腫,以至於嚴重影響瀏覽效能,而能流暢玩手遊《絕地求生》的入門級移動裝置甚至難以正常載入。Wix 每個網頁需要載入 21MB,Patreon 和 Threads 每個網頁需要載入 13MB 的資料。臃腫的網頁導致載入時間長達 33 秒,部分情況下甚至無法載入。基本上主流社交平臺都存在臃腫的問題。而內容建立平臺 Squarespace 和論壇 Discourse 的新版本通常比舊版本效能更差。
How web bloat impacts users with slow devices
WebForms需要在客戶端和服務端保持控制元件狀態,所以在頁面回發時,需要將頁面上所有控制元件的狀態資訊一併回發,導致比較大的網路傳輸。20年後的今天,隨著4G、5G行動網路的普及,以及充足的寬頻網路,這些流量已經變得不值一提。
WebForms是劃時代的技術,也可以看做是微軟的低程式碼解決方案,只不過20年前出來太超前了,受制於網路傳輸頻寬的限制,所以才為大家所詬病。現在回頭看看,每次頁面回發時多傳輸10K資料算個事嗎?想想你刷一個抖音影片怎麼說也要消耗10M(10,240K)流量吧。而WebForms帶來的開發效率提升,以及後期節約的維護成本,則是實實在在的好處,真金白金看得見摸得著。
實測WebForms的資料傳輸量
我猜測大家估計還是心有不甘,雖然多點資料傳輸能提高開發效率,減少我們寫的程式碼量,提高可維護性。但是成年人的世界既要、又要還要,能少傳輸點資料豈不是更妙。
帶著這個疑問,我們來對比下FineUICore(RazorPages)、FineUIPro(經典WebForms)和FineUICore(WebForms開發模式)下傳輸的資料量,爭取讓大家用的心情舒暢。
示例一:表格的資料庫分頁與排序
示例二:省市縣聯動
示例三:樹控制元件延遲載入
注:上述表格中數字表示網路資料傳輸量,單位KB。
經過上述三個頁面對比,我們可以看出,經典WebForms不管是頁面第一次載入,還是回發時上傳和下載的資料量都是最大的。
小結
1. 相比經典WebForms,不管是頁面第一次載入,還是回發時的資料傳輸量,ASP.NET Core(WebForms開發模式)都是碾壓級的,綜合資料下載量比經典WebForms減少 50% 左右。
2. 與資料傳輸量最少的ASP.NET Core RazorPages相比,啟用WebForms時,只有在頁面回發時上傳資料量有所增加,而頁面第一次載入和回發時的下載資料量兩者保持一致。
3. 不管哪種技術,上述三個示例的資料傳輸都是10KB之內,相比現在動輒10MB(大了1000倍!)的資料傳輸,你覺得WebForms資料傳輸量大的缺點還存在嗎?
7. 如何開啟WebForms開發模式?
首先確保你使用的是ASP.NET Core RazorPages 開發模式,只需要如下兩個步驟即可在FineUICore專案中輕鬆開啟 WebForms 模式。
第一步:修改appsettings.json配置檔案
{ "FineUI": { "EnableWebForms": true, "DebugMode": true, "Theme": "Pure_Black", "EnableAnimation": true, "MobileAdaption": true } }
第二步:修改 Startup.cs啟動檔案
在 ConfigureServices 函式中,增加 WebForms過濾器,如下所示。
// FineUI 服務 services.AddFineUI(Configuration); services.AddRazorPages().AddMvcOptions(options => { // 自定義JSON模型繫結(新增到最開始的位置) options.ModelBinderProviders.Insert(0, new FineUICore.JsonModelBinderProvider()); // 自定義WebForms過濾器(僅在啟用EnableWebForms時有效) options.Filters.Insert(0, new FineUICore.WebFormsFilter()); }).AddNewtonsoftJson().AddRazorRuntimeCompilation();
搞定!
小結
深度整合到FineUICore中,僅僅透過一個引數來控制是否開啟WebForms,可以對比學習RazorPages和WebForms,降低了學習成本,同時也讓之前購買FineUICore企業版的客戶享受到WebForms帶來的便利。
8. Page_Load事件的迴歸
在經典WebForms頁面中,Page_Load事件非常重要,也是大家耳熟能詳的,甚至在20年前ASP.NET 1.0 釋出的時候,我們就是這麼寫程式碼的。
Page_Load事件往往伴隨著對IsPostBack屬性的判斷,因為Page_Load事件不管是頁面第一次載入,還是頁面回發都會執行。因此對於哪些只需要在頁面第一次載入的程式碼,就需要放到 !IsPostBack的邏輯判斷中。
RazorPages中的核取方塊列表的初始化
示例:https://pages.fineui.com/#/Form/CheckBoxList
在ASP.NET Core RazorPages開發模式下,我們需要在OnGet中初始化資料,由於此時頁面檢視尚未初始化,因此我們無法知道頁面檢視上的任何定義。
public void OnGet() { LoadData(); } private void LoadData() { List<TestClass> myList = new List<TestClass>(); myList.Add(new TestClass("1", "資料繫結值 1")); myList.Add(new TestClass("2", "資料繫結值 2")); myList.Add(new TestClass("3", "資料繫結值 3")); myList.Add(new TestClass("4", "資料繫結值 4")); ViewBag.CheckBoxList2DataSource = myList; ViewBag.CheckBoxList2SelectedValueArray = new string[] { "1", "3" }; }
將準備好的資料儲存在ViewData(自定義的ViewBag)中,然後傳入檢視檔案,並在頁面檢視標籤中使用這些資料。
<f:CheckBoxList ID="CheckBoxList2" Label="列表二(一列)" ColumnNumber="1" DataTextField="Name" DataValueField="Id" DataSource="@ViewBag.CheckBoxList2DataSource" SelectedValueArray="@ViewBag.CheckBoxList2SelectedValueArray"> </f:CheckBoxList>
WebForms核取方塊列表的初始化
示例:https://forms.fineui.com/#/Form/CheckBoxList
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { LoadData(); } } private void LoadData() { List<TestClass> myList = new List<TestClass>(); myList.Add(new TestClass("1", "資料繫結值 1")); myList.Add(new TestClass("2", "資料繫結值 2")); myList.Add(new TestClass("3", "資料繫結值 3")); myList.Add(new TestClass("4", "資料繫結值 4")); CheckBoxList2.DataSource = myList; CheckBoxList2.DataBind(); CheckBoxList2.SelectedValueArray = new string[] { "1", "3" }; }
其中,IsPostBack屬性定義在頁面模型基類BaseModel.cs中:
public bool IsPostBack { get { return FineUICore.PageContext.IsFineUIAjaxPostBack(); } }
注意:在Page_Load事件中,頁面檢視已經初始化完畢,因此我們可以直接呼叫頁面檢視上的控制元件例項,比如這裡的CheckBoxList2,對應於頁面上的CheckBoxList標籤定義。
<f:CheckBoxList ID="CheckBoxList2" Label="列表二(一列)" ColumnNumber="1" DataTextField="Name" DataValueField="Id"> </f:CheckBoxList>
小結
從上面示例中可以看出,WebForms模式下的頁面初始化更加直觀,等檢視檔案初始化完畢後,直接獲取控制元件例項,並設定控制元件屬性。反過來看RazorPages的實現就有點繁瑣了,必須透過ViewData進行中轉,先賦值,再使用,在頁面模型OnGet函式中無法獲取檢視中定義的變數。
9. 頁面回發事件(PostBack)
簡化頁面回發事件的函式名
首先看下RazorPages中的按鈕點選事件,:
<f:Button ID="btnChangeEnable" Text="啟用後面的按鈕" OnClick="@Url.Handler("btnChangeEnable_Click")" /> <f:Button ID="btnEnable" Text="禁用的按鈕" OnClick="@Url.Handler("btnEnable_Click")" Enabled="false" />
對應的後臺事件處理器:
public IActionResult OnPostBtnChangeEnable_Click() { var btnEnable = UIHelper.Button("btnEnable"); btnEnable.Enabled(true); btnEnable.Text("本按鈕已經啟用(點選彈出對話方塊)"); return UIHelper.Result(); }
在檢視檔案中,定義了按鈕的點選事件名為btnChangeEnable_Click,而後臺對應的事件處理器名稱為OnPostBtnChangeEnable_Click。由於前後臺事件名稱的不一致,導致很多開發人員將後臺事件名稱誤寫為OnPostbtnChangeEnable_Click,導致無法進入事件處理函式。
而WebForms開發模式下,再看下相同的示例:
<f:Button ID="btnChangeEnable" Text="啟用後面的按鈕" OnClick="btnChangeEnable_Click" /> <f:Button ID="btnEnable" Text="禁用的按鈕" OnClick="btnEnable_Click" Enabled="false" />
對應的後臺處理函式名稱和前臺的定義一模一樣:
protected void btnChangeEnable_Click(object sender, EventArgs e) { btnEnable.Enabled = true; btnEnable.Text = "本按鈕已經啟用(點選彈出對話方塊)"; }
除了事件名稱保持前後臺一致,程式碼邏輯中已經完全移除UIHelper的呼叫,我們可以直接呼叫控制元件例項,修改例項屬性(並非所有屬性都可以在頁面回發中改變,我們將這些能夠在回發中改變的屬性為AJAX屬性,這個概念和經典FineUIPro保持一致)。
.......
小結
下面簡單總結一下WebForms模式下回發事件和RazorPages中的不同之處:
- 檢視程式碼中無需將事件名稱置於Url.Handler()函式中。
- 檢視程式碼中無需編寫JavaScript程式碼來獲取控制元件狀態。
- 檢視中也無需設定OnClickFields來向後臺傳遞控制元件狀態。
- 後臺事件名稱和前臺檢視定義的事件名稱完全一致。
- 事件處理函式的返回值是void,因此無需返回UIHelper.Result()。
- 事件處理函式引數和經典的WebForms保持一致,第一個引數是觸發事件的控制元件例項,第二個是事件引數(比如表格分頁的事件引數型別為GridPageEventArgs)。
- 事件處理函式中完全移除對UIHelper的依賴(之前需要重建控制元件例項,比如UIHelper.Button("btnEnable"))。
如果你是從經典的 ASP.NET WebForms直接學習的FineUICore(WebForms開發模式),忘記上面所有的不同,你只需要記著一點:FineUICore(WebForms模式)的事件處理和經典WebForms的事件處理一模一樣!
- 產品名稱:FineUICore(WebForms開發模式)
- 單位全稱:XXX單位
- 申請人郵箱:XXX
- 申請人QQ:XXX
- 申請人姓名:XXX
- 申請人地址:XX省XX市
下載完整技術白皮書