.NET 8 で ASP.NET Core でホストされた Blazor WebAssembly アプリを作成する

# やりたいこと
- .NET 8 で、.NET 7 以前はできた ASP.NET Core でホストされた Blazor WebAssembly アプリを作成する

# 環境

- Windows 11 23H2
- Visual Studio Community 2022 Version 17.9.5

# 手順

## 準備
1. 空のソリューションを追加
    1. `{ソリューション名}`
1. **Blazor WebAssembly アプリ** プロジェクトを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/a7337d29-effc-52dc-c5e0-88020b756fe8.png)
    1. `{ソリューション名}.UI.Client`
    1. .NET 8 を選択、その他はそのまま
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/aee59ad6-cf9e-7b00-5e5b-014ac41bf51e.png)
    1. デバッグ実行確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/64125e15-bac2-e473-c04b-21dc81a707ec.png)
1. **ASP.NET Core Web API** プロジェクトを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/790a2884-6c74-cd7c-c0e8-4816a289da61.png)
    1. `{ソリューション名}.UI.Server`
    1. .NET 8 を選択、その他はそのまま
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/3bcd7335-fa11-5936-66fe-58e2a89ce789.png)
    1. デバッグ実行確認（Swawgger が開く）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/3e885b60-8815-622b-c0c9-ef638e2fb287.png)
1. クラスライブラリ プロジェクトを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/447a0cdb-e0bf-eb87-93d1-caf825dfd807.png)
    1. `{ソリューション名}.UI.Share`
    1. .NET 8 を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/4ab9d321-dce8-69f3-ab99-fd1918d1fb7a.png)

## 作成したプロジェクト構成
- `{ソリューション名}`
    - `{ソリューション名}.UI.Client`
        - クライアントアプリ
    - `{ソリューション名}.UI.Server`
        - API
    - `{ソリューション名}.UI.Share`
        - クライアントアプリとAPIの間でやり取りするオブジェクト等

## Server の変更
### NuGet パッケージを追加
1. `Microsoft.AspNetCore.Components.WebAssembly.Server`
### 依存関係追加
1. Client
1. Share
### `Program.cs` の変更
```diff_c_sharp:Program.cs
- builder.Services.AddControllers();
+ builder.Services.AddControllersWithViews();
+ builder.Services.AddRazorPages();
```
```diff_c_sharp:Program.cs
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
+   app.UseWebAssemblyDebugging();
}
```
```diff_c_sharp:Program.cs
app.UseHttpsRedirection();

+ app.UseBlazorFrameworkFiles();
+ app.UseStaticFiles();
```
```diff_c_sharp:Program.cs
+ app.MapRazorPages();
app.MapControllers();
+ app.MapFallbackToFile("index.html");
```
```diff_c_sharp:Program.cs（完成）
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
    app.UseWebAssemblyDebugging();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseAuthorization();

app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();
```

### 動作確認
- スタートアッププロジェクトに Server を設定しデバッグ実行
- Swagger が開く・・・！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/e3df36f9-d839-7bd9-f022-84e987fd1842.png)

### Swagger が開かないようにする
1. `launchSettings.json` を開く
1. 書く環境に設定されている `"launchUrl": "swagger",` を `"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",` に置換

### 動作確認
- デバッグ実行
- Blazor WebAssembly アプリが起動した！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/b0d4518c-1ef2-b5f7-538c-6c9df3684a94.png)

## Client の変更
### 依存関係追加
- Share
### Weather.razor から Server の API を叩く
```diff_c_sharp:Weather.razor
    protected override async Task OnInitializedAsync()
    {
-       forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
+       forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("weatherforecast");
    }
```

## Share を使うように変更
### Share に WeatherForecast クラスを追加
このクラスを Client/Server 両方から使います。
```diff_c_sharp:WeatherForecast.cs
+ namespace SolutionName.UI.Share.WeatherForecastModels;
+
+ public class WeatherForecast
+ {
+     public DateOnly Date { get; set; }
+
+     public int TemperatureC { get; set; }
+
+     public string? Summary { get; set; }
+
+     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+ }
```
### Client の WeatherForecast クラスを削除
```diff_c_sharp:Weather.razor
@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("weatherforecast");
    }
    
-   public class WeatherForecast
-   {
-       public DateOnly Date { get; set; }
-
-       public int TemperatureC { get; set; }
-
-       public string? Summary { get; set; }
-
-       public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-   }
}
```
### Client に using 追加
```diff_c_sharp:Weather.razor
@page "/weather"
+ @using SolutionName.UI.Share.WeatherForecastModels
@inject HttpClient Http
```

### Server の WeatherForecast クラスを削除
```diff_c_sharp:WeatherForecast.cs
- namespace HigeDaruma.DemoNet8BlazorWasm.UI.Server;
-
- public class WeatherForecast
- {
-     public DateOnly Date { get; set; }
-
-     public int TemperatureC { get; set; }
-
-     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-
-     public string? Summary { get; set; }
- }
```

### Server に using 追加
```diff_c_sharp:WeatherForecast.cs
+ using SolutionName.UI.Share.WeatherForecastModels;
using Microsoft.AspNetCore.Mvc;
```

### 動作確認
取得成功！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107938/e8ef7f0c-9ef9-d86e-a890-b45a80984e5b.png)

## ついでに C# 12 の新機能を使う
### コレクションの初期化を簡素化する
```diff_c_sharp:WeatherForecastController.cs
-     private static readonly string[] Summaries = new[]
-     {
-         "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", - "Sweltering", "Scorching"
-     };
+     private static readonly string[] Summaries =
+     [
+         "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+     ];
```
### プライマリーコンストラクターを使う（そしてフィールドを削除する）
```diff_c_sharp:WeatherForecastController.cs
- public class WeatherForecastController : ControllerBase
+ public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ControllerBase
{
    private static readonly string[] Summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

-     private readonly ILogger<WeatherForecastController> _logger;
-
-     public WeatherForecastController(ILogger<WeatherForecastController> logger)
-     {
-         _logger = logger;
-     }
```

# おわりに
.NET 8 に ASP.NET Core でホストされた Blazor WebAssembly アプリは追加されないんですかね。Blazor でクライアントを作る単一ソリューションのアプリだと API 層があった方がレイヤードアーキテクチャーを構成しやすいんですけど。
次回この続きでレイヤードアーキテクチャーを実現していこうと思います。

---
# 参考

https://zenn.dev/vigilanteyu/articles/d7a47dff63e8e2

https://qiita.com/jsakamoto/items/07e67b68a469d8ef5b0e