【WebForms王者歸來】在 ASP.NET Core 中運行 WebForms 業務代碼,99%相似度!

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 傳入視圖文件:

 

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();
}

  

這個示例有兩個難點:

  1. 代碼抽象不好理解:通過UIHelper.Label函數拿到的控件是一個在內存中新建的實例(其目的是爲了向前臺輸出一段改變標籤控件文本的JavaScript腳本),和頁面初始化時的那個Label控件沒有任何關係。
  2. 不小心寫錯參數名稱的話,編譯不會報錯,運行時不能正確獲取傳入的參數值。

 

而在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 CoreWebForms開發模式)都是碾壓級的,綜合數據下載量比經典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,可以對比學習RazorPagesWebForms,降低了學習成本,同時也讓之前購買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中的不同之處:

  1. 視圖代碼中無需將事件名稱置於Url.Handler()函數中。
  2. 視圖代碼中無需編寫JavaScript代碼來獲取控件狀態。
  3. 視圖中也無需設置OnClickFields來向後臺傳遞控件狀態。
  4. 後臺事件名稱和前臺視圖定義的事件名稱完全一致。
  5. 事件處理函數的返回值是void,因此無需返回UIHelper.Result()。
  6. 事件處理函數參數和經典的WebForms保持一致,第一個參數是觸發事件的控件實例,第二個是事件參數(比如表格分頁的事件參數類型爲GridPageEventArgs)。
  7. 事件處理函數中完全移除對UIHelper的依賴(之前需要重建控件實例,比如UIHelper.Button("btnEnable"))。

 

 

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

 

 

FineUICore(WebForms開發模式)現已開放企業版試用申請!
 
請填寫如下資料發送到郵箱:[email protected]
================================= 
  • 產品名稱:FineUICore(WebForms開發模式)
  • 單位全稱:XXX單位
  • 申請人郵箱:XXX
  • 申請人QQ:XXX
  • 申請人姓名:XXX
  • 申請人地址:XX省XX市
=================================
 
 
全球首創,讓你 20 年前習得的編程技能再用 20 年!
 
 
 

 

下載完整技術白皮書

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章