ようこそゲストさん

mitc - 日記

2007/11/28(水) ASP.NETのValidator理解のための覚え書き

はてブ 2007/11/28 15:10 .NETmiff
OSWindows Vista
VisualStudio2005 + Vistaパッチ
.NET2.0
ブラウザIE7
言語JavaScript
CustomValidatorであれこれしようとした時にValidatorの裏側を利用しないと細かい制御ができない気がしたので,
見えている部分から動作を追った過程をメモっておきます.後で使うかもしれない.
ところで,ASP.NETが生成するファイル内のコードをここに載せて良かったのかが微妙です.
でも完全に見えてる部分だし……ということで一部抜粋でお茶を濁してます.

それから,こういう情報を使って色々やるのは多分あんまり良くないです.
.NET Frameworkのバージョンが変わったら突然中身ががらっと変わったりするかもしれませんし.

説明は多分わかりにくいので「まとめ」を読むと何がしたかったかわかるかも.

前提

Validatorの処理を追うために,次のようなページを作って吐き出されるコードを確かめます.

ValidatorSample.png

Validatorを使わなかった時に吐き出されるHTMLファイル

例えば,次のような.aspxファイルを想定します.
<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_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:TextBox runat="server" ID="sample" Text="" />
    <asp:Button runat="server" ID="proc" Text="処理" /> 
    </div>
    </form>
</body>
</html>
これの実行結果は,次のようになります.
<!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><title>
	無題のページ
</title></head>
<body>
    <form name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTkwNjc4NTIwMWRkg5+ZqZBT5NE9RSOzHkJ74iIs2Ng=" />
</div>

    <div>
    <input name="sample" type="text" id="sample" />
    <input type="submit" name="proc" value="処理" id="proc" /> 
    </div>
    
<div>

	<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwKk0aJiAtit3+ECAr+LlP8FrKEque5agT/Q2ul3EP/4V38MLSc=" />
</div></form>
</body>
</html>
特にJavaScriptなどを必要とするような処理は無いので,VIEWSTATEなどの値が埋め込まれている以外は.aspxファイルと大差ない状態です.

Validatorを使った時に吐き出されるHTMLファイル

.aspxファイルににValidatorを追加してみます.
   <asp:RequiredFieldValidator ControlToValidate="sample" runat="server" ID="validatorSample" ErrorMessage="必須です" Display="dynamic" />
すると,実行結果が次のように変わります.
<!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><title>
	無題のページ
</title></head>
<body>
    <form name="form1" method="post" action="Default.aspx" onsubmit="javascript:return WebForm_OnSubmit();" id="form1">
<div>
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTEwODY4OTY1NTNkZKshFeGKAvCYkhIv6mSQVzOcfOYt" />
</div>

<script type="text/javascript">
<!--
var theForm = document.forms['form1'];
if (!theForm) {
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
// -->
</script>


<script src="/SampleDotNet/WebResource.axd?d=L6u_cog5rGt8k4C4jGFXZw2&amp;t=633197954615849000" type="text/javascript"></script>
<script src="/SampleDotNet/WebResource.axd?d=QPItuxfSgpBNWFhnb65V-hKYAPPunmZkKIqFSvfsED81&amp;t=633197954615849000" type="text/javascript"></script>
<script type="text/javascript">
<!--
function WebForm_OnSubmit() {
if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false;
return true;
}
// -->
</script>

    <div>
    <input name="sample" type="text" id="sample" />
   <span id="validatorSample" style="color:Red;display:none;">必須です</span>
    <input type="submit" name="proc" value="処理" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;proc&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" id="proc" /> 
    </div>
    
<script type="text/javascript">
<!--
var Page_Validators =  new Array(document.getElementById("validatorSample"));
// -->
</script>

<script type="text/javascript">
<!--
var validatorSample = document.all ? document.all["validatorSample"] : document.getElementById("validatorSample");
validatorSample.controltovalidate = "sample";
validatorSample.errormessage = "必須です";
validatorSample.display = "Dynamic";
validatorSample.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
validatorSample.initialvalue = "";
// -->
</script>

<div>

	<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwKxwaX3CALYrd/hAgK/i5T/BcT3PPGh/LvzqMoLvuHKENrF981H" />
</div>

<script type="text/javascript">
<!--
var Page_ValidationActive = false;
if (typeof(ValidatorOnLoad) == "function") {
    ValidatorOnLoad();
}

function ValidatorOnSubmit() {
    if (Page_ValidationActive) {
        return ValidatorCommonOnSubmit();
    }
    else {
        return true;
    }
}
// -->
</script>
        </form>
</body>
</html>
かなり多くの要素が追加されていることがわかると思います.

検証処理の本体がどこにあるか

ボタンクリック時に検証が実行される仕組みは,次の部分で実行されます.
   <iinput type="submit" name="proc" value="処理" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions( (略)
ボタンのonclickイベントで関数を呼び出しているわけですね.
ところが,HTML中をいくら探してもここで呼び出しているWebForm_DoPostBackWithOptions関数はみつかりません.
これは,ASP.NETにより自動的に追加され,呼び出されている二つのjsファイル内に記述されているからです.
<script src="/SampleDotNet/WebResource.axd?d=L6u_cog5rGt8k4C4jGFXZw2&amp;t=633197954615849000" type="text/javascript"></script>
<script src="/SampleDotNet/WebResource.axd?d=QPItuxfSgpBNWFhnb65V-hKYAPPunmZkKIqFSvfsED81&amp;t=633197954615849000" type="text/javascript"></script>
jsファイルと書きましたが,実際にどこかにjsファイルが存在するわけではなく,WebResource.axdに引数を渡して生成したデータをブラウザに読み込ませる仕組みになっています.
WebResource.axdは,こういうものを生成するためにASP.NETに組み込まれているシステムらしいですがよくわかりません.
一方には,ASP.NETが提供するJavaScriptを使ったシステムを動かすための基本的なコードが入っています.
もう一方には,クライアントサイドの検証システムをJavaScriptで動かすためのコードが入っています.
処理の流れを確認する時には,これらのURLにアクセスし,jsファイルを実際に手に入れて覗いてみると参考になります.
以後,このエントリでは簡単のために検証システムが入っているWebResource.axdから提供されるコードをValidatorコード,もう一方のコードをSystemコードと呼びます*1

次の項目以降は,これらの中身などを参考にしてクライアントサイド検証の流れを確認します.

*1 : センスが悪くてごめんなさい.多分正式名称は別にあると思いますが

初期化処理

初期化処理は,ページが読み込まれた時に実行されます.

1. ページ内のValidatorを取得

ページ内のValidatorを要素に持つ配列を作成します.
<script type="text/javascript">
<!--
var Page_Validators =  new Array(document.getElementById("validatorSample"));
// -->
</script>
ここで取得しているIDがvalidatorSampleの要素の実体は,次のようにspan要素になっています.
<span id="validatorSample" style="color:Red;display:none;">必須です</span>
複数のValidatorがページ内に存在しているような場合は,次のような形で複数のValidatorが登録されます.
var Page_Validators =  new Array(document.getElementById("validatorSample"), document.getElementById("rangeValidatorSample"));

2. ページ内のValidatorオブジェクトに検証用の設定を追加

ページ内にあるValidatorのオブジェクトに属性を追加します.
<script type="text/javascript">
<!--
var validatorSample = document.all ? document.all["validatorSample"] : document.getElementById("validatorSample");
validatorSample.controltovalidate = "sample";
validatorSample.errormessage = "必須です";
validatorSample.display = "Dynamic";
validatorSample.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
validatorSample.initialvalue = "";
// -->
</script>
手順1で配列に登録しているものと同じspan要素のオブジェクトに.aspxで指定した属性を設定しています.
例えば,今回は次のようなValidatorを用意しましたが,上記のJavaScriptコードと見比べると対応していることがわかると思います.
<asp:RequiredFieldValidator ControlToValidate="sample" runat="server" ID="validatorSample" ErrorMessage="必須です" Display="dynamic" />

3. Validatorの初期化

ValidationActive変数をfalseにしておき,
ValidatorOnLoad関数を呼び出して初期化を行っています.
ValidatorOnLoad関数が存在しなかったり初期化に失敗した場合は,
ValidationActive変数がtrueにならないので,
後の処理でこれをチェックすることでエラーの発生を防いでいます.
<script type="text/javascript">
<!--
var Page_ValidationActive = false;
if (typeof(ValidatorOnLoad) == "function") {
    ValidatorOnLoad();
}
ValidationOnLoad関数は,Validatorコードの中に入っています.
中身を簡単に書くと,1番の処理で作成し,2番の処理でその各要素に検証用の設定を追加したPage_Validators配列の各要素を見て次のような処理を行っています.
  • isvalidプロパティを追加し,trueにする
  • ValidatorHookupControlID -> ValidatorHookupControl -> ValidatorHookupEvent を呼び出し,検証対象のコントロールに検証用イベントを追加する
この辺の詳細は,実際にコードを見た方が早いかな.

PostBack前の一括検証処理

検証処理は,テキストボックスに値を入力する,ラジオボタンを選択するなど入力を行った直後と,
ボタンなどをクリックしてポストバックを行う直前のタイミングで行われます.
前者は,初期化時に設定したイベント経由で実行されるので,ここでは後者の処理について書きます.

Validatorを追加すると,ボタンのonclickに次のような関数が追加されます.
<input type="submit" name="proc" value="処理" onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;proc&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" id="proc" /> 
WebForm_DoPostBackWithOptionsとWebForm_PostBackOptionsは,Systemコードに含まれています.
onClickのところは,見やすく書くとこんな感じ.
onclick="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("proc", "", true, "", "", false, false)

WebForm_DoPostBackWithOptions

WebForm_PostBackOptionsオブジェクトに記された情報を使ってPostBackを実行します.
WebForm_DoPostBackWithOptionsのコードで重要なのは,次の部分です.
if (options.validation) {
    if (typeof(Page_ClientValidate) == 'function') {
        // ページ内のvalidationGroupに所属するValidatorを一括起動
        validationResult = Page_ClientValidate(options.validationGroup);
    }
}
これで,validationGroupに所属するvalidatorを一括で起動する方法がわかります.
validationResultには,検証の結果エラーが無ければtrue,あればfalseが返っているようです.
WebForm_PostBackOptions
引数に使われているWebForm_PostBackOptionsは,次のような感じです.
function WebForm_PostBackOptions(eventTarget, eventArgument, validation, validationGroup, actionUrl, trackFocus, clientSubmit)
詳しい意味については,MSDN2のここを参照して下さい.
検証処理に直接関わるのは,validation(検証処理を実行するかどうか),validationGroup(検証対象のvalidationGroup)です.

Page_ClientValidate

Page_ClientValidateは,Validatorコードに入っています.
このコードで注目したいのは次の部分.
    for (i = 0; i < Page_Validators.length; i++) {
        // Validatorを起動
        ValidatorValidate(Page_Validators[i], validationGroup, null);
    }
    // 全Validatorの検証結果を確認
    ValidatorUpdateIsValid();
    ValidationSummaryOnSubmit(validationGroup);
Validatorの初期化処理によってPage_Validators配列には,Validatorの一覧が入っているわけですから,
1. 全Validatorを起動
2. Validatorの検証結果を保存
3. ValidationSummary*2を更新

ValidatorUpdateIsValid関数は,全Validatorの検証結果をチェックして,
一つでもエラーがあればグローバル変数のPage_IsValidにfalseを入れているだけです.

*2 : 各Validatorに表示されるErrorMessageをまとめて一覧表示できるコントロール

個別の検証処理

一括検証処理のコードを追うことで,個別の検証処理を行うには,ValidatorValidate関数を使えばいいことがわかりました.
function ValidatorValidate(val, validationGroup, event) {
    ~~
    検証処理
    ~~
    ValidatorUpdateDisplay(val);
}
検証処理では,Validatorコントロールで設定したクライアントサイドの検証用関数を呼び出してtrue/falseで結果を取得し,
Validator自身のisvalidプロパティに格納しています.
ValidatorUpdateDisplayは,渡されたValidatorのisvalidプロパティがtrueかfalseかで検証が成功したかどうかを判別し,
検証が失敗していれば,ErrorMessageを表示したり表示してあったErrorMessageを消したりします.

まとめ

まとめのようなまとめじゃないような.
上のを一切読まなくても問題無いと思います.

ページ内のValidatorを手動で一括起動したい

Page_ClientValidate関数を呼び出せば,引数で渡したvalidationGroupに所属するValidatorを起動し,
検証した結果入力が不正ならエラーメッセージを表示できます.
戻り値がtrueなら検証OK,falseならどれかの入力が不正と判定されたことになります.
var validationResult = false;
validationResult = Page_ClientValidate("検証対象のvalidationGroup");

特定のValidatorを手動で呼び出したい

ValidatorValidate関数を呼び出せば,対象のValidatorを検証できます.
エラーメッセージの表示もOK.
// 検証対象のValidatorを取得する.ここではgetElementByIdで取得しているが,方法は何でもOK.
var validationTarget = document.getElementById('validatorID');
var validationGroup = 'mainValidationGroup';
ValidatorValidate(validationTarget, validationGroup, null);
// 結果の取得は,検証したValidatorのisvalidプロパティに入っている
var validationResult = validationTarget.isvalid;

特定のValidatorをエラー状態にしたい

例えば,あるValidatorがエラーならばそれに関係する他のValidatorも無条件でエラーにしたい時などに使えます.

具体的には,次の図のようなページがあったとします.
MultiValidatorSample.png
そして,この時の検証項目を「Aの文字数+Bの文字数+Cの文字数 < 100」としたいとします.
この時,「Aの検証に失敗した時」→「Aの文字数かBの文字数かCの文字数を減らさないといけない」わけで,BとCでもエラーを表示したい!と考えると,このような処理で実装するのが一つのやり方ということです.
他にもやり方はあると思いますが…….

他にも使い道はあると思います.

次のような感じにすると実現できます.
isvalid=falseにし,ValidatorUpdateDisplayで表示を更新.
var validationTarget = document.getElementById('validatorID');
validationTarget.isvalid = false;
ValidatorUpdateDisplay(val);


名前:  非公開コメント   

E-Mail(任意/非公開):
URL(任意):
  • TB-URL  http://mitc.s279.xrea.com/diary/070/tb/