ASP.NET Web FormsでRequest.QueryString[0]使用時にIndexOutOfRangeExceptionが発生する原因と安全なリダイレクト実装

ASP.NET Web Forms でクエリ文字列を使って別ページにリダイレクトするとき、ちょっとした書き方の違いで IndexOutOfRangeException が発生することがあります。この記事では、Request.QueryString[0] を使った典型的な落とし穴と、安全かつ拡張性の高いリダイレクト方法を、具体的なサンプルコードとともに丁寧に解説します。

目次

IndexOutOfRangeException が発生する典型的な状況

今回のケースは次のようなイメージです。

  • URL のクエリ文字列から id パラメータを取得したい
  • id が存在すれば Request.aspx?id=… にリダイレクト
  • なければ Request.aspx にリダイレクト

ありがちな実装例は次のようなコードです。


protected void Page_Load(object sender, EventArgs e)
{
    // クエリ文字列の最初の要素を見てみる
    if (Request.QueryString[0] != null)
    {
        Response.Redirect("Request.aspx?id=" + Request.QueryString[0]);
    }
    else
    {
        Response.Redirect("Request.aspx");
    }
}

一見すると「クエリ文字列の 0 番目が存在すればそれを使う」という意図に見えますが、QueryString に 1 件も要素が無い状態で [0] にアクセスしているため、次のような例外が発生します。

Index was out of range. Must be non‑negative and less than the size of the collection.

つまり、配列やコレクションの範囲外アクセスが原因です。このエラーは ASP.NET 特有というより、C# 全般に共通する「インデックス越境」のエラーです。

Request.QueryString の仕組みと落とし穴

まず、Request.QueryString の正体を把握しておきましょう。

  • Request.QueryString は NameValueCollection 型
  • キー名(文字列)でもインデックス(整数)でもアクセスできる
  • クエリが 1 つも無い場合、Count == 0 であり [0] へのアクセスは例外

代表的な URL と動作を表にまとめると次の通りです。

URL の例QueryString.CountRequest.QueryString[0]想定される挙動
/Request.aspx0アクセス不能IndexOutOfRangeException が発生
/Request.aspx?id=101“10”正常に取得できる
/Request.aspx?id=10&foo=bar2“10”(最初のキーの値)id が先頭なら期待通りに見える
/Request.aspx?foo=bar&id=102“bar”id ではなく foo の値になる

この表から分かるように、インデックスでのアクセスは存在チェックもしづらく、順番に依存してしまうため、実務ではおすすめできません。

要素数を確認してからアクセスする基本パターン

どうしてもインデックスでアクセスしたい場合は、必ず件数を確認してからアクセスする必要があります。


protected void Page_Load(object sender, EventArgs e)
{
    if (Request.QueryString.Count > 0)  // 要素が 1 件以上あるか?
    {
        Response.Redirect("Request.aspx?id=" + Request.QueryString[0]);
    }
    else
    {
        Response.Redirect("Request.aspx");
    }
}

この修正により、QueryString が空のときに [0] を読むことがなくなるため、IndexOutOfRangeException は解消されます。

ただし、この書き方には次の問題が残ります。

  • クエリの順番に依存している(最初のパラメータが id とは限らない)
  • 複数のクエリパラメータがあるときに意図しない値を拾う可能性がある

そのため、実務的にはキー名("id")で値を取得する方法に切り替えることを強くおすすめします。

キー名で取得する方が安全で読みやすい

より安全かつ可読性の高い書き方は、クエリパラメータのキー名で値を取得する方法です。


protected void Page_Load(object sender, EventArgs e)
{
    var id = Request.QueryString["id"];  // キー名で取得

    if (!string.IsNullOrEmpty(id))
    {
        Response.Redirect($"Request.aspx?id={id}");
    }
    else
    {
        Response.Redirect("Request.aspx");
    }
}

この書き方のメリットとデメリットをまとめると次のようになります。

取得方法書き方メリットデメリット
インデックスRequest.QueryString[0]一見短く書ける順番に依存する 空のとき例外が出る 後から読むと意図が分かりにくい
キー名Request.QueryString["id"]順番に依存しない 存在しない場合は null になるだけで例外にならない 後から見ても意図が分かりやすいキー名のタイプミスに注意が必要

とくに保守の観点では、「何番目か」ではなく「何という名前のパラメータか」でコードを書いた方が、後続の開発者にも親切です。

文字列連結時は UrlEncode を忘れずに

クエリ文字列に値を埋め込む場合は、URL エンコードも重要なポイントです。ユーザー入力をそのまま URL に載せると、次のような問題が起こりえます。

  • スペースや日本語、記号が含まれていると URL として無効になる
  • & や = が混ざるとクエリ構造が壊れる

そのため、次のように HttpUtility.UrlEncode を併用するのが安全です。


using System.Web;

protected void Page_Load(object sender, EventArgs e)
{
    var id = Request.QueryString["id"];

    if (!string.IsNullOrEmpty(id))
    {
        var encoded = HttpUtility.UrlEncode(id);
        Response.Redirect($"Request.aspx?id={encoded}");
    }
    else
    {
        Response.Redirect("Request.aspx");
    }
}

こうしておくと、日本語 ID やメールアドレスなどをクエリに載せる場合でも安心です。

無限リダイレクトを避ける設計

今回のようなリダイレクト処理は、配置場所を間違えると無限リダイレクトを引き起こします。たとえば、上記コードを Request.aspx 自身の Page_Load に書くと、次のような流れになります。

  1. ブラウザが /Request.aspx を開く
  2. Request.aspx の Page_Load が実行され、Response.Redirect("Request.aspx") が呼ばれる
  3. 再び /Request.aspx にアクセス → 1 に戻る

結果として、ブラウザ上では「ページがリダイレクトを繰り返しています」といったエラーが表示されます。

無限ループを避ける 3 つの方法

方法概要向いている場面
別ページからリダイレクト中継用ページ(例:Gate.aspx)を用意して、そこでクエリを整形してから Request.aspx に飛ばす構成変更が容易な新規開発
現在ページを判定Request.CurrentExecutionFilePath 等を使って「今いるページ」である場合はリダイレクトしない既存ページに処理を追加したい場合
フラグをクエリやセッションに保存「整形済み」の印としてフラグを持ち、2 回目以降はリダイレクトを行わない複雑な遷移制御が必要な場合

別ページで処理する例

最もシンプルなのは、Start.aspx のような中継ページを作る方法です。


// Start.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
    var id = Request.QueryString["id"];

    if (!string.IsNullOrEmpty(id))
    {
        var encoded = HttpUtility.UrlEncode(id);
        Response.Redirect($"Request.aspx?id={encoded}");
    }
    else
    {
        Response.Redirect("Request.aspx");
    }
}

この場合、Request.aspx 側には特別なリダイレクト処理を置かないため、無限ループは発生しません。

現在ページを判定してリダイレクトを止める例

どうしても同じページの Page_Load で処理したい場合は、「自分自身にリダイレクトしているか」を判定する必要があります。


protected void Page_Load(object sender, EventArgs e)
{
    // 現在のページパス(例:/Request.aspx)
    var currentPath = Request.CurrentExecutionFilePath;

    // 既に Request.aspx であればリダイレクトしない
    if (currentPath.EndsWith("Request.aspx", StringComparison.OrdinalIgnoreCase))
    {
        return;
    }

    var id = Request.QueryString["id"];

    if (!string.IsNullOrEmpty(id))
    {
        var encoded = HttpUtility.UrlEncode(id);
        Response.Redirect("Request.aspx?id=" + encoded);
    }
    else
    {
        Response.Redirect("Request.aspx");
    }
}

このように条件を設けておけば、一度 Request.aspx に到達した後はリダイレクトが止まるので、ブラウザの警告画面を回避できます。

Page_PreLoad と Page_Load の違いと例外発生タイミング

「Page_PreLoad に書くか、Page_Load に書くかで例外の有無が変わるのでは?」と考えがちですが、今回の IndexOutOfRangeException はイベントのタイミングとは無関係です。

  • Request.QueryString[0] は、ページライフサイクルのどのイベントでも同じように動く
  • クエリが空なら Page_PreLoad でも Page_Load でも例外になる
  • あくまで「インデックス越境」の問題であり、ライフサイクルの問題ではない

したがって、イベントを変えるのではなく、配列越境しないようにコードを修正することが本質的な解決策です。

より堅牢な実装にするための追加ポイント

実際の業務システムでは、単に「例外が出ない」だけでなく、不正値や欠損値に対してどう振る舞うかも重要です。ここでは、もう一歩踏み込んだ改善案を紹介します。

id を数値として扱う場合

id が数値(整数)であることを前提とするケースは多いでしょう。その場合は、整数変換の成否もチェックしておくと安心です。


protected void Page_Load(object sender, EventArgs e)
{
    var idText = Request.QueryString["id"];

    if (!string.IsNullOrEmpty(idText) && int.TryParse(idText, out var id))
    {
        // id は整数として有効
        var encoded = HttpUtility.UrlEncode(idText);
        Response.Redirect($"Request.aspx?id={encoded}");
    }
    else
    {
        // id が無い、または不正な値なのでプレーンなページへ
        Response.Redirect("Request.aspx");
    }
}

これにより、「id=abc」のような不正な値が渡されたときでも、例外を出さずに安全側に倒すことができます。

QueryString のデバッグに役立つテクニック

原因調査で迷ったときは、まず 実際にどんなクエリが届いているかを確認しましょう。


protected void Page_Load(object sender, EventArgs e)
{
    // デバッグ用:全てのクエリパラメータをログ出力
    foreach (string key in Request.QueryString.Keys)
    {
        var value = Request.QueryString[key];
        System.Diagnostics.Debug.WriteLine($"key={key}, value={value}");
    }

    // ここから本処理…
}

Visual Studio の出力ウィンドウで値を確認できるため、「そもそも本当に id が来ているのか?」を素早く判断できます。

実務でそのまま使えるサンプル実装

ここまでの内容を踏まえ、実務で使いやすい形にまとめたサンプルコードを示します。

サンプル 1:単純な id 付きリダイレクト


protected void Page_Load(object sender, EventArgs e)
{
    var id = Request.QueryString["id"];

    // id があれば Request.aspx?id=xxx に、なければ Request.aspx へ
    if (!string.IsNullOrEmpty(id))
    {
        var encoded = HttpUtility.UrlEncode(id);
        Response.Redirect("Request.aspx?id=" + encoded, false);
        Context.ApplicationInstance.CompleteRequest();
    }
    else
    {
        Response.Redirect("Request.aspx", false);
        Context.ApplicationInstance.CompleteRequest();
    }
}

Response.Redirect(url, false) と CompleteRequest を組み合わせることで、スレッドを強制終了せずにリダイレクトできます。大規模アプリケーションではパフォーマンス・ログ出力の観点からも有利です。

サンプル 2:現在ページを考慮したループ回避版


protected void Page_Load(object sender, EventArgs e)
{
    var currentPath = Request.CurrentExecutionFilePath;

    // すでに Request.aspx にいるときは何もしない
    if (currentPath.EndsWith("Request.aspx", StringComparison.OrdinalIgnoreCase))
    {
        return;
    }

    var id = Request.QueryString["id"];

    if (!string.IsNullOrEmpty(id))
    {
        var encoded = HttpUtility.UrlEncode(id);
        Response.Redirect("Request.aspx?id=" + encoded, false);
        Context.ApplicationInstance.CompleteRequest();
    }
    else
    {
        Response.Redirect("Request.aspx", false);
        Context.ApplicationInstance.CompleteRequest();
    }
}

このように条件を整えておけば、IndexOutOfRangeException も無限リダイレクトも同時に回避できます。

よくある失敗パターンとその対処

最後に、ASP.NET Web Forms でクエリ文字列を扱うときにありがちな失敗パターンを整理しておきます。

失敗パターン例問題点対処法
空の QueryString にインデックスアクセスRequest.QueryString[0]要素数 0 のとき IndexOutOfRangeExceptionCount チェック、もしくは ["id"] でアクセス
パラメータ順に依存Request.QueryString[0] を id と決め打ち後からクエリ順序が変わると誤動作キー名でアクセスして順序依存をなくす
自ページへの無条件リダイレクトResponse.Redirect("Request.aspx") を Request.aspx に記述無限リダイレクトループ別ページに切り出す/現在ページを判定する
URL エンコードの欠如"Request.aspx?id=" + id日本語・記号で URL が壊れる可能性HttpUtility.UrlEncode でエンコード
不正な値の未チェックint id = int.Parse(Request.QueryString["id"])id=abc などで例外int.TryParse で検証してから利用

まとめ

ASP.NET Web Forms でリダイレクト時に IndexOutOfRangeException が発生する原因の多くは、Request.QueryString へのインデックス越境アクセスにあります。とくに Request.QueryString[0] のような書き方は、クエリが無い場合に例外を発生させるだけでなく、クエリ順に依存するため保守性も低くなりがちです。

この記事で紹介したポイントを押さえておけば、同様のトラブルはほぼ確実に避けられます。

  • Request.QueryString[0] ではなく Request.QueryString["id"] を使う
  • 必要に応じて Count や string.IsNullOrEmpty、int.TryParse で存在チェックと妥当性チェックを行う
  • リダイレクト先が自ページになっていないかを確認し、必要ならページ判定や中継ページで無限ループを防ぐ
  • クエリ文字列に埋め込む値は HttpUtility.UrlEncode でエンコードする

このように、「例外を潰す」のではなく「そもそも例外が出ない設計にする」ことが、安定した Web Forms アプリケーションを作る上での近道です。既存コードで Request.QueryString[0] のような書き方を見かけたら、今回の内容をヒントにリファクタリングを検討してみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次