こんにちは、社内SEのなんでも屋です。
前回の記事(#05)では、Webシステムの環境構築として Blazor Server + IIS の構成を選定したお話をしました。
今回は、システム開発の初期段階で私が見事に踏み抜いたデカすぎる地雷(ハマりポイント)についての解説です。
WinFormsやC#のデスクトップアプリ開発に慣れている方がBlazor Serverへ移行する際、99%の確率で事故りそうになる「static(静的メンバー)の使い方」について、実体験を交えて注意喚起と解決策をお伝えします!
発生したトラブル:「別の人の画面に私の名前が…?」
開発中、手元で動作確認をしていたときのことです。 テストユーザーAでログインし、別ブラウザ(シークレットウィンドウ)でテストユーザーBとしてログインした瞬間、恐ろしい現象が起きました。
- トラブル1:ユーザー情報の混ざり(なりすまし状態) ユーザーBの画面に、なぜか「ようこそ、ユーザーAさん」と表示される。
- トラブル2:DB接続先のごっちゃ混ぜ 開発環境とテスト環境の切り替えフラグを判定する処理で、タイミングによってテストDBと本番DBの接続情報が入れ替わり、テストデータが本番側に飛びそうになる。
「えっ……ブラウザ分わせてるのになんでデータが共有されてるの!?」と冷や汗が吹き出しました。
(web開発者にとっては当たり前だとは思うのですが、私の工場システムはほぼデスクトップアプリケーションでstatic多様が伝統であったこともあり・・・)
原因:WinFormsの癖による「static」の誤用
原因は至ってシンプル。私がデスクトップアプリ(Windows Forms)を開発していた頃の癖で、共通で使うデータや設定保持クラスに static(静的変数・静的クラス)を使っていたことです。
// 【NG例】WinFormsの感覚で書いてしまった共通クラス
public static class UserContext
{
// staticにすると、サーバー上で全ユーザー共有になってしまう!
public static string CurrentUserId { get; set; }
public static string DbConnectionString { get; set; }
}
なぜBlazor Serverでstaticを使ってはいけないのか?
- Windows Formsの場合: アプリ(.exe)はユーザーのPC上で個別に起動します。メモリ空間もユーザーごとに独立しているため、
static変数を使っても他のユーザーに影響を与えることはありません。 - Blazor Serverの場合: サーバー(IISなど)上の「単一のプロセス(メモリ空間)」で、接続してくる全ユーザーのSignalR通信を同時に処理しています。
つまり、Blazor Serverで static 変数を書き換えるということは、「サーバーにアクセスしている全ユーザーの画面の変数を裏で一斉に書き換える」ことと同義なのです。
解決策:DI(依存性注入)の「Scoped」を使おう!
Blazor Server(ASP.NET Core)で「ユーザーごとのセッション内で共通データを保持したい」場合は、static ではなく DI(Dependency Injection)の Scoped サービス を使用するのが鉄則です。
1. 状態保持クラスを「普通のクラス(non-static)」で作る
まずは static を外し、普通のインスタンス化できるクラスとして作成します。
// 【OK例】通常のクラスとして定義
public class UserState
{
public string CurrentUserId { get; set; }
public string DbConnectionString { get; set; }
}
2. Program.cs で「Scoped」として登録する
Program.cs にて、サービス登録を行う際に AddScoped を使用します。
// Program.cs
builder.Services.AddScoped<UserState>();
ASP.NET Coreにおけるサービスの生存期間(ライフタイム)には以下の3つがあります。
- Transient(一時的): 要求されるたびに新しいインスタンスを生成。
- Scoped(スコープ): 1つのWeb接続(Blazor Serverでは1つのユーザーセッション/Circuit)ごとに1つのインスタンスを保持。【★これを使う!】
- Singleton(単一): アプリ起動中、全ユーザーで1つのインスタンスを共有(※
staticに近い動きになるので注意!)。
3. Razorコンポーネントで @inject して使う
画面(.razor)側では、DIを使って注入して使用します。
Razor CSHTML
@inject UserState UserState
<h3>ようこそ、@UserState.CurrentUserId さん</h3>
@code {
protected override void OnInitialized()
{
// このユーザーのセッション内でのみ保持されるので混ざらない!
UserState.CurrentUserId = "User_A";
}
}
まとめ:Blazor Server開発者が心に刻むべき教訓
- Blazor Serverでの
staticは「全ユーザー共有のグローバル変数」になる! - ユーザー情報や接続情報など、「人によって変わる値」には絶対に
staticを使わない。 - セッションごとに共有したいデータは、
AddScopedでDIコンテナに登録して使う。
「デスクトップアプリの感覚のままWeb(特にBlazor Server)に参入すると、思わぬセキュリティ事故やデータ汚染に繋がる」という、身をもって知った非常に良い勉強(恐怖体験)になりました……。
これからBlazor Serverで開発を始める方は、ぜひこの static の罠に気を付けて実装してみてください!