作者:Jonathan C. Miller
連結:https://msdn.microsoft.com/zh-cn/magazine/mt833288
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 客戶端之間共享的程式碼都位於一個獨立的共享庫專案中。共享庫包含模型類和非常簡單的驗證引擎。模型類保留註冊窗體中的資料欄位。該命令如下所示:
public class RegistrationData : ModelBase
{
[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 類,如下所示:
public class ModelBase
{
}
錯誤和規則
現在,我將向 ModelBase 類新增包含驗證錯誤串列的專用字典。_errors 字典先以欄位名稱為鍵,再以規則名稱為鍵。值是要顯示的實際錯誤訊息。
透過此設定,可以輕鬆確定特定欄位是否有驗證錯誤,並快速檢索錯誤訊息。
程式碼如下:
private Dictionary> _errors =
new Dictionary<string, Dictionary<string, string>>();
現在,我將新增 AddError 方法,以將錯誤輸入內部錯誤字典。
AddError 有 fieldName、ruleName 和 errorText 引數。它先搜尋內部錯誤字典,並刪除已有條目,再新增新的錯誤條目,如下麵的程式碼所示:
private void AddError(String fieldName, String ruleName, String 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 引數,併在內部錯誤字典中搜索並刪除匹配的錯誤。程式碼如下:
private void RemoveError(String fieldName, String 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 函式
public void CheckRules(String fieldName)
{
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 字典來確定相應欄位是否有任何錯誤,如下所示:
public String Errors(String fieldName)
{
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 伺服器也使用此方法來確定傳入的模型資料是否有錯誤。此函式的程式碼如下:
public bool HasErrors()
{
foreach (var key in _errors.Keys)
if (_errors[key].Keys.Count > 0) { return true; }
return false;
}
值和事件
是時候新增 GetValue 方法了,它需要使用 fieldname 引數,並使用反射來查詢此模型中的欄位並傳回欄位值。Blazor 客戶端使用此方法來檢索當前值,併在輸入框中顯示它,如下所示:
public String GetValue(String fieldName)
{
var propertyInfo = this.GetType().GetProperty(fieldName);
var value = propertyInfo.GetValue(this);
if (value != null) { return value.ToString(); }
return String.Empty;
}
現在,新增 SetValue 方法。它使用反射來查詢此模型中的欄位,並更新欄位值。然後,它觸發 CheckRules 方法,以對相應欄位驗證所有規則。Blazor 客戶端使用此方法,以在使用者在輸入文字框中鍵入內容的同時更新值。程式碼如下:
public void SetValue(String fieldName, object value)
{
var propertyInfo = this.GetType().GetProperty(fieldName);
propertyInfo.SetValue(this, value);
CheckRules(fieldName);
}
最後,我新增 ModelChanged 事件。如果此模型中的值已更改或在內部錯誤字典中新增或刪除了驗證規則,便會觸發這個事件。Blazor 客戶端偵聽此事件,併在事件觸發時更新 UI。正因為此,顯示的錯誤會更新,如下麵的程式碼所示:
public event EventHandler ModelChanged;
protected void OnModelChanged()
{
ModelChanged?.Invoke(this, new EventArgs());
}
得承認此驗證引擎的設計非常簡單,還有很多改進機會。在生產業務應用程式中,設定錯誤的嚴重性級別(如“資訊”、“警告”和“錯誤”)會很有用。在某些情況下,如果無需修改程式碼,即可從配置檔案動態載入規則,將會很有幫助。我不是在提倡建立你自己的驗證引擎;只是有很多選擇。此驗證引擎既要足夠好,以便演示實際示例;又要足夠簡單,以適應本文且易於理解。
建立規則
此時,有包含窗體欄位的 RegistrationData 類。
此類中的欄位使用 RequiredRule 和 EmailRule 等屬性進行修飾。
RegistrationData 類繼承自 ModelBase 類,後者包含所有用於驗證規則並向客戶端通知更改的邏輯。驗證引擎的最後一部分是規則邏輯本身。接下來,我將對此進行探索。
首先,我在 SharedLibrary 中新建 IModelRule 類。
此規則由一個傳回 ValidationResult 的 Validate 方法組成。每個規則都必須實現 IModelRule 介面,如下所示:
public interface IModelRule
{
ValidationResult Validate(String fieldName, object fieldValue);
}
接下來,我在 SharedLibrary 中新建 ValidationResult 類,它由兩個欄位組成。IsValid 欄位指明規則是否有效,而 Message 欄位則包含要在規則無效時顯示的錯誤訊息。程式碼如下所示:
public class ValidationResult
{
public bool IsValid { get; set; }
public String Message { get; set; }
}
示例應用程式使用四個不同的規則,所有規則都是繼承自 Attribute 類並實現 IModelRule 介面的公共類。
現在,是時候建立規則了。請註意,所有驗證規則都只是繼承自 Attribute 類並實現 IModelRule 介面的 Validate 方法的類。如果輸入的文字超過指定的長度上限,圖 4 中的長度上限規則傳回錯誤。其他用於驗證必填欄位、電話和電子郵件位址列位格式的規則的工作方式類似,區別在於它們對要驗證的資料型別採用不同的邏輯。
圖 4:MaxLengthRule 類
public class MaxLengthRule : Attribute, IModelRule
{
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:新增註冊窗體連結
<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
<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 程式碼在
-
Model 欄位:標識資料要系結到的類。
-
FieldName:標識資料要系結到的資料成員。
-
DisplayName 欄位:讓元件可以顯示易記訊息。
圖 7:新增 RegistrationForm.cshtml 檔案
@page "/registrationform"
@inject HttpClient Http
@using SharedLibrary
Registration Form
@if (!registrationComplete)
{
=“form-group”>
“model” FieldName=“FirstName” DisplayName=“First Name” />
=“form-group”>
“model” FieldName=“LastName” DisplayName=“Last Name” />
=“form-group”>
“model” FieldName=“Email” DisplayName=“Email” />
=“form-group”>
“model” FieldName=“Phone” DisplayName=“Phone” />
class=“btn btn-primary” onclick=“@Register”
disabled=“@model.HasErrors()”>Register
}
else
{
Registration Complete!
}
@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(
“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:更新錯誤訊息
@using SharedLibrary
"text"
class=“form-control” placeholder=“@DisplayName”
oninput=“@(e => OnFieldChanged(e.Value))”
value=“@Model.GetValue(FieldName)” />
class=“form-text” style=“color:darkred;”>@Model.Errors(FieldName)
@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:註冊控制器
[Route("api/Registration")]
[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# 程式碼,將大大提升開發人員的工作效率。它還便於開發人員更快地向客戶交付更多功能和更多業務價值。
朋友會在“發現-看一看”看到你“在看”的內容