顯示具有 MVP2021 標籤的文章。 顯示所有文章
顯示具有 MVP2021 標籤的文章。 顯示所有文章

2021年7月24日 星期六

ASP.NET Core 應該具備知識 - 選項模式

ASP.NET Core 應該具備知識 - 選項模式


ASP.NET Core 應該具備知識

ASP.NET Core 應用程式啟動與 Startup 類別

ASP.NET Core 靜態檔案 UseStaticFiles

ASP.NET Core 相依性注入設計模式 Dependency Injection

ASP.NET Core 中介軟體 Middleware

ASP.NET Core 設定 Configuration

ASP.NET Core 選項模式 IOptions

ASP.NET Core 日誌記錄 Logger

ASP.NET Core 從空白專案建立 Blazor 應用


建立一個 ASP.NET Core Blazor Server 專案

  • 開啟 Visual Studio 2019

  • 在 [Visual Studio 2019] 對話窗中,點選右下方的 [建立新的專案] 選項

  • 在 [建立新專案] 對話窗中,在中間上方的專案範本過濾條件中

    1. 設定程式語言為 [C#]

    2. 設定專案範本為 [Web]

    3. 選擇專案範本項目清單,點選 [Blazor Server 應用程式] 這個專案範本項目

      用來建立 Blazor Server 應用程式的專案範本,該應用程式會在 ASP.NET Core 應用程式內執行伺服器端,並透過 SignalR 連線處理使用者互動。Web 應用程式具有豐富且動態的使用者介面 (UI) 時,此範本最適用。

    4. 點選右下方的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗出現後

    在 [專案名稱] 內,輸入 AC06

    點選右下角的 [下一步] 按鈕

  • 在 [其他資訊] 對話窗出現後,確認 [目標 Framework] 的下拉選單要選擇 [.NET 5.0 (目前)]

  • 點選右下角的 [建立] 按鈕

  • 此時這個 [ASP.NET Core Web 應用程式] 專案已經建立完成,從方案總管視窗內可以看到如下圖的結構

執行這個專案

  • 請按下 [F5] 按鍵,開始執行這個專案

  • 瀏覽器的畫面如下

修正 appsettings.json 檔案

  • 在專案根目錄下找到並且打開 [appsettings.json] 檔案
  • 使用底下 JSON 定義更新這個檔案內容
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Custom": {
    "MyEnglishName": "Vulcan Lee",
    "IsNETDeveloper": true,
    "NETDeveloperYear": 10,
    "Nest": {
      "IsNETDeveloper": true,
      "NETDeveloperYear": 10
    }
  }
}

建立 階層設定 對應的類別

  • 滑鼠右擊 [Data] 資料夾
  • 從彈出功能表清單點選 [加入] > [類別]
  • 在出現 [新增項目 - AC06] 對話窗
  • 在下方 [名稱] 欄位內輸入 CustomMapClass
  • 點選右下方 [新增] 按鈕
  • 使用底下程式碼替換掉這個檔案內容
namespace AC06.Data
{
    public class CustomMapClass
    {
        public string MyEnglishName { get; set; }
        public bool IsNETDeveloper { get; set; }
        public int NETDeveloperYear { get; set; }
        public Nest Nest { get; set; }
    }

    public class Nest
    {
        public bool IsNETDeveloper { get; set; }
        public int NETDeveloperYear { get; set; }
    }
}

修正 Index.razor 元件頁面

  • 在 [Pages] 資料夾下找到並且打開 [Index.razor] 檔案
  • 使用底下的程式碼取代這個檔案內容
@page "/"
@using Microsoft.Extensions.Configuration
@using Data
@inject IConfiguration configuration

<h1>Hello, 選項模式!</h1>
<h2 class="text-primary">@Message1</h2>

@code{

    public string Message1 { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        var customMapClass = new CustomMapClass();
        configuration.GetSection("Custom").Bind(customMapClass);
        Message1 = $"{customMapClass.MyEnglishName} is .NET Developer :" +
                $"{customMapClass.Nest.IsNETDeveloper}";
        StateHasChanged();
    }
}

執行這個專案

  • 按下 [Shift] + [F5] 來停止這個專案執行

  • 請按下 [F5] 按鍵,開始執行這個專案

  • 瀏覽器的畫面如下

從這裡可以看到使用了 ConfigurationBinder.Bind 方法 來做到嘗試將屬性名稱與組態索引鍵以遞迴方式比對,以將指定的物件執行個體繫結至組態值

因此,一旦完成這個 [Bind] 方法呼叫之後,從 設定 系統便可以讀取到相關設定屬性值,所以,透過 [customMapClass] 變數便可以使用強型別來取得相關設定屬性值。

使用強型別方式的 選項介面 來取得設定屬性值

  • 在專案根目錄打開 [Startup.cs] 檔案
  • 找到這個 [Configuration] 方法
  • 接著加入底下的程式碼到這個方法內
services.Configure<CustomMapClass>(Configuration.GetSection("Custom"));

修正 Index.razor 元件頁面

  • 在 [Pages] 資料夾下找到並且打開 [Index.razor] 檔案
  • 使用底下的程式碼取代這個檔案內容
@page "/"
@using Microsoft.Extensions.Configuration
@using Microsoft.Extensions.Options
@using Data
@inject IConfiguration configuration
@inject IOptions<CustomMapClass> customMapClassOption

<h1>Hello, 選項模式!</h1>
<h2 class="text-primary">@Message1</h2>
<h2 class="text-success">@Message2</h2>

@code{

    public string Message1 { get; set; }
    public string Message2 { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        var customMapClass = new CustomMapClass();
        configuration.GetSection("Custom").Bind(customMapClass);
        Message1 = $"{customMapClass.MyEnglishName} is .NET Developer :" +
                $"{customMapClass.Nest.IsNETDeveloper}";
        var customMapOption = customMapClassOption.Value;
        Message2 = $"{customMapOption.MyEnglishName} is .NET Developer :" +
            $"{customMapOption.Nest.IsNETDeveloper}";
        StateHasChanged();
    }
}

在這裡注入了 [IOptions] 物件到 [customMapClassOption] 變數內,透過這個屬性內的 [Value] 屬性,便可以使用強型別方式來取得對應在設定內的相關屬性值。

執行這個專案

  • 按下 [Shift] + [F5] 來停止這個專案執行

  • 請按下 [F5] 按鍵,開始執行這個專案

  • 瀏覽器的畫面如下

 









2021年7月23日 星期五

ASP.NET Core 5 Blazor Server 入門學習教學指引

ASP.NET Core 5 Blazor Server 入門學習教學指引

對於想要學習與體驗 Blazor Server 託管模式的專案開發,第一件要瞭解到的事情那就是,開發者需要對於 ASP.NET Core 的許多基本開發既能有所了解,因為,許多開發專案上的需求,是需要使用到 ASP.NET Core 所提供的基本功能;另外,那就是對於想要開發 Blazor Server 專案的時候,許多 Blazor 功能與特色必須要能夠有所知道,這裡也會列出需要學會的知識與開發方法。

ASP.NET Core 應該具備知識

ASP.NET Core 應用程式啟動與 Startup 類別

ASP.NET Core 靜態檔案 UseStaticFiles

ASP.NET Core 相依性注入設計模式 Dependency Injection

ASP.NET Core 中介軟體 Middleware

ASP.NET Core 設定 Configuration

ASP.NET Core 選項模式 IOptions

ASP.NET Core 日誌記錄 Logger

ASP.NET Core 從空白專案建立 Blazor 應用

Blazor Server 必會開發技能

Blazor Server 建立一個新的頁面

Blazor Server 元件生命週期事件 Component Life Cycle

Blazor Server C# 程式碼設計方法

Blazor Server 單向資料綁定 One Way Data Binding 與 重新轉譯 Binding

Blazor Server Hello 互動頁面與事件設計 Two Way Data Binding

Blazor Server 元件間的參數傳遞與回應事件 Component Parameter EventCallback

Blazor Server C# 與 JavaScript 互相呼叫 IJSRuntime

Blazor Server 表單和驗證 Form Validation 








2021年7月21日 星期三

ASP.NET Core 應該具備知識 - 相依性注入設計模式

ASP.NET Core 應該具備知識 - 相依性注入設計模式



建立一個 ASP.NET Core MVC 專案

  • 開啟 Visual Studio 2019

  • 在 [Visual Studio 2019] 對話窗中,點選右下方的 [建立新的專案] 選項

  • 在 [建立新專案] 對話窗中,在中間上方的專案範本過濾條件中

    1. 設定程式語言為 [C#]
    2. 設定專案範本為 [Web]
    3. 選擇專案範本項目清單,點選 [ASP.NET Core Web API] 這個專案範本項目
    4. 點選右下方的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗出現後

    在 [專案名稱] 內,輸入 AC03

    點選右下角的 [下一步] 按鈕

  • 在 [其他資訊] 對話窗出現後,確認 [目標 Framework] 的下拉選單要選擇 [.NET 5.0 (目前)]

  • 點選右下角的 [建立] 按鈕

  • 此時這個 [ASP.NET Core Web API] 專案已經建立完成,從方案總管視窗內可以看到如下圖的結構

建立服務類別與介面

  • 滑鼠右擊專案節點

  • 從彈出功能表來選取 [加入] > [新增資料夾]

  • 將這個資料夾命名為 Services

  • 滑鼠右擊剛剛建立的 [Services] 資料夾

  • 從彈出功能表點選 [加入] > [類別]

  • 當出現 [新增項目 - AC03] 對話窗

  • 在中間區域的樣板清單項目,點選 [介面] 項目

  • 在下方 [名稱] 欄位輸入 [IMyService.cs]

  • 在右下方點選 [新增] 按鈕

  • 使用底下的程式碼替換掉這個檔案原先內容

using System;

namespace AC03.Services
{
    public interface IMyService
    {
        Guid Guid { get; set; }
        int HashCode { get; }
    }
    public interface IMyServiceTransient : IMyService { }
    public interface IMyServiceScoped : IMyService { }
    public interface IMyServiceSingleton : IMyService { }
}

為了要能夠充分表現出 .NET Core 中的相依性插入 所支援相依性注入的三中生命週期,在這個檔案中,將會宣告四個介面

其中 [IMyService] 為基底介面的宣告,在這裡宣告會有兩個屬性,一個是型別為 [Guid] 的唯一識別碼屬性,另外一個透過 .NET 本身的 [object] 物件的 [GetHashCode] 方法來目前物件的雜湊碼;在這裡是要透過這兩個屬性來判斷出當時注入的物件,是否為同一個

[IMyServiceTransient] 介面將會是用來取得注入 Transient 暫時性 的物件,這代表了每次透過相依性注入機制來取得或者注入一個物件的時候,相依性注入的 Container 容器都會每次重新建立出一個新的物件出來;這些注入得到的物件當沒有使用後,將會被記憶體回收機制回收。

[IMyServiceScoped] 介面將會是用來取得注入 Scoped 範圍 的物件,當使用相依性注入容器來註冊具有範圍性的後,每次需要注入這樣類型物件的時候,會判斷當時若有使用 HTTP Request 請求,對於同一個 HTTP 請求,第一次將會產生出一個新的注入物件,而在同一個 HTTP 請求想要注入這樣具有範圍性的物件,將不再建立新的物件,相依性注入容器而是將剛剛剛產生的物件直接回傳回去,因此,在同一個 HTTP Request 請求要注入具有範圍性的物件,將會取得相同的物件;這些注入得到的物件當同一個 HTTP 請求內都沒有使用後,將會被記憶體回收機制回收。

[IMyServiceSingleton] 介面將會是用來取得注入 Singleton 單一 的物件,這代表了每次透過相依性注入機制來取得或者注入一個物件的時候,相依性注入的 Container 容器僅僅會在第一次的時候來建立出一個新的物件出來,之後同樣的 單一 注入物件請求,大家都會取得同一個物件。

  • 滑鼠右擊 [Services] 資料夾
  • 從彈出功能表點選 [加入] > [類別]
  • 在下方 [名稱] 欄位輸入 [MyService.cs]
  • 在右下方點選 [新增] 按鈕
  • 使用底下的程式碼替換掉這個檔案原先內容
using System;

namespace AC03.Services
{
    public class MyService : IMyService, IMyServiceTransient, IMyServiceScoped, IMyServiceSingleton
    {
        public int HashCode
        {
            get
            {
                return this.GetHashCode();
            }
        }
        public Guid Guid { get; set; } = Guid.NewGuid();
    }
}

在這裡很簡單的實作這兩個屬性,只是分別宣告其預設值。

進行相依性服務的註冊 Registration

  • 在專案根目錄下找到並且打開 [Startup.cs] 檔案
  • 找到 [ConfigureServices] 方法,在這個方法內加入底下的程式碼
#region 註冊客製化的服務
services.AddTransient<IMyServiceTransient, MyService>();
services.AddScoped<IMyServiceScoped, MyService>();
services.AddSingleton<IMyServiceSingleton, MyService>();
services.AddTransient<MyService>();
#endregion

在這裡分別使用不同的介面來對相依性注入容器宣告都要注入同一個類型的物件,甚至在最後一行的程式碼,也可以直接使用實體類別來進行註冊,不需要使用到介面。

開始使用 IoC Container 反轉控制容器 來注入物件

  • 滑鼠右擊 [Controllers] 資料夾

  • 從彈出功能表清單中點選 [加入] > [控制器]

  • 當 [新增 Scaffold 項目] 對話窗出現之後

  • 依序點選 [已安裝] > [通用] > [API] > [API 控制器 - 空白]

  • 點選右下角的 [加入] 按鈕

  • 現在將會看到 [新增項目 - AC03] 對話窗

  • 在下方的 [名稱] 欄位中輸入 DIController

  • 點選右下方 [新增] 按鈕

  • 使用底下的程式碼替換這個檔案內容

using AC03.Services;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Text;

namespace AC03.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DIController : ControllerBase
    {
        private readonly IMyServiceTransient myServiceTransient1;
        private readonly IMyServiceTransient myServiceTransient2;
        private readonly IMyServiceScoped myServiceScoped1;
        private readonly IMyServiceScoped myServiceScoped2;
        private readonly IMyServiceSingleton myServiceSingleton1;
        private readonly IMyServiceSingleton myServiceSingleton2;
        private readonly MyService myService;

        public DIController(IMyServiceTransient myServiceTransient1, IMyServiceTransient myServiceTransient2,
            IMyServiceScoped myServiceScoped1, IMyServiceScoped myServiceScoped2,
            IMyServiceSingleton myServiceSingleton1, IMyServiceSingleton myServiceSingleton2, MyService myService)
        {
            this.myServiceTransient1 = myServiceTransient1;
            this.myServiceTransient2 = myServiceTransient2;
            this.myServiceScoped1 = myServiceScoped1;
            this.myServiceScoped2 = myServiceScoped2;
            this.myServiceSingleton1 = myServiceSingleton1;
            this.myServiceSingleton2 = myServiceSingleton2;
            this.myService = myService;
        }

        [HttpGet]
        public string Get()
        {
            StringBuilder result = new StringBuilder();

            result.Append($"注入 [類別] 暫時性物件1 " + Environment.NewLine);
            result.Append($"Guid:{myService.Guid} , HashCode :{myService.HashCode}" +
                Environment.NewLine);
            result.Append($"注入暫時性物件1 " + Environment.NewLine);
            result.Append($"Guid:{myServiceTransient1.Guid} , HashCode :{myServiceTransient1.HashCode}" +
                Environment.NewLine);
            result.Append($"注入暫時性物件2 " + Environment.NewLine);
            result.Append($"Guid:{myServiceTransient2.Guid} , HashCode :{myServiceTransient2.HashCode}" +
                Environment.NewLine + Environment.NewLine);
            result.Append($"注入範圍性物件1 " + Environment.NewLine);
            result.Append($"Guid:{myServiceScoped1.Guid} , HashCode :{myServiceScoped1.HashCode}" +
                Environment.NewLine);
            result.Append($"注入範圍性物件2 " + Environment.NewLine);
            result.Append($"Guid:{myServiceScoped2.Guid} , HashCode :{myServiceScoped2.HashCode}" +
                Environment.NewLine + Environment.NewLine);
            result.Append($"注入永久性物件1 " + Environment.NewLine);
            result.Append($"Guid:{myServiceSingleton1.Guid} , HashCode :{myServiceSingleton1.HashCode}" +
                Environment.NewLine);
            result.Append($"注入永久性物件2 " + Environment.NewLine);
            result.Append($"Guid:{myServiceSingleton2.Guid} , HashCode :{myServiceSingleton2.HashCode}" +
                Environment.NewLine);

            return result.ToString();
        }
    }
}

在這個控制器類別中,可以看到使用這樣的建構函式 public DIController(IMyServiceTransient myServiceTransient1, IMyServiceTransient myServiceTransient2,IMyServiceScoped myServiceScoped1, IMyServiceScoped myServiceScoped2,IMyServiceSingleton myServiceSingleton1, IMyServiceSingleton myServiceSingleton2, MyService myService) 宣告,這代表了當產生了 [DIController] 控制器物件,將會透過 相依性注入容器 Dependency Injection Container 或者 反轉控制容器 Inversion of Control IoC Container 來依據當時註冊的生命週期設定,注入適當的物件到建構函式內,在建構式內將會把這些注入的參數指派給該類別的欄位。

執行這個專案

  • 請按下 [F5] 按鍵,開始執行這個專案

  • 瀏覽器的畫面如下

  • 請在瀏覽器網址列上輸入 https://localhost:5001/api/di

從這個 [DIController] 控制器提供的 [Get] 方法執行結果看出

注入 [類別] 暫時性物件1 
Guid:925f5a0a-f899-47d0-9321-21801eb9619c , HashCode :47546512

在進行相依性注入設計過程中,在進行註冊的過程中,是可以直接類別,而不一定要透過介面,上面的執行結果看到直接注入一個類別的執行結果

接下來要來看看採用暫時性生命週期的注入方式所執行的結果,結果如下

注入暫時性物件1 
Guid:674f897d-5c92-4d41-b45a-287b455ece2e , HashCode :48077747
注入暫時性物件2 
Guid:c841ca16-ed9e-424e-82a6-4db695f4c828 , HashCode :6999374

從上面看到的執行結果,在同一個 HTTP 請求中,使用 [IMyServiceTransient] 來注入兩個物件,從 [HashCode] 與 [Guid] 看出這兩個注入的物件為不同的物件。

現在來看看具範圍性的注入行為

注入範圍性物件1 
Guid:97bd9b84-89e9-4971-924f-6a03162fe68b , HashCode :7425030
注入範圍性物件2 
Guid:97bd9b84-89e9-4971-924f-6a03162fe68b , HashCode :7425030

上面為採用範圍性的注入物件,從 [HashCode] 與 [Guid] 看出這兩個注入的物件為指向同一個物件,這裡可以看出與短暫性注入的方式不同。

最後來看看單一性的注入行為

注入永久性物件1 
Guid:990251da-cf95-4894-8867-c9672b1cfc99 , HashCode :28282175
注入永久性物件2 
Guid:990251da-cf95-4894-8867-c9672b1cfc99 , HashCode :28282175

乍看之下,採用單一性的注入得到的物件似乎與具範圍性方式注入的物件得到相同的結果,因為,所注入的兩個單一性注入物件,都指向同一個物件。

現在請重新整理呼叫這個 [Get] Web API 網頁,將會得到下圖畫面

從第二次的執行結果可以看到,在注入範圍性物件將會得到底下的結果

注入範圍性物件1 
Guid:718fe4fa-f69e-4b06-b023-f8a87b36408c , HashCode :65472899
注入範圍性物件2 
Guid:718fe4fa-f69e-4b06-b023-f8a87b36408c , HashCode :65472899

這與第一次呼叫這個 Web API 所得到物件將會不同(可以從 Hash Code 或者從 Guid 的物件值簡單判斷出來,會有這樣的結果那是因為這是兩個不同的 HTTP 請求,因此,對於具有範圍性的相依性注入行為發生的時候,會在這次新的 HTTP 請求發生的時候,將會產生新的物件。

最後,對於第二次呼叫單一性的注入行為,得到底下的結果

注入永久性物件1 
Guid:990251da-cf95-4894-8867-c9672b1cfc99 , HashCode :28282175
注入永久性物件2 
Guid:990251da-cf95-4894-8867-c9672b1cfc99 , HashCode :28282175

從這裡便可以看出 [範圍性] 相依性注入 與 [單一性] 相依性注入的差異,在採用單一性注入的時候,不論在任何時候(只要這個應用程式沒有重新啟動),當使用單一性注入請求,將會得到相同的物件,就算在不同的 HTTP 請求發生的時候,不會得到不同的物件