當前位置: 妍妍網 > 碼農

在 ASP.NET Core 中執行 WebForms 業務程式碼,99%相似度!

2024-05-12碼農

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%。

  1. Microsoft Teams' journey to .NET Core | .NET

  2. 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 傳入檢視檔:

1

2

3

4

5

6

7

8

9

10

11

12

13

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 中,我們可以直接獲取表格控制項進行數據繫結:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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的方式獲取:

1

2

3

4

5

< f:Button ID="btnSubmit" Css class="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,後台透過函式參數的方式接受:

1

2

3

4

5

public IActionResult OnPostBtnSubmit_Click( string userName, string password)

{

UIHelper.Label( "labResult" ).Text( "使用者名稱:" + userName + " 密碼:" + password);

return UIHelper.Result();

}

這個範例有兩個難點:

  1. 程式碼抽象不好理解:透過 UIHelper.Label函式拿到的控制項是一個在記憶體中新建的例項(其目的是為了向前台輸出一段改變標簽控制項文本的JavaScript指令碼),和頁面初始化時的那個Label控制項沒有任何關系。

  2. 不小心寫錯參數名稱的話,編譯不會報錯,執行時不能正確獲取傳入的參數值。

而在 WebForms中,可以直接在後台獲取控制項的內容,無需任何特殊處理:

1

2

< f:Button ID="btnSubmit" Css class="marginr" runat="server" OnClick="btnSubmit_Click" ValidateForms="SimpleForm1" Text="登入">

</ f:Button >

後台直接透過控制項例項的內容獲取,可以直接透過智慧提示快速輸入內容名稱,而且有編譯時提示:

1

2

3

4

protected void btnSubmit_Click( object sender, EventArgs e)

{

labResult.Text = "使用者名稱:" + tbxUserName.Text + " 密碼:" + tbxPassword.Text;

}

回發時數據處理方式不同

ASP.NET Core 中,後台更新表格數據需要一套單獨的程式碼(因為頁面初始化時使用ViewData進行數據傳遞,所以無法和回發時的數據繫結共用一套程式碼):

1

2

3

4

5

6

7

8

9

10

11

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中,頁面回發時重新繫結表格數據和頁面初始化時共用一套程式碼:

1

2

3

4

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,那就對了。 業務程式碼 9 9% 的相似度是實打實的,這也就為經典 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. 不管哪種技術,上述三個範例的數據傳輸都是 1 0KB之內,相比現在動輒10MB(大了1000 倍!)的數據傳輸,你覺得 WebForms數據傳輸量大的缺點還存在嗎?

7. 如何開啟 WebForms 開發模式?

首先確保你使用的是 ASP .NET Core RazorPages 開發模式,只需要如下兩個步驟即可在 FineUICore計畫中輕松開啟 WebForms 模式。

第一步:修改 appsettings.json 配置檔

1

2

3

4

5

6

7

8

9

{

"FineUI" : {

"EnableWebForms" : true ,

"DebugMode" : true ,

"Theme" : "Pure_Black" ,

"EnableAnimation" : true ,

"MobileAdaption" : true

}

}

第二步:修改 Startu p.cs 啟動檔

Config ureServices 函式中,增加 WebForms過濾器,如下所示。

1

2

3

4

5

6

7

8

9

10

11

12

// 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,可以對比學習Razor Pages WebForms,降低了學習成本,同時也讓之前購買FineUICore企業版的客戶享受到WebForms帶來的便利。

8. Page_Load 事件的回歸

在經典 WebForms頁面中,Page _Load 事件非常重要,也是大家耳熟能詳的,甚至在 2 0 年前 ASP .NET 1.0 釋出的時候,我們就是這麽寫程式碼的。

Page_Load 事件往往伴隨著對 Is PostBack 內容的判斷,因為 Page _Load 事件不管是頁面第一次載入,還是頁面回發都會執行。因此對於哪些只需要在頁面第一次載入的程式碼,就需要放到 !IsPostBack的邏輯判斷中。

R azorPages 中的 核取方塊列表的初始化

範例:https://pages.fineui.com/#/Form/CheckBoxList

ASP .NET Core RazorPages開發模式下,我們需要在OnGet中初始化數據,由於此時頁面檢視尚未初始化,因此我們無法知道頁面檢視上的任何定義。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public void OnGet()

{

LoadData();

}

private void LoadData()

{

List<Test class> myList = new List<Test class>();

myList.Add( new Test class( "1" , "數據繫結值 1" ));

myList.Add( new Test class( "2" , "數據繫結值 2" ));

myList.Add( new Test class( "3" , "數據繫結值 3" ));

myList.Add( new Test class( "4" , "數據繫結值 4" ));

ViewBag.CheckBoxList2DataSource = myList;

ViewBag.CheckBoxList2SelectedValueArray = new string [] { "1" , "3" };

}

將準備好的數據保存在 ViewData(自訂的ViewBag)中,然後傳入檢視檔,並在頁面視圖示簽中使用這些數據。

1

2

3

4

5

< 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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

protected void Page_Load( object sender, EventArgs e)

{

if (!IsPostBack)

{

LoadData();

}

}

private void LoadData()

{

List<Test class> myList = new List<Test class>();

myList.Add( new Test class( "1" , "數據繫結值 1" ));

myList.Add( new Test class( "2" , "數據繫結值 2" ));

myList.Add( new Test class( "3" , "數據繫結值 3" ));

myList.Add( new Test class( "4" , "數據繫結值 4" ));

CheckBoxList2.DataSource = myList;

CheckBoxList2.DataBind();

CheckBoxList2.SelectedValueArray = new string [] { "1" , "3" };

}

其中, IsPostBack內容定義在頁面模型基礎類別BaseModel .cs中:

1

2

3

4

5

6

7

public bool IsPostBack

{

get

{

return FineUICore.PageContext.IsFineUIAjaxPostBack();

}

}

註意:在 Page _Load 事件中,頁面檢視已經初始化完畢,因此我們可以直接呼叫頁面檢視上的控制項例項,比如這裏的 CheckBoxList 2 ,對應於頁面上的 CheckBoxList標簽定義。

1

2

3

< f:CheckBoxList ID="CheckBoxList2" Label="列表二(一列)" ColumnNumber="1"

DataTextField="Name" DataValueField="Id">

</ f:CheckBoxList >

小結

從上面範例中可以看出, WebForms模式下的頁面初始化更加直觀,等檢視檔初始化完畢後,直接獲取控制項例項,並設定控制項內容。反過來看RazorPages的實作就有點繁瑣了,必須透過ViewData進行中轉,先賦值,再使用,在頁面模型OnGet函式中無法獲取檢視中定義的變量。

9. 頁面回發事件( PostBack

簡化頁面回發事件的函式名

首先看下 RazorPages中的 按鈕點選 事件 ,:

1

2

3

4

< f:Button ID="btnChangeEnable" Text="啟用後面的按鈕"

OnClick="@Url.Handler("btnChangeEnable_Click")" />

< f:Button ID="btnEnable" Text="禁用的按鈕" OnClick="@Url.Handler("btnEnable_Click")"

Enabled="false" />

對應的後台事件處理器:

1

2

3

4

5

6

7

public IActionResult OnPostBtnChangeEnable_Click()

{

var btnEnable = UIHelper.Button( "btnEnable" );

btnEnable.Enabled( true );

btnEnable.Text( "本按鈕已經啟用(點選彈出對話方塊)" );

return UIHelper.Result();

}

在檢視檔中,定義了按鈕的點選事件名為 btn ChangeEnable_Click ,而後台對應的事件處理器名稱為 OnPostBtn ChangeEnable_Click 。由於前後台事件名稱的不一致,導致很多開發人員將後台事件名稱誤寫為 OnPost btnChangeEnable_Click,導致無法進入事件處理常式。

WebForms開發模式下,再看下相同的 範例:

1

2

3

4

< f:Button ID="btnChangeEnable" Text="啟用後面的按鈕"

OnClick="btnChangeEnable_Click" />

< f:Button ID="btnEnable" Text="禁用的按鈕" OnClick="btnEnable_Click"

Enabled="false" />

對應的後台處理常式名稱和前台的定義一模一樣:

1

2

3

4

5

protected void btnChangeEnable_Click( object sender, EventArgs e)

{

btnEnable.Enabled = true ;

btnEnable.Text = "本按鈕已經啟用(點選彈出對話方塊)" ;

}

除了事件名稱保持前後台一致,程式碼邏輯中已經完全移除 UIHelper的呼叫,我們可以直接呼叫控制項例項,修改例項內容(並非所有內容都可以在頁面回發中改變,我們將這些能夠在回發中改變的內容為AJAX內容,這個概念和經典FineUIPro保持一致)。

.......

小結

下面簡單總結一下 WebForms模式下回發事件和RazorPages中的不同之處:

  1. 檢視程式碼中無需將事件名稱置於 Url.Handler ()函式中。

  2. 檢視程式碼中無需編寫 JavaScript程式碼來獲取控制項狀態。

  3. 檢視中也無需設定 OnClickFields來向後台傳遞控制項狀態。

  4. 後台事件名稱和前台檢視定義的事件名稱完全一致。

  5. 事件處理常式的返回值是 void,因此無需返回UIHelper .Result()。

  6. 事件處理常式參數和經典的 WebForms保持一致,第一個參數是觸發事件的控制項例項,第二個是事件參數(比如表格分頁的事件參數型別為G ridPageEventArgs)。

  7. 事件處理常式中完全移除對 UIHelper的依賴(之前需要重建控制項例項,比如UIHelper .Button("btnEnable"))。

如果你是從經典的 ASP .NET WebForms 直接學習的 FineUICore(WebForms開發模式),忘記上面所有的不同,你只需要記著一點:FineUICore(WebForms模式)的事件處理和經典WebForms的事件處理 一模一樣