Blazorで何故か IConfiguration を使うところでビルドエラー、の謎を解く

## 「CS1929：'IConfiguration' に 'GetConnectionString' の定義が含まれていません」
先日、Qiita で Blazor に関する下記記事を見つけました。

https://qiita.com/SGTY/items/e61e10bd533c847fa5f1

`IConfiguration.razor` というファイル名で Razor コンポーネントを記述し、その中で `Microsoft.Extensions.Configuration.IConfiguration` オブジェクトを DI コンテナから注入してもらって使うコードを書いたら、
「CS1929：'IConfiguration' に 'GetConnectionString' の定義が含まれておらず、最も適している拡張メソッド オーバーロード 'ConfigurationExtensions.GetConnectionString(IConfiguration, string)' には 'IConfiguration' 型のレシーバーが必要です」
というビルドエラーになってしまった、というお話でした。

なぜこんなビルドエラーになってしまうのか、ちょっと紐解いてみましょう。

## `.razor` は、"ちょっと変わった構文の C# ソースファイル"

まず重要なヒントとして、**「`.razor` ファイルは C# のソースコードに変換されている」** ということがあります。先日下記記事を Qiita に投稿したのですが、その中に同じく "`.razor` ファイルは C# のソースコードに変換されている" という名前のセクションで、このことを少し説明してあります。

https://qiita.com/jsakamoto/items/bd5e1541f0524de40183#razor-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AF-c-%E3%81%AE%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B

つまり、**`.razor` ファイルというのは、(`ComponentBae` クラスから派生した) C# のクラス実装を生成する、ちょっと変わった構文の C# ソースファイル**、と捉えてみることができる、ということですね。

で、その **`.razor` ファイルの名前が、生成される C# クラスの名前になる**、という仕様になっております。例えば `Foo.razor` というファイル名で Razor コンポーネントを実装したら、生成される C# コード上、そのクラス名は `Foo` になるんですね。ちなみに名前空間は、とくに明示的に指定なき場合は、`<アセンブリ名>.<その Razor ファイルが配置されているサブフォルダ>` になります (名前空間を明示的に指定するには [`@namespace` ディレクティブ](https://learn.microsoft.com/ja-jp/aspnet/core/mvc/views/razor?view=aspnetcore-7.0#namespace)が使えます)。

この情報を基に、冒頭で説明したビルドエラーが発生する下記 Razor コンポーネントのソースコードを、

```csharp:Pages/IConfiguration.razor
@page "/IConfiguration"
@inject IConfiguration config

pg_con = @connection_str

@code {
  private string? connection_str;

  protected override void OnInitialized()
  {
    connection_str = config.GetConnectionString("db_con");
  }
}
```
C# ソースコードの体裁に書き直してみると、下記のようになります。

```csharp:Pages/IConfiguration.razor から生成されるC#ソースコードに相当するもの
namespace {アセンブリ名}.Pages;

[Route("/IConfiguration")]
public class IConfiguration : ComponentBase
{
  [Inject] 
  private IConfiguration config { get; set; }

  private string? connection_str;

  protected override void OnInitialized()
  {
    connection_str = config.GetConnectionString("db_con");
  }
  // ... 以下略...
```

さて、上記 C# コードを見たときに、お気づきでしょうか。DI コンテナから注入してもらうつもりの 「Microsoft.Extensions.Configuration.**IConfiguration**」と、この Razor コンポーネントのクラス名「{アセンブリ名}.Pages.**IConfiguration**」 とで、**型名が被ってしまっている**ことを!

その結果、以下のように解釈されて、CS1929 ビルドエラーになってしまうわけですね。

```csharp:Pages/IConfiguration.razor から生成されるC#ソースコードに相当するもの
...
// ⚠️この Razor コンポーネントのクラス名が、"IConfiguration" になっている!
public class IConfiguration : ComponentBase
{
  [Inject] 
  private IConfiguration config { get; set; }
  //   ここ👆 の IConfiguration は、名前空間 Microsoft.Extensions.Configuration
  //   にあるそれではなく、この Razor コンポーネント自身のクラス名であると解釈される!
  ...
  protected override void OnInitialized()
  {
    connection_str = config.GetConnectionString("db_con");
    // この Razor コンポーネント自身には 👆 "GetConnectionString()"
    // などというメソッドは当然存在しないので、それで CS1929 ビルドエラー発生!🔥
  }
  // ... 以下略...
```
Visual Studio で開発している場合は、下図のとおり、「@inject IConfiguration config」のところでマウスホバーさせてツールチップを表示させると、「Microsoft.Extensions.Configuration.**IConfiguration**」ではなくて、「{アセンブリ名}.Pages.**IConfiguration**」のほうを指していることを知ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/234872c3-ba5e-7f0d-cc2f-4b98e1087873.png)


## 回避策

ビルドエラーの原因、その正体がわかれば、回避策も考えられます。

まず最初に思いつくのは、冒頭で紹介した記事内で既に解決されているとおり、`.razor` のファイル名を変える (結果として Razor コンポーネントのクラス名が `IConfiguration` ではなくなり、型名の被りが解消される) ことで解決できます。

他に考えられる方法としては、`@inject` ディレクティブで注入してもらう型名 `IConfiguration` を、完全修飾型名で指定する、つまり名前空間込みで型名を記述する方法も考えられます。実際のコーディング例は下記です。

```html:Pages/IConfiguration.razor
...
@inject Microsoft.Extensions.Configuration.IConfiguration config
<!-- @Inject ディレクティブで指定する型名を、👆 名前空間込みで記述する -->
...
```

## まとめ

以上、「`.razor` は、"ちょっと変わった構文の C# ソースファイル"」という意識で Razor コンポーネントのコーディングに取り組むことで、ビルドエラーが発生してもその原因を特定しやすくなるのではないか、との期待があります。

_Happy Coding :)_