BlazorServerでクライアントのIPアドレスを全ページで共有する

# クライアントのIPアドレスが必要になった

DBで保存しておきたいとか、ログに出しておきたいとか、それなりにありがちな要求にBlazorServerでどうすればいいのか調べてみた。

## BlazorWASMであればさほど悩まなくて済むであろうクライアントのIPアドレス。

BlazorServerはその名の通りサーバーで動作している。
WASMと同じ方法(HttpContext.Connection.RemoteIpAddress)で取得して正しい値が得られるか気になったので先ずは検索。

最初にgithubのaspnetcoreにある以下のissueが目についた。
> How to get the remote IP address of the end user in Blazor Server App running on IIS #31920
> https://github.com/dotnet/aspnetcore/issues/31920

が、解決していない様子。
続いて、↓も見てみる
> Get IP adress blazor server side #37728
> https://github.com/dotnet/aspnetcore/issues/37728

`var remoteIpAddress = request.HttpContext.Connection.RemoteIpAddress;` を、_Host.cshtmlで処理しておけばルートコンポーネントに渡せるとか書いてある。

そういえば#31920の[コメント](https://github.com/dotnet/aspnetcore/issues/31920#issuecomment-822075634)にあった[サンプル](https://github.com/javiercn/blazor-server-aad-sample/blob/master/BlazorServerAuthWithAzureActiveDirectory/Pages/_Host.cshtml)にも_Host.cshtmlで似たようなことが書いてあった。
> `<component type="typeof(App)" param-InitialState="tokens" render-mode="ServerPrerendered" />`

## 現時点で用意してある環境で試してみる

（サーバーを用意していないためほぼ意味がない）
Win11
IIS10
VS2022 .NET6

- 適当な場所にクラス「UserInfo」を用意しておく。
```csharp:UserInfo.cs
public class UserInfo
{
    public string UserAgent { get; set; }
    public string IPAddress { get; set; }
}
```

- 一応XFFを考慮しつつ、UserInfoをDI
``` csharp:Program.cs
// 略
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
// 略
builder.Services.AddScoped<UserInfo>();
// 略
app.UseForwardedHeaders();
// 略
```

- アプリのルートページでリクエスト元のIPアドレスを取得、ルートコンポーネントにパラメーターとして渡す。
``` csharp:_Host.cshtml
@using System.Net
@using Microsoft.AspNetCore.Authentication

@code {
    string ip = HttpContext.Connection?.RemoteIpAddress.ToString();

    // デバッグ実行してるとどうしても::1になる
    if (ip == "::1")
    {
        // TODO:AddressListは環境に依存してしまうので後で考える（ほとんどの環境で[2]なのではないだろうか・・・
        ip = Dns.GetHostEntry(Dns.GetHostName()).AddressList[3].ToString();
    }

    UserInfo _userInfo = new()
    {
        UserAgent = HttpContext.Request.Headers["User-Agent"],
        IPAddress = ip
    };
}

// param-InitialUserInfoの「InitialUserInfo」は後述のApp.razorでパラメーター定義
<component type="typeof(App)" param-InitialUserInfo="_userInfo" render-mode="ServerPrerendered" />
```

- ルートコンポーネントにDIされたUserInfoのインスタンスに、ルートページから渡された値を戻すと、子コンポーネントでも同じ値が得られる
```csharp:App.razor
@inject UserInfo UserInfo

@code {
    // ルートページから渡されるUserInfo
    [Parameter]
    public UserInfo InitialUserInfo { get; set; }

    protected override Task OnInitializedAsync()
    {
        // DIされたUserInfo
        UserInfo.IPAddress = InitialUserInfo.IPAddress;
        UserInfo.UserAgent = InitialUserInfo.UserAgent;

        return base.OnInitializedAsync();
    }
}
```

- IPアドレスを使用したいコンポーネントでUserInfoをDIしてもらえるように記述するだけ
```csharp:Index.razor
@inject UserInfo UserInfo
<p>@UserInfo.UserAgent</p>
<p>@UserInfo.IPAddress</p>
```

　
こんな感じで取れるはず
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726410/9140ac9d-9520-1953-8140-9bcc2515b909.png)

## 今後

### サーバー立てて検証する予定

BlazorServerをUbuntuにデプロイすることも気になっている。

各Razorコンポーネントに `@inject` を書くことすら億劫なのでちゃんと共有できるように書き直す。