歡迎光臨
每天分享高質量文章

使用 C# 和 Blazor 進行全棧開發

作者: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 代碼在 

 標記內有四個  欄位。 標記是自定義 Blazor 組件,用於處理欄位的資料系結和錯誤顯示邏輯。此組件只需要三個引數即可正常運行:

 

 

  • Model 欄位:標識資料要系結到的類。

  • FieldName:標識資料要系結到的資料成員。

  • DisplayName 欄位:讓組件可以顯示易記訊息。

 

圖 7:添加 RegistrationForm.cshtml 檔案

@page "/registrationform"
@inject HttpClient Http
@using SharedLibrary

Registration Form

@if (!registrationComplete)
{

 

class

 

=“form-group”>
“model” FieldName=“FirstName” DisplayName=“First Name” />

class

=“form-group”>

“model” FieldName=“LastName” DisplayName=“Last Name” />

class

=“form-group”>
“model” FieldName=“Email” DisplayName=“Email” />

class

=“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 = [email protected], 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# 代碼,將大大提升開發人員的工作效率。它還便於開發人員更快地向客戶交付更多功能和更多業務價值。

已同步到看一看
赞(0)

分享創造快樂