【C#】BlazorWASMでHttpClientを使用すると「There is no registered service of type 'System.Net.Http.HttpClient'」となりページが表示されない

## 初めに

BlazorWASMを触っている際にハマってしまったことがあったためまとめます。

## 環境

- Windows11
- .Net 8
- Blazor Web App

## 問題

Blazor Web Appを使用して作成したソリューションのクライアントプロジェクトの`Program.cs`に対して`HttpClient`のサービスを追加してrazorページ上から依存性注入を行い使用するページについてです。

ページ表示すると`InvalidOperationException: Cannot provide a value for property 'Http' on type 'BlazorApp.Client.Pages.TodoItems'. There is no registered service of type 'System.Net.Http.HttpClient'.`というエラーメッセージ表示されてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783169/647d61af-4388-124b-79d4-15f076ce6f9b.png)

## 原因

同じ状況になった海外の方がstackoverflowで質問をしていました。

https://stackoverflow.com/questions/77570803/how-do-i-register-http-client-in-blazor-net-8

このうち「Ed Mendez」様の回答に記載がありました。
少々長いですが重要なのですべて引用します。

>I am new to Blazor in general, but I was having the same error in Blazor 8, my setup had two projects, the server and client. the project was setup as webassembly at each component.
>
>program.cs 
>```CSharp
>// Add services to the container.
>builder.Services.AddRazorComponents()
>    .AddInteractiveWebAssemblyComponents();
>
>...
>
>app.MapRazorComponents<App>()
>    .AddInteractiveWebAssemblyRenderMode()
>    .AddAdditionalAssemblies(typeof(Client._Imports).Assembly);
>```
>What seemed to have been occurring was that the razor component was loading twice, first looks like it was being rendered at the server and the subsequent time at the client.
>
>I was testing the 3 methods to access http, via httpclient, named httpclient and typed httpclient.
>
>My registration was as follows. jsonplaceholder.typicode.com is a public rest api that I used to provide json sample data.
>
>```CSharp
>    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://jsonplaceholder.typicode.com") });
>
>    builder.Services.AddHttpClient("JsonPlaceHolderClient", client =>
>    {
>        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
>    });
>
>    builder.Services.AddHttpClient<Client.JsonPlaceHolderClient>(client =>
>    {
>        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
>    });
>```
>The razor component had the following
>
>```CSharp
>@rendermode InteractiveWebAssembly
>```
>
>If I performed the httpclient registration at the server project, the first time the razor component loaded the httpclient's base address was "https://jsonplaceholder.typicode.com", which tells me it was using the server registration. The subsequent time, the httpclient's base address was null.
>
>If I switched the registration to the client project and commented out the server side project registration, the first razor component load had the base address as null, and the subsequent time it was using the client registration and the base address was correct.
>
>If I changed the razor's component rendermode to
>
>```CSharp
>@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
>```
>Then the razor component was loaded once using the registration from the client. This is what I wanted.
>
>This was issue. I wanted the component to execute on the client and didn't think to register the services on the server. The pre-rendering was executing on the server and the http registered services were not found.
>
>HTH.

回答について原因部分をざっくり訳すと重要なのは以下です。

- razorコンポーネントがプリレンダリング時とクライアントにロード時の２回読み込まれる
- プリレンダリングはサーバー側、クライアントにロードはクライアント側で実行
- サーバー側にはHttpClientを登録していないためロード時にHttpClientが見つからない

つまりプリレンダリングがサーバー側で実行されているのにサーバ側に対象のサービスが登録されていないが原因でした。

## 解決策

「Ed Mendez」様の回答に記載があったものを含めて３つ紹介いたします。

### 解決策１ ページのプリレンダリングを無効にする

「Ed Mendez」様の回答にあった方法です。レンダーモードの設定時に以下のように設定しプリレンダリングが行われないようにします。

```CSharp
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
```

この方法の注意点とす。

### 解決策２ サーバー側でHttpClientをサービス登録する

サーバー側でのプリレンダリングの際に登録されていないというエラーなのでサーバー側にも下記のようにサービス登録すればエラーは発生しなくなります。

```CSharp
builder.Services.AddHttpClient();
```

この方法だとプリレンダリングも実行されるためサクサクされるようになります。
ただし初期化処理もサーバー側で１度実行されるため、実装には注意が必要な場合があります。

### 解決策３ レンダーモードのグローバル設定を行う

レンダーモードのグローバル設定に設定するとはプロジェクト作成時に設定できる項目のことです。
(もちろんあとからソース上で設定することもできます)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783169/4b4d2ff3-c931-31a4-6010-b2b32b9e0851.png)

このようにするとページ表示時にエラーが発生しなくなります。ただしこの方法だとすべてのページがWASMになるため既存システムで変更する場合は影響が大きいです。

## おわりに

Blazorのレンダーモード周りについてはかなり複雑なので調べるのが大変でした。。。
この記事が皆さんの助けになれば幸いです。

## 参考

https://stackoverflow.com/questions/77570803/how-do-i-register-http-client-in-blazor-net-8