RazorPagesで入力値を配列プロパティにバインドしたが、IndexOutOfRangeExceptionが発生して動作しない - ASP.NET Core

RazorPagesで入力値を配列プロパティにバインドしたが、IndexOutOfRangeExceptionが発生して動作しない現象について紹介します。

概要

こちらの記事では、入力フィールドの値を配列プロパティにバインドして、 入力値を取得するコードを紹介しました。 しかし、実装状況によってはIndexOutOfRangeException例外が発生してうまく動作しない場合があります。 この記事では、配列プロパティにバインドした際にIndexOutOfRangeExceptionが発生するケースを紹介します。

現象の確認

ASP.NET Coreアプリケーションを作成し、以下のコードを記述します。

コード

/Pages/ArrayBindPropertyNotWork01.cshtml
@page
@model BindPropertyArray.Pages.ArrayBindPropertyNotWork01Model
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
@{
}
<html>
  <head></head>
  <body>
    <h3>配列のBindPropertyのデモ(0から始まらない例)</h3>

    <form method="post">
      <input type="text" asp-for="values[10]" /><br/>
      <input type="text" asp-for="values[11]" /><br/>
      <input type="text" asp-for="values[12]" /><br/>
      <input type="text" asp-for="values[13]" /><br/>
      <input type="text" asp-for="values[14]" /><br/>
      <input type="text" asp-for="values[15]" /><br/>
      <input type="submit" value="Post" />
    </form>

    <hr/>
    <p>@Model.out1</p>
    <p>@Model.out2</p>
    <p>@Model.out3</p>
    <p>@Model.out4</p>
    <p>@Model.out5</p>
    <p>@Model.out6</p>

  </body>
</html>
/Pages/ArrayBindPropertyNotWork01.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace BindPropertyArray.Pages
{
  public class ArrayBindPropertyNotWork01Model : PageModel
  {
    [BindProperty]
    public string[] values { get; set; }

    public string out1;
    public string out2;
    public string out3;
    public string out4;
    public string out5;
    public string out6;

    public ArrayBindPropertyNotWork01Model()
    {
      values = new string[64];
    }

    public void OnGet()
    {
    }

    public void OnPost()
    {
      out1 = values[10];
      out2 = values[11];
      out3 = values[12];
      out4 = values[13];
      out5 = values[14];
      out6 = values[15];
    }


  }
}
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.Run();

解説

ページ部分で、テキストボックスの値をページモデルクラスのプロパティに反映しますが、この時インデックスを10から開始しています。
    <form method="post">
      <input type="text" asp-for="values[10]" /><br/>
      <input type="text" asp-for="values[11]" /><br/>
      <input type="text" asp-for="values[12]" /><br/>
      <input type="text" asp-for="values[13]" /><br/>
      <input type="text" asp-for="values[14]" /><br/>
      <input type="text" asp-for="values[15]" /><br/>
      <input type="submit" value="Post" />
    </form>

ページモデルクラスでは、配列の要素を64個確保していますので、10から開始しても配列外の要素にアクセスする状況にはなりません。
    public ArrayBindPropertyNotWork01Model()
    {
      values = new string[64];
    }

実行結果

プロジェクトを実行し(アプリケーションルート)/ArrayBindPropertyNotWork01 のURLにアクセスします。下図のページが表示されます。


テキストボックス6つに値を入力します。入力ができたら[Post]ボタンをクリックします。


ボタンをクリックすると、例外が発生し実行が中断します。
以下のエラーメッセージが表示されます。
エラーメッセージ
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'




values配列の値を確認すると、長さが0になっており、値が全く入っていないことが確認できます。

現象の確認 (配列が0から開始し、疎な場合)

続いて、バインドする値が配列の0から開始するものの、途中で抜けがある場合の実行結果を確認します。

コード

ASP.NET Coreアプリケーションを作成し、以下のコードを記述します。
/Pages/ArrayBindPropertyNotWork02.cshtml
@page
@model BindPropertyArray.Pages.ArrayBindPropertyNotWork02Model
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
@{
}
<html>
  <head></head>
  <body>
    <h3>配列のBindPropertyのデモ(0から始まり、不連続な例)</h3>

    <form method="post">
      <input type="text" asp-for="values[0]" /><br/>
      <input type="text" asp-for="values[1]" /><br/>
      <input type="text" asp-for="values[4]" /><br/>
      <input type="text" asp-for="values[6]" /><br/>
      <input type="text" asp-for="values[10]" /><br/>
      <input type="text" asp-for="values[16]" /><br/>
      <input type="submit" value="Post" />
    </form>

    <hr/>
    <p>@Model.out1</p>
    <p>@Model.out2</p>
    <p>@Model.out3</p>
    <p>@Model.out4</p>
    <p>@Model.out5</p>
    <p>@Model.out6</p>

  </body>
</html>
/Pages/ArrayBindPropertyNotWork02.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace BindPropertyArray.Pages
{
    public class ArrayBindPropertyNotWork02Model : PageModel
    {

    [BindProperty]
    public string[] values { get; set; }

    public string out1;
    public string out2;
    public string out3;
    public string out4;
    public string out5;
    public string out6;

    public ArrayBindPropertyNotWork02Model()
    {
      values = new string[64];
    }


    public void OnGet()
        {

        }

    public void OnPost()
    {
      out1 = values[0];
      out2 = values[1];
      out3 = values[4];
      out4 = values[6];
      out5 = values[10];
      out6 = values[16];
    }

  }
}

Progarm.cs は先ほどの例と同じコードです。

解説

ページ部分で、テキストボックスの値をページモデルクラスのプロパティに反映します。インデックスは0から開始しますが、インデックスの2,3は抜けており、 配列にとびとびに値を設定しています。
    <form method="post">
      <input type="text" asp-for="values[0]" /><br/>
      <input type="text" asp-for="values[1]" /><br/>
      <input type="text" asp-for="values[4]" /><br/>
      <input type="text" asp-for="values[6]" /><br/>
      <input type="text" asp-for="values[10]" /><br/>
      <input type="text" asp-for="values[16]" /><br/>
      <input type="submit" value="Post" />
    </form>

実行結果

プロジェクトを実行します。下図のページが表示されます。


テキストボックスに値を入力します。入力ができたら、[OK]ボタンをクリックします。


ボタンをクリックすると、例外が発生し実行が中断します。
以下のエラーメッセージが表示されます。先ほどと同様の例外メッセージです。
エラーメッセージ
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'



values配列の値を確認すると、長さは2になっており、最初の2つのテキストボックスの値はvalues配列に代入されていますが、 3つめからのテキストボックスの値は代入されていていないことが確認できます。

原因

RazorPagesで配列のプロパティに代入する場合は、配列の要素は0から開始する必要があり、かつ、配列に連続して値を設定する必要があります。 値の設定されていない要素があった場合、値の設定されていない要素以降の値はページ遷移時に設定されません。

対処法

配列プロパティに代入する場合は、配列の0番目から値を設定します。また、途中が抜けないよう、連続して要素に値を代入します。

実際の例

配列プロパティに空きができて代入してしまうケースの具体例として、次の実装があります。
/Pages/ArrayBindPropertyNotWork03.cshtml
@page
@model BindPropertyArray.Pages.ArrayBindPropertyNotWork03Model
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
@{
}
<html>
  <head></head>
  <body>
    <h3>配列のBindPropertyのデモ(動的表示の例)</h3>

    <form method="post">
    @{
      for (int i=0; i<16; i++){
        if (Model.enabled[i] == true) {
          <input type="text" asp-for="values[i]" /><br/>
        }
      }
    }
          <input type="submit" value="Post" />
    </form>
  </body>
</html>
/Pages/ArrayBindPropertyNotWork03.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace BindPropertyArray.Pages
{
  public class ArrayBindPropertyNotWork03Model : PageModel
  {

    [BindProperty]
    public string[] values { get; set; }

    [BindProperty]
    public bool[] enabled { get; set; }

    public string[] outValue;

    public ArrayBindPropertyNotWork03Model()
    {
      values = new string[64];
      enabled = new bool[64];
      for (int i = 0; i < 64; i++) {
        values[i] = "";
        enabled[i] = false;
      }

      outValue = new string[64];
    }

    public void OnGet()
    {
      enabled[0] = true;
      enabled[1] = true;
      enabled[2] = true;
      enabled[8] = true;
      enabled[10] = true;
      enabled[12] = true;
    }

    public void OnPost()
    {
      for (int i = 0; i < 16; i++) {
        outValue[i] = values[i];
      }
    }

  }
}

解説

Razor Pagesの処理で、ページモデルのenabled配列の値がtrueであった場合に、配列に対応するテキストボックスを画面に表示する動作を実装している例です。
    <form method="post">
    @{
      for (int i=0; i<16; i++){
        if (Model.enabled[i] == true) {
          <input type="text" asp-for="values[i]" /><br/>
        }
      }
    }
          <input type="submit" value="Post" />
    </form>

画面に表示されるテキストボックスの項目は、ページモデルのOnGetメソッドで決められます。
今回の例では単純にするため、enabledにtrueを単純に代入していますが、実際の実装では、条件やDBの値によってenabledの値を設定します。
    public void OnGet()
    {
      enabled[0] = true;
      enabled[1] = true;
      enabled[2] = true;
      enabled[8] = true;
      enabled[10] = true;
      enabled[12] = true;
    }

プロジェクトを実行しページを表示すると下図の画面が表示されます。

この時のぺージのコードは以下の通りで、配列の途中が抜けていることが確認できます。
<html>
  <head></head>
  <body>
    <h3>配列のBindPropertyのデモ(動的表示の例)</h3>

    <form method="post">
          <input type="text" id="values_0_" name="values[0]" value="" /><br/>
          <input type="text" id="values_1_" name="values[1]" value="" /><br/>
          <input type="text" id="values_2_" name="values[2]" value="" /><br/>
          <input type="text" id="values_8_" name="values[8]" value="" /><br/>
          <input type="text" id="values_10_" name="values[10]" value="" /><br/>
          <input type="text" id="values_12_" name="values[12]" value="" /><br/>
          <input type="submit" value="Post" />
    <input name="__RequestVerificationToken" type="hidden" value="(トークン値)" /></form>
    <hr/>

  <script src="/_framework/aspnetcore-browser-refresh.js"></script></body>
</html>

RazorPagesで入力値を配列プロパティにバインドする際には実装方法に注意する必要があります。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2022-07-17
作成日: 2022-07-17
iPentec all rights reserverd.