Blazor WebAssembly(.NET Core3.1)で作ったポートフォリオサイトを.NET6にバージョンアップした話

# 概要

Blazor Webassemblyで構築したサイトを.NET Core3.1から.NET6にマイグレーションした際の手順およびメモです。
大した規模ではなく、外部のパッケージなども殆ど使用していないレベルのものなので、プロジェクトごと新規作成したほうが早いのですが、アドベントカレンダーもあることだし、既存のプロジェクトを使用しつつ移行するといった事を試してみました。

.NET5から.NET6への移行に関しては、@jsakamotoさんがまとめてくださっており、一部重複してる上に、あまり参考にならない内容が多いと思いますがご容赦を。

https://qiita.com/jsakamoto/items/7a500d8413b142b8f597


## マイグレーション対象
趣味で作ったアプリ等を紹介している、ポートフォリオサイトです。GitHub Pages上でホストしています。

https://nobu17.github.io/Nobus_portfolio/


![toppage.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/eb6d38e8-6f47-018b-afc8-3759b4973ce5.png)


Blazor WebassemblyがGAしたあたりの、2019年のタイミングで作成したものです。
入力フォーム等は無い表示がメインのウェブサイトとなり、どちらかというデザイン等に凝りたかったので、BlazorのUIフレームワークは使用せずにCSSとHTMLのフレームワーク(MDBootstrap (Material Design for Bootstrap))を使用しています。


## 参考にした資料等

.NET Core3.1から見ると.NET6は2つのメジャーバージョンアップを含んでいます。
そこで、それぞれのバージョンアップの方法が書かれたMSのドキュメントを参考に実施しています。
下記リンク内でASP.NET Coreのバージョンアップ内容の一部として、Blazorの場合の説明も行われています。

.NET Core3.1 => .NET5
https://docs.microsoft.com/ja-jp/aspnet/core/migration/31-to-50?view=aspnetcore-6.0&tabs=visual-studio

.NET5 => .NET6
https://docs.microsoft.com/ja-jp/aspnet/core/migration/50-to-60?view=aspnetcore-6.0&tabs=visual-studio

# 修正

## プロジェクトファイルの編集

csprojファイルをエディタで開いて直接編集をします。
必須ではありませんが、暗黙的な global using ディレクティブなども有効にしています。
(詳細は後述するProgram.csにて。)

参照DLLのバージョンはコマンドで変更可能ですので、いったんこのままとします。

```diff:csproj
- <Project Sdk="Microsoft.NET.Sdk.Web">
+ <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
-    <TargetFramework>netstandard2.1</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
-    <RazorLangVersion>3.0</RazorLangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <!-- DLLの内容は後で変更 -->
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
    <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
  </ItemGroup>

 ...
</Project>
```

いったんこの状態でcsprojファイルのあるディレクトリで下記のコマンドを実行します。

```bat:cmd
dotnet add package <パッケージ名>

// 今回の例
dotnet add package Microsoft.AspNetCore.Components.WebAssembly
dotnet add package Microsoft.AspNetCore.Components.WebAssembly.Build
dotnet add package Microsoft.AspNetCore.Components.WebAssembly.DevServer
dotnet add package System.Net.Http.Json

// 実行後に下記でキャッシュをクリアする
dotnet nuget locals --clear all

```

dotnet add packagを実行すると自動でバージョンアップデートを行ってくれます。
実行すると、下記のような状態になりました。

```diff:csproj
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <ItemGroup>
-   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
+   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
-   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
+   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.1" PrivateAssets="all" />
-   <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
+   <PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
  </ItemGroup>

 ...
</Project>
```

バージョンが6になっているものもありますが、3.0で止まっているものもあります。
3.0のままの、**Microsoft.AspNetCore.Components.WebAssembly.Build**は.NET5で不要となったので参照を外すだけで良いようです。

```diff:csproj
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <ItemGroup>
-   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
+   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.1" />
-   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
-   <PackageReference Include="Toolbelt.Blazor.DevServer.WithCssLiveReloader" Version="3.2.0" PrivateAssets="all" />
+   <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.1" PrivateAssets="all" />
-   <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
+   <PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
  </ItemGroup>

 ...
</Project>
```


## index.htmlの修正

下記のようにindex.htmlの修正を行いました。

```diff:index.html
 <!DOCTYPE html>
 <html>
 <head>
...

+ <link href="{DLL名}.styles.css" rel="stylesheet" /> 
 </head>
 <body>
-    <app>
+    <div id="app">
        <div class="loading">
            <div class="loader">
            </div>
        </div>
+    </div>
-    </app>
 </body>
 </html>
```

修正したポイントは下記の2つです。

- 分離CSSの参照追加 (.NET5で追加されたコンポーネント毎に設定可能なCSS)
- appタグをdivタグへ変更 (.NET5)


## csおよびrazorファイル編集

### Program.cs

```diff:_Import.razor
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Domains.Models.Portofolios;
using InMemoryInfrastructure;
using Domains.Applications.Portfolios;
using Domains.Services;

namespace Blazor_NobuPortfolioApp
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
-           builder.RootComponents.Add<App>("app");
+           builder.RootComponents.Add<App>("#app"); //index.htmlのタグ変更に対応(.NET5)
+           builder.RootComponents.Add<HeadOutlet>("head::after"); // .NET6で追加されたHeadOutletコンポーネントを追加
           
-           builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+           builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); // AddTransientからAddScopedへの変更(.NET5)

            // 独自サービスの追加
            builder.Services.AddSingleton<IPortfolioRepository, InMemoryPortfolioRepository>();
            builder.Services.AddSingleton<PortfolioApplicationService>();
            builder.Services.AddSingleton<PortfolioService>();

            await builder.Build().RunAsync();
        }
    }
}
```

.NET6で追加された、**HeadOutlet**コンポーネントを追加しています。
Javascriptを使用しなくても、ページごとにheadタグの内容変更が可能になる新機能になります。
**HeadOutlet**に関しては下記の記事が非常にわかりやすくまとまっています。

https://zenn.dev/okazuki/articles/blazor-dotnet6-headoutlet


このままでも良いのですが、さらに.NET6の新機能の**暗黙的な global using ディレクティブ**と**最上位レベルのステートメント**を適応します。
(詳細は、最初に挙げた@jsakamotoさんの記事がわかりやすくまとまっています。)

こちらを適応すると下記のようになり、行数が減ってすっきりとしました。

```csharp:Program.cs
using Blazor_NobuPortfolioApp;
using Domains.Applications.Portfolios;
using Domains.Models.Portofolios;
using Domains.Services;
using InMemoryInfrastructure;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

// 独自サービスの追加
builder.Services.AddSingleton<IPortfolioRepository, InMemoryPortfolioRepository>();
builder.Services.AddSingleton<PortfolioApplicationService>();
builder.Services.AddSingleton<PortfolioService>();

await builder.Build().RunAsync();
```


### _Import.razor
Microsoft.AspNetCore.Components.Web.Virtualizationの参照を追加します。
(.NET5)

```diff:_Import.razor
@using System.Net.Http
 ...
+ @using Microsoft.AspNetCore.Components.Web.Virtualization
 ...
```

### App.razor
変更しなくても問題はありませんでしたが、.NET6のBlazorのテンプレートでは下記の通りになってたので、修正しました。

```diff:App.razor
- <Router AppAssembly="@typeof(Program).Assembly">
+ <Router AppAssembly="@typeof(App).Assembly">
 ...
</Router>

```

### razor.cssの追加
index.htmlに追加した分離cssを有効化するために、どのコンポーネントでも構わないので**<コンポーネント名>.razor.css**のファイルを追加します。(中身は不要)

コンポーネント固有のCSSなどがあれば合わせて移動させてしまうのも良いと思います。
検証後、簡単に移せそうなものはコンポーネント毎のCSSに移しました。
コンポーネント毎にCSSを管理できるほうが、やはりしっくり来ますね。

## 以上

これで作業は完了です。
ビルドして動けば確認完了です。


## 詰まった場所

手順としてまとめると大したことありませんが、個人的に詰まった部分を何点か紹介します。
うっかりや単純なものが多いので、ネタとして読んでいただければ。

### ロード画面で止まる

最初、ロード画面のまま、アプリが起動しないという症状になりました。
ブラウザのコンソール上にはblazorのjsファイルが読み込めていないエラーが発生していました。
![err5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/f1f231dd-e062-64f0-3eae-9726cd3e2a5c.png)

原因としては、参照しているパッケージの問題でした。
デフォルトの**Microsoft.AspNetCore.Components.WebAssembly.DevServer**の代わりに、@jsakamotoさんが開発した、CSSライブロード機能のある**Blazor Dev Server with CSS live reloader**を使用していた事を完全に失念して、デフォルトで入っているオフィシャルのパッケージだと勘違い。
(オフィシャルのDevServer自体がCSSライブロードに対応したため、こちらは最終バージョンの5.04(.NET5用)となっていた。)

DevServerの参照に戻すことで解決しました。
お恥ずかしい限り・・・。

https://qiita.com/jsakamoto/items/cbab6e9ec105711a0bb8


### スクロールバー出ない問題

ビルド完了後アプリが起動するものの、ブラウザの縦のスクロールバーが表示されず、画面が縦にスクロールできないといった問題が発生しました。
ブラウザのコンソールを見てみると、下記のようなjsファイルの読み込みエラーが出ていました。
![err3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/4c5ae9b7-9b5d-e224-9509-0c616dbdacaa.png)

原因としては、index.htmlにappタグの代わりに追加した**id="app"のdivタグ**に対して、class名を記載しているせいでした。
既存のappタグ直下のdivにappを追加してしまったのが良くなかったと思われます・・・。
もう一段下にclass="loading"のdivタグを追加して対応することで解決しました。
詳細は分かっていないのですが、ルートのappのタグには何も追加しないほうが良いみたいです。

```diff:index.html
 <!DOCTYPE html>
 <html>

 ...

 <body>
+    <div id="app" class="loading">
       <div class="loader">
       </div>
+    </div>
 </body>
 </html>
```


### スタイルシート読み込みエラー

同様にブラウザのコンソール上にindex.htmlに追加した分離CSSの参照エラーが発生していました。
![err4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/21a18243-b0ac-7820-1618-4df6fa08776c.png)
分離CSSが無い時代に作ったものなので、razor.cssファイルが1つも無いため、ビルド時にCSSが生成されていない事が原因のようでした。
1つでも良いので、空のrazor.cssを作ることで解決しました。

## おまけ

Git HubPageへのデプロイに関して、GitHub Actionsを使用したものに変更しました。
またまた、@jsakamotoさんの**PublishSPAforGitHubPages.Build**を使用しています。
パッケージをインストールして、ビルドのコマンドに**”-p:GHPages=true”**を追加するだけで、簡単に使用できてかなりお手軽です。感謝！

https://qiita.com/jsakamoto/items/0be0005d9b30acbfe1e4


```yml
name: .NET Deploy to Github Pages

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 6.0.x
    - name: Build
      run: dotnet publish Blazor_NobuPortfolioApp/Blazor_NobuPortfolioApp.csproj -c:Release -o:bin/public -p:GHPages=true
    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
           github_token: ${{ secrets.GITHUB_TOKEN }}
           publish_dir: bin/public/wwwroot
           force_orphan: true

```

## まとめ

なんとか.NET Core3.1のBlazor WebAssemblyを.NET6にアップデートすることができました。
新規でプロジェクトを作ってコードを移したほうが、かなり楽だと思いますが、１つ１つ対応していく事で何が変わったのかわかって、これはこれで勉強になりました。

他にも.NET5時代に作ったものなどもあり、これを機にアップデートしようと思います。

もう少しサクッと行けるかと思ったら、Blazor自体を触るのが久しぶりということもあり、所々で問題に直面し、思った以上に時間がかかりました・・・。
ちゃんと日々変更をキャッチアップしたり、定期的なバージョンアップを行う大事さを改めて痛感した所存です。