WinForms の DataGridView CellClick に慣れた業務SEが ASP.NET Razor の onclick で戸惑う3つの構造差

みなさんこんにちは!ヒロポンです!

WinForms の DataGridView。行をクリックすると CellClick が飛んできて、e.RowIndex でその場で行が取れる。業務系の画面を作ってきた人なら、息をするように書いてきたやつですよね。

その感覚のまま ASP.NET Razor の onclick を書くと、見事に詰まります。俺も最初これで半日溶かしました。「ボタン押したのにハンドラが動かん」。「押すたびに値が消える」。極めつけは「e.RowIndex はどこ行った??」って。

この記事は、前に書いたDataGridView のクリック3アクションの続き。Web 側に足を伸ばす入口です。asp.net razor onclick でつまずく構造差を3点、WinForms の既知の言葉で翻訳していきます。脅さないので安心してください。

目次

忙しい人向けに最初にまとめ

  • 構造差1: サーバへ往復する — WinForms のクリックは同一プロセス内で即ハンドラ。Razor の onclick は HTTP でサーバへ往復する。
  • 構造差2: ページは毎回作り直される — クリックのたびにページが新しく生成される。状態は自分で持ち回る(TempData / セッション / DB)。
  • 構造差3: e.RowIndex がない — イベント引数オブジェクトは来ない。行IDを hidden や data 属性に埋めて、引数として受け取る。

この記事の位置づけ

DataGridView のクリック3アクションでは、WinForms の中だけでクリックを捌く話を書きました。今回はその次の一歩。同じ「行をクリックして処理する」を Web(Razor)でやるとどう変わるかです。

WinForms を知ってる人ほど、Web は「なんか別世界」に見える。でも、変わるのは3つだけ。そこさえ押さえれば、CellClick 脳のままでも Razor は書けます。

対応マップ — CellClick と Razor onclick を3観点で並べる

まず全体像から。「どこで動くか・状態は誰が持つか・イベント引数の受け取り方」。この3観点で並べると、違いがくっきりします。

WinForms DataGridView CellClick と ASP.NET Razor onclick を、どこで動くか・状態は誰が持つか・イベント引数の受け取り方の3観点で対比した早見表

e.RowIndex で即取れる世界と、行IDを自分で運ぶ世界。ここが一番デカい差です。順番に見ていきます。

構造差1: イベントはサーバへ往復する

WinForms のクリックは、同一プロセス内で即ハンドラが呼ばれます。

// WinForms: クリックは同一プロセス内で即ハンドラが呼ばれる
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
    if (e.RowIndex < 0) return;                 // ヘッダクリックは除外
    var row = dataGridView1.Rows[e.RowIndex];   // 行はメモリ上にある
    var id = row.Cells["ID"].Value;             // その場で値が取れる
    MessageBox.Show($"選択ID: {id}");
}

クリックした瞬間、メモリ上の行にアクセスできる。ネットワークも HTTP も挟まらない。速い。

Razor は違います。onclick でサーバの処理を動かすには、HTTP でサーバへ往復する。ボタンを <form> で囲んで POST する。これが基本形です。

<!-- Razor: 行ごとにフォーム。クリックでサーバへ POST する -->
@foreach (var item in Model.Items)
{
    <form method="post">
        <input type="hidden" name="id" value="@item.Id" />
        <button type="submit" asp-page-handler="Select">@item.Name</button>
    </form>
}
// PageModel: POST されて初めてサーバ側ハンドラが動く
public class IndexModel : PageModel
{
    public IActionResult OnPostSelect(int id)   // 引数で行IDを受け取る
    {
        // ここはクリックの「後」。ページは作り直されている
        TempData["Message"] = $"選択ID: {id}";
        return RedirectToPage();                // PRG パターン
    }
}

この往復の流れ、図にすると一発で腑に落ちます。

WinForms は同一プロセスで即実行、Razor はブラウザからサーバへ HTTP POST で往復してハンドラが動く流れのシーケンス図

WinForms は U=B=S が全部同じプロセスにいる。Razor は B(ブラウザ)と S(サーバ)が HTTP で隔てられてる。この一本の線が分かると、残り2つの差も全部つながります。

構造差2: ページは毎回作り直される

WinForms のフォームは、開いてる間ずっとメモリにいます。変数も、DataGridView の中身も、閉じるまで保持される。

Razor は逆です。クリック(POST)のたびにページが新しく作り直される。さっき画面に出てた値も、サーバ側のインスタンス変数も、次のリクエストではきれいに消えてます。

だから「ボタン押したら値が消えた」が起きる。HTTP がステートレスだからです。

状態を残したいなら、自分で持ち回る。手段はこのへん。

// 状態の持ち回り方(用途で使い分け)
TempData["Message"] = msg;     // 次の1リクエストだけ持たせる(PRG とセット)
HttpContext.Session.SetInt32("SelectedId", id);  // セッション中ずっと
// または、毎回 DB / hidden から復元する(いちばん素直)

WinForms 脳だと「変数に入れときゃ残るやろ」で書いて詰まる。Web では「残らないのが前提、残したいなら明示」。ここの発想の転換がいい感じにハマると、一気に楽になります。

構造差3: e.RowIndex に相当するものをどう受け取るか

WinForms の e.RowIndex は、クリックされた行番号をイベント引数が勝手に持ってきてくれる。親切な世界です。

Razor には、この「イベント引数オブジェクト」がありません。e は来ない。じゃあどうやって「どの行が押されたか」を知るのか。自分で行IDを送るんです。

さっきの構造差1で <input type="hidden" name="id" value="@item.Id" /> を埋めて、ハンドラの引数 OnPostSelect(int id) で受け取ってましたよね。あれが Razor 流の e.RowIndex です。

サーバへ往復させず、JavaScript で完結させたいなら data- 属性で渡す手もあります。

// JS で完結させる場合: data-id を読んで fetch で送る
document.querySelectorAll(".row-btn").forEach(btn => {
  btn.addEventListener("click", async () => {
    const id = btn.dataset.id;            // e.RowIndex の代わりに data-id
    await fetch(`/api/select/${id}`, { method: "POST" });
  });
});

つまり Razor では、「行を識別する情報を、自分で HTML に埋めて、自分で取り出す」。e.RowIndex という名前のサービスが消えただけで、やることは同じです。慣れればいい感じに手が動きます。

💡 Form_Load にあたるものが Razor でどこに行ったかはASP.NET で Form_Load に相当する処理はどこに書くかにまとめてます。イベントの「入口」の話なので、この記事とセットでどうぞ。

ミニマム検証 — 最小の Razor onclick を動かす

理屈だけだと不安ですよね。コピペで動く最小形を置いときます。Index.cshtmlIndex.cshtml.cs の2枚だけ。

// Index.cshtml.cs — ハンドラと状態の持ち回りだけの最小形
public class IndexModel : PageModel
{
    public string? Message { get; private set; }

    public void OnGet() { }                   // 初回表示
    public void OnPostSelect(int id)          // ボタン押下時
    {
        Message = $"選択された行ID: {id}";    // この値はこのリクエスト内だけ生きる
    }
}
<!-- Index.cshtml — ボタンと結果表示 -->
<form method="post">
    <input type="hidden" name="id" value="42" />
    <button type="submit" asp-page-handler="Select">42番を選ぶ</button>
</form>
<p>@Model.Message</p>

ボタンを押すと POST が飛んで OnPostSelect(42) が動く。Message に値が入って再描画される。こんな感じで、WinForms の CellClick 一発が、Razor では「POST → ハンドラ → 再描画」の3ステップになる。これが体で分かれば勝ちです。

ハマりポイント — WinForms の癖のまま書くと詰まる所

俺が実際にやらかした順に3つ。

  • ハンドラが動かない: asp-page-handler="Select"OnPostSelect の名前がズレてた。Razor はメソッド名で紐づくので、OnPost + ハンドラ名が一致してないと無言で何も起きない。WinForms のデザイナ自動紐づけに慣れてると、ここで30分溶かす。
  • 値が消える: インスタンス変数に入れて「次のクリックでも残ってるやろ」と思ったら毎回リセット。POST ごとに IndexModel が作り直されてた。状態は TempData か DB へ逃がす。
  • 二重送信: POST しっぱなしでリロードしたら同じ処理が2回走った。RedirectToPage()(PRG パターン)で受け止めるのが定番。

俺の現場メモ

WinForms から Web に移った時、いちばんしんどかったのが「状態が残らない」感覚でした。

WinForms は、画面を開いてる間ずっと変数が生きてる安心感がある。それが当たり前すぎて、Web で消えると「ん?さっきの値どこ行った??」ってなる。あれね……地味に効くんですよ。

腑に落ちたのは、「Web のサーバは、リクエストが来た瞬間だけ働いて、返したら忘れる」と割り切ってから。毎回作り直される前提で、残したいものだけ明示的に持ち回る。そう考えたら、Razor の onclick が WinForms の CellClick の素直な親戚に見えてきました。

まとめ

CellClick 脳のまま Razor で詰まるのは、だいたいこの3つが原因です。

  • サーバへ往復する — クリックは HTTP を越えてサーバで動く
  • ページは毎回作り直される — 状態は自分で持ち回る
  • e.RowIndex がない — 行IDは自分で埋めて自分で取る

逆に言うと、この3つさえ翻訳できれば、WinForms で培ったイベント設計の勘はそのまま Web で効きます。別世界じゃない。地続きです。一歩踏み出せば、業務SEの守備範囲がぐっと広がりますよ!!

よくある質問

Q1. Razor の onclick は毎回サーバへ往復しますか?

<form> の submit やハンドラーメソッドを使う場合は往復します。ページ内で完結させたいなら JavaScript の addEventListener + fetch でクライアント側だけで処理できます。サーバの状態を更新したいか、画面の見た目だけ変えたいか。そこで選びます。

Q2. WinForms の e.RowIndex みたいに行番号で取れないんですか?

Razor には行番号を運ぶイベント引数がありません。行を識別したいなら、行IDを hidden input や data-id 属性に埋めて送り、ハンドラーの引数で受け取ります。「番号」より「ID」で運ぶ。これが Web 流です。

Q3. ボタンを押すたびに前の値が消えます。どうすれば残せますか?

HTTP はステートレスなので、POST ごとにページが作り直されて値が消えます。TempData(次の1リクエストだけ)、セッション(その人のブラウザ単位)、DB(永続)のいずれかで持ち回ってください。一覧の再表示なら、毎回 DB から取り直すのが一番素直で事故りません。

Q4. WinForms の経験は Web で無駄になりますか?

なりません。イベント駆動でユーザー操作を捌く設計の勘は、そのまま使えます。変わるのは「どこで動くか・状態を誰が持つか・引数の渡し方」の3点だけ。土台の考え方は地続きです。

次に読むべき記事

WinForms から Web に足を伸ばそうとしてる同業がいたら、この記事ぶん投げてやってください。どんどんシェア待ってるぜ!!

以上!

執筆者

バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次