使用 C# 和 Blazor 进行全栈开发
Blazor 是將 C# 引入瀏覽器的 Microsoft 試驗框架,正好可以填補欠缺的 C# 一環。如今,C# 程序員可以編寫桌面、服務器端 Web、云、電話、平板電腦、手表、電視和 IoT 應用程序。
Blazor 填補了欠缺的一環,C# 開發人員現在可以直接在用戶瀏覽器中共享代碼和業務邏輯。對于 C# 開發人員來說,這是一項十分強大的功能,可顯著提升工作效率。
本文將展示常見的代碼共享用例。我將展示如何在 Blazor 客戶端和 WebAPI 服務器應用程序之間共享驗證邏輯。目前,你不僅要在服務器中驗證輸入,還要在客戶端瀏覽器中驗證輸入。新式 Web 應用程序的用戶希望獲得準實時反饋。在填寫長窗體并單擊“提交”后僅看到紅色錯誤返回的日子已經一去不復返了。
在瀏覽器中運行的 Blazor Web 應用程序可以與 C# 后端服務器共享代碼。可以將邏輯放入共享庫中,并在前端和后端使用它。這會帶來很多好處。
可以將所有規則都集中放置在一處,并知道只需在一處更新它們。它們的工作方式確實相同,因為它們是相同的代碼。
在客戶端和服務器邏輯并不總是完全相同的情況下,可以節省大量測試和故障排除時間。
也許最值得一提的是,可以在客戶端和服務器上使用一個庫進行驗證。
以前,JavaScript 前端強制開發人員編寫兩個版本的驗證規則:一個是用適用于前端的 JavaScript 編寫,另一個是用適用于后端的語言編寫。若要嘗試解決這種不匹配問題,需要涉及復雜的規則框架和額外的抽象層。使用 Blazor,可以在客戶端和服務器上運行同一.NET Core 庫。
雖然 Blazor 仍是試驗框架,但它的進展迅速。生成此示例前,請先確保已安裝正確版本的 Visual Studio、.NET Core SDK 和 Blazor 語言服務。有關入門步驟,請訪問 blazor.net。
新建 Blazor 應用程序
首先,新建 Blazor 應用程序。
在“新建項目”對話框中,依次單擊“ASP.NET Core Web 應用程序”和“確定”,再選擇圖 1 所示對話框中的“Blazor”圖標。單擊“確定”。
這會創建默認的 Blazor 示例應用程序。如果已試用過 Blazer,便會對此默認應用程序很熟悉。
圖 1:選擇 Blazor 應用程序
新的注冊窗體將展示驗證業務規則的共享邏輯。圖 2 展示了包含“名字”、“姓氏”、“電子郵件地址”和“電話”字段的簡單窗體。
在此示例中,它會驗證所有字段是否都為必填、姓名字段是否有長度上限,以及電子郵件地址和電話字段的格式是否正確。它會在每個字段下顯示錯誤消息,這些消息會在用戶鍵入內容的同時更新。最后,只有在沒有錯誤的情況下,“注冊”按鈕才處于啟用狀態。
圖 2:注冊窗體
共享庫
所有需要在服務器和 Blazor 客戶端之間共享的代碼都位于一個獨立的共享庫項目中。共享庫包含模型類和非常簡單的驗證引擎。模型類保留注冊窗體中的數據字段。該命令如下所示:
{
[RequiredRule]
[MaxLengthRule(50)]
public String FirstName { get; set; }
[RequiredRule]
[MaxLengthRule(50)]
public String LastName { get; set; }
[EmailRule]
public String Email { get; set; }
[PhoneRule]
public String Phone { get; set; }
}
RegistrationData 類繼承自 ModelBase 類,后者包含所有可用于驗證規則并返回綁定到 Blazor 頁面的錯誤消息的邏輯。每個字段都使用映射到驗證規則的屬性進行修飾。我選擇了創建非常簡單的模型,它很像實體框架 (EF) 數據注釋模型。此模型的所有邏輯都包含在共享庫中。
ModelBase 類包含 Blazor 客戶端應用程序或服務器應用程序可用來確定是否有任何驗證錯誤的方法。它還會在此模型更改時觸發事件,以便客戶端能夠更新 UI。任何模型類都可以繼承自它,并自動獲取所有驗證引擎邏輯。
首先,我將在 SharedLibrary 項目中新建 ModelBase 類,如下所示:
{
}
錯誤和規則
現在,我將向 ModelBase 類添加包含驗證錯誤列表的專用字典。_errors 字典先以字段名稱為鍵,再以規則名稱為鍵。值是要顯示的實際錯誤消息。
通過此設置,可以輕松確定特定字段是否有驗證錯誤,并快速檢索錯誤消息。
代碼如下:
new Dictionary<string, Dictionary<string, string>>();
現在,我將添加 AddError 方法,以將錯誤輸入內部錯誤字典。
AddError 有 fieldName、ruleName 和 errorText 參數。它先搜索內部錯誤字典,并刪除已有條目,再添加新的錯誤條目,如下面的代碼所示:
{
if?(!_errors.ContainsKey(fieldName))?{?_errors.Add(fieldName,
new Dictionary<string, string>()); }
if (_errors[fieldName].ContainsKey(ruleName))
{ _errors[fieldName].Remove(ruleName); }
_errors[fieldName].Add(ruleName, errorText);
OnModelChanged();
}
最后,我將添加 RemoveError 方法,它接受 fieldName 和 ruleName 參數,并在內部錯誤字典中搜索并刪除匹配的錯誤。代碼如下:
{
if?(!_errors.ContainsKey(fieldName))?{?_errors.Add(fieldName,
new Dictionary<string, string>()); }
if (_errors[fieldName].ContainsKey(ruleName))
{ _errors[fieldName].Remove(ruleName);
OnModelChanged();
}
}
下一步是添加 CheckRules 函數,這些函數負責查找并執行附加到此模型的驗證規則。有兩種不同的 CheckRules 函數:一種是缺少參數,但對所有字段驗證全部規則;另一種有 fieldName 參數,并僅驗證特定字段。在字段更新時,使用的是第二種函數,并立即對此字段驗證規則。
CheckRules 函數使用反射來查找附加到字段的屬性列表。然后,它測試每個屬性,以確定屬性類型是否為 IModelRule。找到 IModelRule 后,它調用 Validate 方法,并返回結果,如圖 3 所示。
圖 3:CheckRules 函數
{
var propertyInfo = this.GetType().GetProperty(fieldName);
var attrInfos = propertyInfo.GetCustomAttributes(true);
foreach (var attrInfo in attrInfos)
{
if (attrInfo is IModelRule modelrule)
{
var value = propertyInfo.GetValue(this);
var result = modelrule.Validate(fieldName, value);
if (result.IsValid)
{
RemoveError(fieldName, attrInfo.GetType().Name);
}
else
{
AddError(fieldName, attrInfo.GetType().Name, result.Message);
}
}
}
}
public bool CheckRules()
{
foreach (var propInfo in this.GetType().GetProperties(
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance))
CheckRules(propInfo.Name);
return HasErrors();
}
接下來,我將添加 Errors 函數。此函數需要使用 fieldname 參數,并返回包含相應字段的錯誤列表的字符串。它使用內部 _errors 字典來確定相應字段是否有任何錯誤,如下所示:
{
if?(!_errors.ContainsKey(fieldName))?{?_errors.Add(fieldName,
new Dictionary<string, string>()); }
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (var value in _errors[fieldName].Values)
sb.AppendLine(value);
return sb.ToString();
}
現在,我需要添加 HasErrors 函數,它會在此模型的任意字段有任何錯誤時返回 true。客戶端使用此方法來確定是否應啟用“注冊”按鈕。
另外,WebAPI 服務器也使用此方法來確定傳入的模型數據是否有錯誤。此函數的代碼如下:
{
foreach (var key in _errors.Keys)
if (_errors[key].Keys.Count > 0) { return true; }
return false;
}
值和事件
是時候添加 GetValue 方法了,它需要使用 fieldname 參數,并使用反射來查找此模型中的字段并返回字段值。Blazor 客戶端使用此方法來檢索當前值,并在輸入框中顯示它,如下所示:
{
var propertyInfo = this.GetType().GetProperty(fieldName);
var value = propertyInfo.GetValue(this);
if (value?!=?null) { return value.ToString(); }
return String.Empty;
}
現在,添加 SetValue 方法。它使用反射來查找此模型中的字段,并更新字段值。然后,它觸發 CheckRules 方法,以對相應字段驗證所有規則。Blazor 客戶端使用此方法,以在用戶在輸入文本框中鍵入內容的同時更新值。代碼如下:
{
var propertyInfo = this.GetType().GetProperty(fieldName);
propertyInfo.SetValue(this, value);
CheckRules(fieldName);
}
最后,我添加 ModelChanged 事件。如果此模型中的值已更改或在內部錯誤字典中添加或刪除了驗證規則,便會觸發這個事件。Blazor 客戶端偵聽此事件,并在事件觸發時更新 UI。正因為此,顯示的錯誤會更新,如下面的代碼所示:
protected void OnModelChanged()
{
??ModelChanged?.Invoke(this, new EventArgs());
}
得承認此驗證引擎的設計非常簡單,還有很多改進機會。在生產業務應用程序中,設置錯誤的嚴重性級別(如“信息”、“警告”和“錯誤”)會很有用。在某些情況下,如果無需修改代碼,即可從配置文件動態加載規則,將會很有幫助。我不是在提倡創建你自己的驗證引擎;只是有很多選擇。此驗證引擎既要足夠好,以便演示實際示例;又要足夠簡單,以適應本文且易于理解。
創建規則
此時,有包含窗體字段的 RegistrationData 類。
此類中的字段使用 RequiredRule 和 EmailRule 等屬性進行修飾。
RegistrationData 類繼承自 ModelBase 類,后者包含所有用于驗證規則并向客戶端通知更改的邏輯。驗證引擎的最后一部分是規則邏輯本身。接下來,我將對此進行探索。
首先,我在 SharedLibrary 中新建 IModelRule 類。
此規則由一個返回 ValidationResult 的 Validate 方法組成。每個規則都必須實現 IModelRule 接口,如下所示:
{
ValidationResult Validate(String fieldName, object fieldValue);
}
接下來,我在 SharedLibrary 中新建 ValidationResult 類,它由兩個字段組成。IsValid 字段指明規則是否有效,而 Message 字段則包含要在規則無效時顯示的錯誤消息。代碼如下所示:
{
public bool IsValid { get; set; }
public String Message { get; set; }
}
示例應用程序使用四個不同的規則,所有規則都是繼承自 Attribute 類并實現 IModelRule 接口的公共類。
現在,是時候創建規則了。請注意,所有驗證規則都只是繼承自 Attribute 類并實現 IModelRule 接口的 Validate 方法的類。如果輸入的文本超過指定的長度上限,圖 4 中的長度上限規則返回錯誤。其他用于驗證必填字段、電話和電子郵件地址字段格式的規則的工作方式類似,區別在于它們對要驗證的數據類型采用不同的邏輯。
圖 4:MaxLengthRule 類
{
private int _maxLength = 0;
public MaxLengthRule(int maxLength) { _maxLength = maxLength; }
public ValidationResult Validate(string fieldName, object fieldValue)
{
var message = $"Cannot be longer than {_maxLength} characters";
if (fieldValue == null) { return new ValidationResult() { IsValid = true }; }
var stringvalue = fieldValue.ToString();
if (stringvalue.Length > _maxLength )
{
return new ValidationResult() { IsValid = false, Message = message };
}
else
{
return new ValidationResult() { IsValid = true };
}
}
}
創建 Blazor 注冊窗體
至此,驗證引擎已在共享庫中完成,它可以應用于 Blazor 應用程序中的新注冊窗體。首先,我在 Blazor 應用程序中添加對共享庫項目的引用。為此,可使用“引用管理器”對話框的“解決方案”窗口,如圖 5 所示。
圖 5:添加對共享庫的引用
接下來,我向應用程序的 NavMenu 添加新導航鏈接。
打開SharedNavMenu.cshtml 文件,并向列表添加新注冊窗體鏈接如圖 6 所示。
圖 6:添加注冊窗體鏈接
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match=NavLinkMatch.All>
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="registrationform">
<span class="oi oi-list-rich" aria-hidden="true"></span> Registration Form
</NavLink>
</li>
</ul>
</div>
最后,我在 Pages 文件夾中添加新 RegistrationForm.cshtml 文件。為此,可使用圖 7 中的代碼。
圖 7 中的 cshtml 代碼在 <form> 標記內有四個 <TextInput> 字段。<TextInput> 標記是自定義 Blazor 組件,用于處理字段的數據綁定和錯誤顯示邏輯。此組件只需要三個參數即可正常運行:
Model 字段:標識數據要綁定到的類。
FieldName:標識數據要綁定到的數據成員。
DisplayName 字段:讓組件可以顯示易記消息。
圖 7:添加 RegistrationForm.cshtml 文件
@inject HttpClient Http
@using SharedLibrary
<h1>Registration Form</h1>
@if?(!registrationComplete)
{
<form>
<div class="form-group">
<TextInput Model="model" FieldName="FirstName" DisplayName="First Name" />
</div>
<div class="form-group">
<TextInput Model="model" FieldName="LastName" DisplayName="Last Name" />
</div>
<div class="form-group">
<TextInput Model="model" FieldName="Email" DisplayName="Email" />
</div>
<div class="form-group">
<TextInput Model="model" FieldName="Phone" DisplayName="Phone" />
</div>
<button type="button" class="btn btn-primary" onclick="@Register"
disabled="@model.HasErrors()">Register</button>
</form>
}
else
{
??<h2>Registration?Complete!</h2>
}
@functions {
bool registrationComplete = false;
RegistrationData model { get; set; }
protected override void OnInit()
{
base.OnInit();
model = new RegistrationData() { FirstName =
"test", LastName = "test", Email = "test@test.com", Phone = "1234567890" };
model.ModelChanged += ModelChanged;
model.CheckRules();
}
private void ModelChanged(object sender, EventArgs e)
{
base.StateHasChanged();
}
async Task Register()
{
await Http.PostJsonAsync<RegistrationData>(
"https://localhost:44332/api/Registration", model);
registrationComplete = true;
}
}
在頁面的 @functions 塊內,代碼只有一點點。OnInit 方法使用其中的一些測試數據來初始化模型類。
它綁定到 ModelChanged 事件,并調用 CheckRules 方法來驗證規則。
ModelChanged 處理程序調用 base.StateHasChanged 方法,以強制執行 UI 刷新。Register 方法在“注冊”按鈕獲得單擊時調用,并將注冊數據發送到后端WebAPI 服務。
TextInput 組件包含輸入標簽、輸入文本框、驗證錯誤消息,以及在用戶鍵入內容的同時更新模型的邏輯。Blazor 組件非常易于編寫,并提供了將接口分解為可重用部分的強大方法。參數成員使用 Parameter 屬性進行修飾,以便讓 Blazor 知道它們是組件參數。
輸入文本框的 oninput 事件連接到 OnFieldChanged 處理程序。每當輸入更改,都會觸發此事件。然后,OnFieldChanged 處理程序調用 SetValue 方法,以對相應字段執行規則,并在用戶鍵入內容的同時實時更新錯誤消息。圖 8 展示了代碼。
圖 8:更新錯誤消息
<label>@DisplayName</label>
<input type="text" class="form-control" placeholder="@DisplayName"
oninput="@(e => OnFieldChanged(e.Value))"
value="@Model.GetValue(FieldName)" />
<small class="form-text" style="color:darkred;">@Model.Errors(FieldName)
</small>
@functions {
[Parameter]
ModelBase Model { get; set; }
[Parameter]
String FieldName { get; set; }
[Parameter]
String DisplayName { get; set; }
public void OnFieldChanged(object value)
{
Model.SetValue(FieldName, value);
}
}
服務器上的驗證
驗證引擎現已開始在客戶端上運行。下一步是在服務器上使用共享庫和驗證引擎。為此,我先向解決方案添加另一個 ASP.NET Core Web 應用程序項目。這次,我在圖 1 所示的“新建 ASP.NET Core Web 應用程序”對話框中選擇的是“API”,而不是“Blazor”。
新建 API 項目后,我就添加對共享項目的引用,就像在 Blazor 客戶端應用程序中(見圖 5)一樣。接下來,我向 API 項目添加新控制器。
新控制器接受來自 Blazor 客戶端的 RegistrationData 調用,如圖 9 所示。注冊控制器在服務器上運行,并且是后端 API 服務器的典型特征。區別在于,它現在運行在客戶端上運行的相同驗證規則。
圖 9:注冊控制器
[ApiController]
public class RegistrationController?:?ControllerBase
{
[HttpPost]
public IActionResult Post([FromBody] RegistrationData value)
{
if (value.HasErrors())
{
return BadRequest();
}
// TODO: Save data to database
return Created("api/registration", value);
}
}
注冊控制器有一個 POST 方法,它接受 RegistrationData 作為自己的值。它調用 HasErrors 方法,以驗證所有規則并返回布爾值。
若有錯誤,控制器返回 BadRequest 響應;否則,它返回成功響應。我特意省略掉了將注冊數據保存到數據庫的代碼,這樣我就可以驗證方案為重點了。
現在,共享驗證邏輯在客戶端和服務器上運行。
遠景
此簡單示例展示了如何在瀏覽器和后端之間共享驗證邏輯,僅僅觸及全棧 C# 環境強大功能的皮毛。
Blazor 的神奇之處在于,使用它,現有 C# 開發人員大軍可以生成功能強大的新式響應式單頁應用程序,且最大限度地縮短啟動時間。使用它,企業可以重用和重新打包現有代碼,以便能夠直接在瀏覽器中運行現有代碼。
能夠在瀏覽器、桌面、服務器、云和移動平臺之間共享 C# 代碼,將大大提升開發人員的工作效率。它還便于開發人員更快地向客戶交付更多功能和更多業務價值。
原文地址:https://msdn.microsoft.com/zh-cn/magazine/mt833288
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結
以上是生活随笔為你收集整理的使用 C# 和 Blazor 进行全栈开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 MSIX 打包 DotNetCor
- 下一篇: AWS vs K8s 是新的 Windo