WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

GridView 内の RadioButton

by WebSurfer 2013年8月30日 14:02

GridView の 1 行のみを選択する目的で、GridView の各行に RadioButton を配置する場合の注意点を備忘録として書いておきます。

GridView 内の RadioButton

あるラジオボタンのグループで、単一のラジオボタンしか選択できないようにするには、そのグループの中のラジオボタンの name 属性の値を同一にします。

ところが GridView に 配置した RadioButton の場合、サーバー側のコードで name 属性が同一になるように設定しても、それからレンダリングされる input 要素の name 属性の値は ASP.NET によって書き換えられ、全て異なった値になります。結果、ラジオボタンなのに複数の行が選択できてしまいます。

理由は以下の通りです。

名前付けコンテナー(INamingContainer インターフェイスを実装するコントロール)内にコントロールが配置されていると、ASP.NET は名前付けコンテナーの ID をコントロールの ID に追加して ClientID 値を生成します。

GridView の場合、その個別の行をあらわす GridViewRow が名前付けコンテナーであり、それから HTML の繰り返しブロックがレンダリングされますが、その中に配置された RadioButton には連続番号を含むプレフィックスが追加され、ページの中で一意になる(同じ ID が無い)ように ClientID 値が生成されます。

RadioButton の場合は name 属性も生成され、その値もページの中で一意になるよう、名前付けコンテナーの ID がプレフィックスとして追加されます。(ClientID とは命名規則が異なりますが、ページ内で一意になる点は同じです)

例えば、今回の記事の例の場合、GridView 内のラジオボタンの name は GridView1$ctl02$RadioButton1 というようになり、その中で ctl02 の部分が行によって異なります。

ASP.NET が name の値を生成するタイミングは、PreRender イベントが完了した後、html コードをレンダリングする時のようです。従って、それ以前にサーバー側で name 属性をいかように設定しても、ブラウザに送信される html コードは名前付けコンテナーの ID が追加されたものに書き換えられてしまいます。

なので、RadioButton.GroupName プロパティを設定するとか、GridView の RowCreated や PreRender イベントなどのハンドラで全ての RadioButton の name 属性を同一に書き換えるなどしても効果はありません。

何か方法はないか探してみましたが、サーバー側で設定する方法は見つけられませんでした。

結局、ブラウザが html コードの読み込みを完了した後、クライアントスクリプトで当該ラジオボタンの name 属性を同一に書き換えてやることで対応しました。

以下のサンプルコードのような感じです。jQuery を利用すると比較的簡単にできます。ただし、当然ですが、ブラウザで JavaScript が無効になっているとダメです。

自分が見つけてない、サーバー側のコードで対応できる、もっと簡単な方法があるかもしれません。

2013/8/31 追記:
愚直に name 属性の値を書き換えるより簡単な方法がありました。詳しくはこの記事の下の方の「------ 2013/8/31 追記 ------」を見てください。また。サーバー側だけで対応する方法も書いておきました。

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  protected void Button1_Click(object sender, EventArgs e)
  {
    string ids = "";
    for (int i = 0; i < GridView1.Rows.Count; i++)
    {
      GridViewRow row = GridView1.Rows[i];
      RadioButton rb = 
        (RadioButton)row.FindControl("RadioButton1");
      if (rb != null)
      {
        if (rb.Checked == true)
        {
          ids += GridView1.DataKeys[i].Value.ToString() + " ";
        }
      }
    }
    Label1.Text = "Setected product(s): " + ids;
  }

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <script src="Scripts/jquery-1.8.3.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    // サーバー側で設定されたラジオボタンの name 属性
    // のオリジナル値を保存しておくための配列。
    var originalNames;

    // ラジオボタンの name 属性の元の値を配列に保存した
    // 上で、同一(この例では radiobutton1)に書き換え。
    $(function () {
      originalNames = new Array();
      $('#<%=GridView1.ClientID%> input:radio').each(
        function () {
          originalNames.push($(this).attr('name'));
          $(this).attr('name', 'radiobutton1');
        });
    });

    // ポストバックする前に、ラジオボタンの name 属性を
    // オリジナル値に戻すためのメソッド。オリジナル値に
    // 戻さないとチェックされたラジオボタンをサーバー側
    // で取得できず、かつ、再描画されたときにチェックマ
    // ークがつかない。
    // この例では Button1.OnClientClick プロパティにこの
    // メソッドを設定している。
    function RetrieveOriginalNames() {
      $('#<%=GridView1.ClientID%> input:radio').each(
        function (n) {
          $(this).attr('name', originalNames[n]);
        });
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand=
        "SELECT [ProductID], [ProductName], [UnitPrice] 
        FROM [Products] 
        WHERE ([CategoryID] = @CategoryID)">
      <SelectParameters>
        <asp:Parameter DefaultValue="1" 
          Name="CategoryID" Type="Int32" />
      </SelectParameters>
    </asp:SqlDataSource>

    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1">
      <Columns>
        <asp:TemplateField HeaderText="選択">
          <ItemTemplate>
            <asp:RadioButton ID="RadioButton1"
              runat="server" />
          </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
      </Columns>
    </asp:GridView>

    <asp:Button ID="Button1" runat="server" 
      Text="Button" 
      OnClick="Button1_Click" 
      OnClientClick="RetrieveOriginalNames();" />
    <br />
    <asp:Label ID="Label1" runat="server"></asp:Label>
  </div>
  </form>
</body>
</html>

------ 2013/8/31 追記 ------

name 属性は書き換えないでそのままにしておいて、あるラジオボタンをチェックしたら他のラジオボタンのチェックを外すようにする方が簡単でした。以下のような感じです。

<script src="Scripts/jquery-1.8.3.js" type="text/javascript">
</script>
<script type="text/javascript">
//<![CDATA[
  // ラジオボタンがチェックされたら、一旦全てのラジオ
  // ボタンのチェックを外し、チェックされたラジオボタ
  // ンに改めてチェックを入れ直す方がはるかに簡単。
  $(function () {
    $('#<%=GridView1.ClientID%> input:radio').change(
      function () {
        $('#<%=GridView1.ClientID%> input:radio').
          removeAttr("checked");
        $(this).attr('checked', 'checked');
      });
  });

  // Button1 クリックで name を元の値に書き戻す必要は
  // なくなる。
//]]>
</script>

その他、クライアントスクリプトを使わないでサーバー側だけで対応する方法として、RadioButton を継承したカスタムコントロールを作る方法があります。その例は以下のページを見てください。

データバインドコントロール内で使用できるカスタムラジオボタンの作成

DataGrid内のラジオボタンでグルー���に出来ない問題の回避方法

Tags: ,

ASP.NET

Object 型と列挙型引数のオーバーロード

by WebSurfer 2013年8月10日 12:48

SqlParameterCollection.Add メソッド には以下のオーバーロードがあります。

  • Add (Object)
  • Add (SqlParameter)
  • Add (String, SqlDbType)
  • Add (String, Object)
  • Add (String, SqlDbType, Int32)
  • Add (String, SqlDbType, Int32, String)

このうち、Add(String, Object) は Add(String, SqlDbType) との曖昧さの問題で廃止され、代わりに AddWithValue メソッド の使用が推奨されています。

何が曖昧かと言うと、MSDN ライブラリの SqlParameterCollection.Add メソッド (String, Object) の説明に書いてありますように、第 2 引数に 0(整数ゼロ)を渡すと、Add (String, Object) ではなくて、Add (String, SqlDbType) オーバーロードが呼び出されることです。

第 2 引数に 0 を渡せばもちろん、(int)0 でも Add (String, SqlDbType) オーバーロードが呼び出されます。

Add (String, Object) オーバーロードを呼び出すには、第 2 引数に Convert.ToInt32(0) または (object)0 を渡さなければなりません。

何故でしょう? 以下のような理由だと思います。(多少推測があります)

まず、SqlDbType が列挙型というところが要注意です。MSDN ライブラリ Enumeration Types (C# Programming Guide) によると、0(整数ゼロ)は列挙型のデフォルト値とのことで、特別な意味を持つようです。

それゆえ、0 や (int)0 は SqlDbType 列挙型のデフォルト値と判断されるらしく、Add (String, Object) ではなくて、Add (string, SqlDbType) オーバーロードが呼ばれます。

Convert.ToInt32(0) は(Object 型から派生した)int 型、(object)0 は Object 型と判断されて、Add (String, Object) オーバーロードが呼ばれます。

ちなみに、もし Add (String, Int32) というオーバーロードがあったとすると、第 2 引数は 0 でも (int)0 でも Convert.ToInt32(0) でも、Add (String, Int32) オーバーロードが呼ばれるはずです。

検証するため以下のサンプルを作ってみましたが、サンプルコード内のコメントにあるように期待通りの結果となりました。

namespace MyConsoleApplication
{
    enum Days { Sat, Sun, Mon, Tue, Wed, Thu, Fri };

    class TestClass
    {
        public void Add1(object obj)
        {
            Console.WriteLine("Add1(object)");
        }

        public void Add1(Days d)
        {
            Console.WriteLine("Add1(Days)");
        }

        public void Add2(object obj)
        {
            Console.WriteLine("Add2(object)");
        }

        public void Add2(Days d)
        {
            Console.WriteLine("Add2(Days)");
        }

        public void Add2(int i)
        {
            Console.WriteLine("Add2(int)");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            TestClass tc = new TestClass();

            tc.Add1(0);
            tc.Add1(1);
            tc.Add1((int)0);
            tc.Add1(Convert.ToInt32(0));
            tc.Add1((object)0);

            Console.WriteLine("--------------------");

            tc.Add2(0);
            tc.Add2(1);
            tc.Add2((int)0);
            tc.Add2(Convert.ToInt32(0));
            tc.Add2((object)0);

            // 結果は:

            // Add1(Days)
            // Add1(object)
            // Add1(Days)
            // Add1(object)
            // Add1(object)
            // --------------------
            // Add2(int)
            // Add2(int)
            // Add2(int)
            // Add2(int)
            // Add2(object)
        }        
    }
}

Tags: ,

.NET Framework

MVC は maxJsonLength を無視

by WebSurfer 2013年8月7日 13:32

Ajax を使用する際、クライアントとサーバー間でやり取りするデータには JSON (JavaScript Object Notation) 形式の UTF-8 文字列を使うのがデファクトスタンダードになっています。

ASP.NET Web Forms アプリケーションで Ajax を使って Web サービスと通信を行う場合も、先の記事 ASP.NET AJAX と Web サービスjQuery AJAX と Web サービス でも書きましたように、データのやり取りには JSON 形式の文字列を使っています。

クライアントからサーバーへ送信できる JSON 文字列の長さは、セキュリティ対策のため、デフォルトで 102,400 文字に制限されています。この値は、web.config の jsonSerialization 要素 (ASP.NET 設定スキーマ) の設定によって変更できます。以下のような感じです。

<system.web.extensions>
    <scripting>
        <webServices>
            <jsonSerialization maxJsonLength="3000000"/>
        </webServices>
    </scripting>
</system.web.extensions>

上の maxJsonLength 要素の設定により、JSON 文字列のデシリアライズに使用されている(と思われる)JavaScriptSerializer クラスの MaxJsonLength プロパティの設定が書き換えられるようで、デフォルト値の 2,097,152 文字を超えて JSON 文字列を送信することも可能です。(どこまで可能かは未検証ですが、2,097,152 文字を超えて可能なのは ASP.NET 4 Web Forms および MVC3 で確認しました)

ところが、呼び出す相手が Web サービスのメソッドでなく、MVC のアクションメソッドの場合、web.config の maxJsonLength 要素の設定は無視され、文字数制限は JavaScriptSerializerクラスの MaxJsonLength プロパティのデフォルト値 2,097,152 文字となります。

このあたりは、いろいろググって調べている時に、stackoverflow の記事 Can I set an unlimited length for maxJsonLength in web.config? を見つけて分かりました。そのページの Answers の 2 つ目に書いてあります。

いろいろ調べましたが、クライアントからサーバーに送信される JSON 文字列の長さ制限を変更する方法は見つかりませんでした。JavaScriptSerializer クラスの MaxJsonLength プロパティのデフォルト値 2,097,152 文字で固定となってしまいます。

ただし、逆方向のサーバーからクライアントに送信される JSON 文字列の長さ制限(MaxJsonLength プロパティのデフォルト値 2,097,152 文字)を緩和するための workaround はあります。詳しくは、上に紹介した stackoverflow のページに書いてありますので見てください。

クライアントからサーバーに送信する JSON データの文字数の制限を変更する方法はないか調べているときにわかったこと(何故変更できないか)を以下に書いておきます。

MVC のアクションメソッドを呼び出す場合、2,097,152 文字を超えてクライアントからサーバーにJSON データを送信すると「ArgumentException: JSON JavaScriptSerializer を使用したシリアル化または逆シリアル化中にエラーが発生しました。文字列の長さが maxJsonLength プロパティで設定されている値を超えています。」というエラーとなります。

スタックトレースを見ると JavaScriptSerializer クラスの DeserializeObject メソッドで JSON 文字列を受け取って、それデシリアライズするときに例外(ArgumentException・・・その条件の一つが「input の長さが MaxJsonLength の値を超えています。」)が発生したことが分かります。

さらに、スタックトレースから、そのメソッドは JsonValueProviderFactory クラスの GetDeserializedObject メソッドで使われているのが分かります。CodePlex のサイトで、その ソースコード を調べたら以下のようになっていました。

private static object GetDeserializedObject(
                    ControllerContext controllerContext)
{
  if (!controllerContext.HttpContext.Request.
          ContentType.StartsWith("application/json", 
                       StringComparison.OrdinalIgnoreCase))
  {
    // not JSON request
    return null;
  }

  StreamReader reader = new StreamReader(
        controllerContext.HttpContext.Request.InputStream);
  string bodyText = reader.ReadToEnd();
  if (String.IsNullOrEmpty(bodyText))
  {
    // no JSON data
    return null;
  }

  JavaScriptSerializer serializer = new JavaScriptSerializer();
  object jsonData = serializer.DeserializeObject(bodyText);
  return jsonData;
} 

上のコードの通り、GetDeserializedObject メソッドで使われている JavaScriptSerializer オブジェクトの MaxJsonLength プロパティの設定を変更する手段はないので、MVC のアクションメソッドを呼び出している限りは制限を 2,097,152 文字を超えて設定する手段はなさそうです。

どうしても 2,097,152 文字を超えて設定する必要があれば(実際、そんな必要があるのか分かりませんが)、アクションメソッドではなく、web サービス(.asmx)を作ってそのメソッドを呼び出すようにして、web.config の maxRequestLength 要素を 2,097,152 文字を超えた値に設定することで対応が可能です。

aspnet:MaxJsonDeserializerMembers について

上記の JSON 文字数の制限に加えて、2011 年 12 月 30 日に公開されたセキュリティ更新プログラム MS11-100 で HTTP 要求内の JSON メンバーの最大数がデフォルトで 1,000 に制限されるようになりました。

この 1,000 という数は、クライアントからサーバーに送信される JSON オブジェクト内の key:value アイテムの総数です。

デフォルト値 1,000 の変更は、web.config の ASP.NET appSettings 要素で aspnet:MaxJsonDeserializerMembers キーを設定することで可能です。

この制限を越えると JsonValueProviderFactory の EntryLimitedDictionary.Add メソッドで InvalidOperationException がスローされます。

ちなみにエラーメッセージは「InvalidOperationException: JSON リクエストが大きすぎるため、逆シリアル化できませんでした。」となります。

なお、web サービス(.asmx)のメソッドにも aspnet:MaxJsonDeserializerMembers による制限が有効かどうかは未確認です。

Tags: , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar