Architect's Log

I'm a Cloud Architect. I'm highly motivated to reduce toils with driving DevOps.

【ASP.NET】Postback時にチェックボックスの状態を変更しようとすると「'length' は Null またはオブジェクトではありません。」のエラーが発生する

ASP.NETで、Postback時にチェックボックスの状態を変更しようとすると「'length' は Null またはオブジェクトではありません。」というエラーが発生しました。(チェックボックスがかなり多い画面です)

原因がわかったので、トラブルシュートの経緯を書き残しておきます。

MaxRequestLengthを超えてしまっているのでは?

最初に疑ったのは、ViewStateが大きすぎてMaxRequestLengthを超えてしまっているのでは?ということでした。

.NET 開発日記 » Blog Archive » ビューステートが大きすぎて12031エラーが発生。
G社のSpreadツールを用いてCheckBox付の業務一覧を表示していたが、ある日Scriptエラーが発生し、ボタン操作が不能になるという問い合わせが来た ...

 

しかし、MaxRequestLengthの設定値を大きくしても、現象は変わりませんでした。

Requestに含まれるフォームキーの上限を超えているのが原因だった 

次にエラーが出た状態で、画面遷移しようとしてみました。

すると、以下のエラーが発生しました。

Message:オブジェクトの現在の状態に問題があるため、操作は有効ではありません。
StackTrace: 場所 System.Web.HttpValueCollection.ThrowIfMaxHttpCollectionKeysExceeded()
場所 System.Web.HttpValueCollection.FillFromEncodedBytes(Byte[] bytes, Encoding encoding)
場所 System.Web.HttpRequest.FillInFormCollection()

"ThrowIfMaxHttpCollectionKeysExceeded"という、わかりやすい名前のメソッドで例外がスローされていたので、このメソッド名で検索してみました。

そして、以下のフォーラムがヒットし、最終的にKBにたどり着きました。

URL にエンコードされたフォーム データが有効ではありません - Visual Basic - Visual Studio User Group
多くのフォーム キー、ファイル、または JSON ペイロード メンバーを含む ASP.NET 要求が例外が発生して失敗する
Microsoft セキュリティ更新プログラム MS11-100 は、HTTP 要求内のフォーム キー、ファイル、および JSON メンバーの最大数を 1000 に制限します。この変更のために、ASP.NET アプリケーションは、1000 を超えるこれらの要素を含む要求を拒否します。 ...

 

KBを読んで、Web.configに以下の設定を追加し、MaxHttpCollectionの上限を変更すればいいことが、わかりました。

<add key="aspnet:MaxHttpCollectionKeys" value="2000" />

詳細な説明

MaxHttpCollectionの上限は、hashdosと呼ばれる攻撃に対応するために配布されたセキュリティ更新で導入されたものです。

(1000という上限は攻撃に必要なパラメータ数を下回ります)

ほとんどのWebサーバーに影響するメジャーなサービス拒否脆弱性
ASP.NETのパッチは、12月29日に提供されている。このパッチは、デフォルトサービスポリシーのWindows Azureの顧客には自動的に適用される。このパッチは、単一のPOSTフォームフィールド数を、サービス拒否の攻撃に必要な数を下回る1,000に制限する。 ...

hashdosについて

hashdosについては、以下のサイトが詳しいです。

Webアプリケーションに対する広範なDoS攻撃手法(hashdos)の影響と対策 | 徳丸浩の日記
これによると、PHPをはじめとする多くのWebアプリケーション開発プラットフォームに対して、CPU資源を枯渇させるサービス妨害攻撃(DoS攻撃)が可能な手法が見つかったということです。この攻撃は、hashdos と呼ばれています。 ...

 

hasdos攻撃が成功した場合の影響について引用します。

DoS攻撃の最中はCPU資源が攻撃に割り振られてしまい、サービスの提供が困難になると考えられます。Apache Killerと異なり、メモリ資源が枯渇するわけではないので、攻撃がやめば直ちにサービスは回復するようです。

参考サイト

Mikeのプログラミング・メモ: ASP.NETのセキュリティパッチ適用でエラー「Operation is not valid...」が発生する場合の対処

 

DataGridViewのDataBindingCompleteイベントが複数回発生する現象に対処する

DataGridViewのDataBindingCompleteイベントが複数回発生する現象に遭遇しました。
検索で次のブログがヒットしました。
DataGridView の DataBindingComplete イベントの発生回数

コード内で DataSource と DataMember の設定を行うとして、DataSource → DataMember の順で設定すると、DataBindingComplete イベントは三回発生する。
これを DataMember → DataSource の順で設定すると一回のみ発生する。
基本的に一回しか発生させたくないため、設定順序に注意。

検証

検証してみましょう。

DataSource→DataMemberの順で設定
using System;
using System.Data;
using System.Windows.Forms;

namespace DataGridView {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();

            dataGridView1.DataBindingComplete += (s, e) => Console.WriteLine("Complete!");

            this.Shown += (s, e) => {
                DataSet ds = new DataSet();
                DataTable dt = new DataTable("hoge");
                ds.Tables.Add(dt);
                dt.Columns.Add(new DataColumn("fuga", typeof(string)));
                dt.Rows.Add("a");

                // DataMemberを後に設定する。
                dataGridView1.DataSource = ds;
                dataGridView1.DataMember = "hoge";
            };
        }
    }
}


Complete!
Complete!
Complete!
3回イベントが発生しました。

DataMember→DataSourceの順で設定
using System;
using System.Data;
using System.Windows.Forms;

namespace DataGridView {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();

            dataGridView1.DataBindingComplete += (s, e) => Console.WriteLine("Complete!");

            this.Shown += (s, e) => {
                DataSet ds = new DataSet();
                DataTable dt = new DataTable("hoge");
                ds.Tables.Add(dt);
                dt.Columns.Add(new DataColumn("fuga", typeof(string)));
                dt.Rows.Add("a");

                // DataMemberを先に設定する。
                dataGridView1.DataMember = "hoge";
                dataGridView1.DataSource = ds;
            };
        }
    }
}


Complete!
イベントは1回しか発生しませんでした。

DataMemberを設定できない場合は?

IListなどDataMemberを設定できない(設定する必要がない)場合は、nullを設定すればOKです。

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace DataGridView {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();

            dataGridView1.DataBindingComplete += (s, e) => Console.WriteLine("Complete!");

            this.Shown += (s, e) => {
                List<string> list = new List<string> { "hoge" };

                // DataMemberを先に設定する。
                dataGridView1.DataMember = null;
                dataGridView1.DataSource = list;
            };
        }
    }
}


Complete!

エラーメッセージを英訳してくれるサイト「FindErr.NET」

エラー原因を調査するとき、日本語のエラーメッセージで検索してもわずかな件数しかヒットしないことがあります。
英語のエラーメッセージで検索したいけどわからない...そんなときはエラーメッセージを英訳してくれるサイト「FindErr.NET」を使ってみましょう。
SQL Server、.NET Framework、Windowsのエラーメッセージに対応しています。
Translate localized error messages from .NET, SQL and Windows - FindErr.NET



機能


「検索」か「エラーメッセージ一覧からの選択」のいずれかの方法で、エラーメッセージを英訳することができます。

検索

今回は「検索」で英訳してみましょう。

日本語のエラーメッセージを入力して[Search]をクリックすると、検索結果が表示されるので、リンクをクリックします。



英訳されたメッセージが表示されます。[Search]をクリックすると英訳メッセージでGoogle検索することができます。



検索結果が表示されました。

SQL Server 2008を手動でアンインストールする方法

SQL Server 2008のアンインストールに失敗してしまう場合は、コマンドラインからアンインストールする方法があります。

リンク先のページ下部に記述があります。
SQL Server 2008 を再度インストールすると SQL Server のインストールが失敗する
SQL Server 2008 を手動でアンインストールする

ビハインドコードのメソッドは、protectedにしないとaspxからは参照できない

ビハインドコードのメソッドは、protectedにしないとaspxのサーバーサイドロジックからは参照できません。

どういうこと?

  1. ビハインドコードにメソッドを作成し、アクセス修飾子をprivateまたはinternalにします。
  2. aspxの<%# 〜 %>に作成したメソッドを参照するコードを記述します。
  3. 実行すると、"CS0103: 名前 'HogeMethod' は現在のコンテキスト内に存在しません。"の例外が発生します。

どうして?

aspxのクラスはビハインドコードのクラスを継承しています。Hoge.aspxだとすると、継承関係は以下のようになります。
Hoge.aspx → Hoge.aspx.cs → System.Web.UI.Pageクラス

どうすれば?

アクセス修飾子をprotectedにします。publicでも参照できますが、スコープはできるだけ限定します。

ObjectDataSourceにはDataTableを双方向でデータバインドできない

ObjectDataSourceにはDataTableを双方向でデータバインドできません。

どういうこと?

以下のサンプルで説明します。

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication3._Default" %>

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

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
            DataObjectTypeName="WebApplication3.HogeDataSet+hogeDataTable" 
            DeleteMethod="Update" InsertMethod="Update" SelectMethod="GetData" 
            TypeName="WebApplication3.DataAccesser" UpdateMethod="Update"></asp:ObjectDataSource>
    </div>
    <asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" 
        AutoGenerateRows="False" DataSourceID="ObjectDataSource1" Height="50px" 
        Width="125px">
        <Fields>
            <asp:BoundField DataField="col1" HeaderText="col1" SortExpression="col1" />
            <asp:BoundField DataField="col2" HeaderText="col2" SortExpression="col2" />
            <asp:CommandField ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
    </form>
</body>
</html>
Default.aspx.cs
using WebApplication3.HogeDataSetTableAdapters;

namespace WebApplication3 {
    public partial class _Default : System.Web.UI.Page {
    }

    public class DataAccesser {
        public HogeDataSet.hogeDataTable GetData() {
            // HogeDataSetは型指定されたDataSetです。
            HogeDataSet.hogeDataTable t = new HogeDataSet.hogeDataTable();
            using(hogeTableAdapter a = new hogeTableAdapter()) {
                a.Fill(t);
            }
            return t;
        }

        public void Update(HogeDataSet.hogeDataTable dataTable) {
            using(hogeTableAdapter a = new hogeTableAdapter()) {
                a.Update(dataTable);
            }            
        }
    }
}
データ表示


データ表示は問題なくできます。

データ登録


しかし、データ登録をすると以下の例外が発生します。
"ObjectDataSource 'ObjectDataSource1' の DataObjectTypeName プロパティによって指定された型上の 'col1' というプロパティが見つかりませんでした。"

どうして?

DataTableに 'col1' というプロパティがないからです。

どうすれば?

Update、Delete、Insertを実装する場合は、面倒でもデータクラスを作成します。

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication3._Default" %>

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

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
            DataObjectTypeName="WebApplication3.Hoge" DeleteMethod="UpdateList" 
            InsertMethod="UpdateList" SelectMethod="GetList" 
            TypeName="WebApplication3.DataAccesser" UpdateMethod="UpdateList"></asp:ObjectDataSource>
    </div>
    <asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" 
        AutoGenerateRows="False" DataSourceID="ObjectDataSource1" Height="50px" 
        Width="125px">
        <Fields>
            <asp:BoundField DataField="col1" HeaderText="col1" SortExpression="col1" />
            <asp:BoundField DataField="col2" HeaderText="col2" SortExpression="col2" />
            <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" 
                ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
    </form>
</body>
</html>
Default.aspx.cs
using System.Collections.Generic;
using System.Web.UI.MobileControls;

namespace WebApplication3 {
    public partial class _Default : System.Web.UI.Page {
    }

    public class DataAccesser {
        public List<Hoge> GetList() {
            List<Hoge> hogeList = new List<Hoge>() {
                new Hoge() { col1 = 0, col2 = 1 },
                new Hoge() { col1 = 10, col2 = 11 },
            };

            return hogeList;
        }

        public void UpdateList(Hoge hoge) {
            // 更新処理を実装する
        }
    }

    /// <summary>
    /// 必要なプロパティを実装したデータクラス。
    /// </summary>
    public class Hoge {
        public int col1 { get; set; }
        public int col2 { get; set; }
    }
}

これで更新できるようになります。

ちなみに

スコット・ガスリー氏のブログでは以下のように述べられています。
Webフォームモデルバインディング パート1:データの選択 (ASP.NET vNextシリーズ) (1/2):CodeZine
本稿は、Scott Guthrie氏のブログを、氏の許可を得て、翻訳、転載したものです。米Microsoft社の副社長で、ASP.NETやSilverlightの開発チームを統率する氏のブログでは、次期製品を含む最新の技術をいち早く紹介しています。 ...

現在利用できる別のオプションは、ObjectDataSourceコントロールの使用です。このコントロールは、データアクセス層から、UIコードをよりクリーンに分別し、データコントロールがページングやソートなどの自動機能を提供できるようにします。しかし、データの選択では上手くいっても、双方向のデータバインディングを実施した時はまだ面倒で、簡単な(複雑な型の”深い”バインディングのない)プロパティだけしかサポートしなかったり、(エラー検証なども含む)多くのシナリオを処理するために、多くの複雑なコードを書かなければいけなかったりします。

以降2011/10/05追記

データクラスの代わりに型指定したDataRowが使えるのでは?

DataRowは引数のないPublicなコンストラクタがないので駄目です。

ObjectDataSource クラス (System.Web.UI.WebControls)
多階層 Web アプリケーション アーキテクチャで、データ バインド コントロールにデータを提供するビジネス オブジェクトを表します。 ...

ObjectDataSource コントロールは、リフレクションを使用してビジネス オブジェクトのインスタンスを作成し、作成したインスタンスに対してメソッドを呼び出してデータを更新、挿入、および削除します。