ようこそゲストさん

mitc - 日記

2007/12/11(火) ASP.NET2.0とかの手探りなあれこれ

はてブ 2007/12/10 20:13 .NETmiff

マスタページ適用状態で独自のスタイルシートを追加

要するに,マスタページを使ってコンテンツページを作っている時にそのページだけで使うスタイルシートを読み込むようにしようと思ってもHTMLのヘッダ部にContentPlaceHolderは入れられないしどうしようということ.

次のページに答えが.
@Prog! - ASP.NET(C#) - - マスターページのヘッダーにスタイルシートファイルや Javascript ファイルを追加する

説明が載っていないので補足すると,次の手順.

SampleMasterPage.master(マスタページ)
<%@ Master Language="VB" CodeFile="SampleMasterPage.master.vb" Inherits="MasterPage" %>

<!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 id="Header" runat="server">
    <title>サンプルマスタページ</title>
    <link rel="stylesheet" href="default.css" type="text/css" media="all" />
</head>
<body>
.
.
.
ヘッダタグにidを追加.(原文はコードがC#)
Contents.aspx.vb(コンテンツページのコードビハインドファイル)
protected Sub Page_Load(object sender, EventArgs e)
      Dim literal As Literal = new Literal()
      literal.Text = "<link rel='stylesheet' type='text/css' href='StyleSheet.css' />"

      Dim hh As HtmlHead = DirectCast(Page.Master.FindControl("Header"),HtmlHead)
      hh.Controls.Add(literal);
End Sub
マスタページは,Page.Masterでアクセスできるので,そこにリテラルを追加.
本当は,こんな方法よりも全体の構成を考えてスタイルシートの割り振りなどを考えた方が良かったと後で思いました.
ただ,それができない状態でもあったのでそういう時に使えるのかもしれません.

GridViewを使う

便利だけれど,初見だととても使いにくいDataGridView
ASP.NET版DataGridViewと言えるGridViewは,便利さも使いにくさもDataGridViewと同じ感じです.
しかし,ぱっと見ただけでも魅力的な機能が満載なので使わないのはもったいない!

私が便利だと思ったのは……
  • SqlDataSourceと関連づけるとコーディングレスでDB内のデータを一覧表示可能
  • ページ送り機能をワンクリックで実装可能
  • ソート機能もワンクリックで実装可能
  • TemplateFieldを使えば表示の柔軟性を高くできる
逆に不便だったのは,DataGridViewと同じようにGridViewに標準で備わっている機能以上のことをやろうとすると途端に大変になることです.

私が使ったバッドノウハウ的なものをいくつか書いておきます.

チェックボックスを配置

TemplateFieldにチェックボックスを載せる.
<asp:TemplateField HeaderText="選択">
	<ItemTemplate>
		<asp:CheckBox ID="chkProc" runat="server" />
	</ItemTemplate>
</asp:TemplateField>

全チェックボックスを選択状態にする/解除する(JavaScript)

Japan.internet.com デベロッパー - GridView上のすべてのチェックボックスをオンにする方法
このサイトに載っています.

チェックされている行を全取得

' IDがSampleGridViewのGridViewのTemplateField内に
' IDがchkProcのCheckBoxが配置されているとします
Public Function getCheckedRows() As DataRow()
	Dim result As New List(Of DataRow)()
	
	' 全行を対象にする
	For Each line As GridViewRow In Me.SampleGridView.Rows
		' ヘッダやフッタは対象外
		If line.RowType = DataControlRowType.DataRow Then
			' 行内のチェックボックスを取得
			Dim checkbox As CheckBox = DirectCast(line.FindControl("chkProc"), CheckBox)
			' チェックが入っていたなら,その行と関連づけられているDataRowを取得
			If checkbox.Checked = True Then
				lines.Add(DirectCast(line.DataItem, DataRowView).Row)
			End If
		End If
	Next
	
	' 配列として返す
	Return result.ToArray()
End Function
非常にシンプルです.
SqlDataSourceとGridViewが結び付いている場合は,
行に関連づけられたDataRowを取り出せるので処理が楽になります.

逆に,DataRowが取り出せなかったりDataRowには入っていないけれど行内で保持している情報を返したい場合は,
lines.Add(DirectCast(line.DataItem, DataRowView).Row)
この部分を変えることでどんな風にでもできます.

HTMLの自動エスケープを停止

自由にタグを入れられないのが不便な場合はそのカラムの自動エスケープを停止することで回避します.
該当カラムのHtmlEncodeプロパティをFalseにすればOK.
ただし必要箇所に自分でエスケープ処理を入れるのはお忘れなく.

データベースから引っ張ってきた値を加工して表示する

GridView イベント (System.Web.UI.WebControls)

例えばテーブル名SAMPLEのカラムSAMPLE_NUMBERの値が1なら「晴天」,2なら「雨天」をGridViewのカラムに表示することを考えます*1
まず,自分で勝手に値を表示するためにDataSourceと結び付いていないカラムを用意します.
この場合は,テンプレートフィールドにラベルを仕込むと良さそうです.
<asp:TemplateField HeaderText="お天気">
	<ItemTemplate>
		<asp:Label ID="lblWEATHER" runat="server" />
	</ItemTemplate>
</asp:TemplateField>
GridViewのRowDataBoundイベントを利用すると,
GridViewの各行とDataSourceが結び付いた時に
その行とその行に結び付くデータを参照しながら作業ができるので簡単です.

と,言葉で書いてもわかりにくいのでコードを見て下さい.
#{' RowDataBoundイベント}
Protected Sub SampleGridView_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles SampleGridView.RowDataBound
	#{' e.Rowには,対象となるGridViewの行が入っています}
	Dim line As GridViewRow = e.Row
	#{' イベントが発生するのは,DataSourceと結び付いた直後なので,このようにしてデータを取り出せます}
	Dim row As DataRow = DirectCast(line.DataItem, System.Data.DataRowView).Row

	#{' 値によって表示をわける}
	Select Case row.Item("SAMPLE_NUMBER")
		Case 1
			DirectCast(line.FindControl("lblWEATHER"), Label).Text = "晴天"
		Case 2
			DirectCast(line.FindControl("lblWEATHER"), Label).Text = "雨天"
	End Select
End Sub
ちなみにこれ,row.Item("SAMPLE_NUMBER")がDbNullだったら多分落ちます.
こんな感じでHiddenFieldに何かの値を入れておいてJavaScriptで読み取って使うとかもできますね*2

ソート状態を知らせる

この辺参照で.
GridView.SortExpression プロパティ (System.Web.UI.WebControls)
How to sort GridView?

*1 : この程度なら,SQL文を工夫すれば普通に表示できますが例なので

*2 : もちろんサーバサイドで使ってもいいですし

Webユーザーコントロール

既存のWebコントロールを組み合わせてロジックを追加するなどした独自のコントロールを作れます.

「新しい項目の追加」>「Webユーザーコントロール」
適当に配置.

Web ユーザー コントロールと Web カスタム コントロール
@IT:.NET TIPS [ASP.NET]サイト共通のレイアウト部分を部品化するには? - C# VB.NET Webフォーム

作ったユーザーコントロールを使うには,使いたいページに次の形式で宣言を記します.
<%@ Register TagPrefix="タグ接頭辞" TagName="タグ名" Src="Webユーザーコントロールへのパス(相対パスと絶対パスどちらでもOK)" %>
実際は次のような感じ.
マスターページは例で載せているだけなので,マスターページを使わないときは当然マスターページ宣言は抜きで.
<%@ Page Language="VB" MasterPageFile="~/SampleMasterPage.master" AutoEventWireup="false" CodeFile="sample.aspx.vb" Inherits="sample" title="サンプルページ" %>
<%@ Register TagPrefix="uc" TagName="SampleWebUserControl" Src="~/SampleWebUserControl.ascx" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
	<uc:SampleWebUserControl runat="server" ID="sample" />
</asp:Content>
このように,<TagPrefix:TagName ~ />といった感じで使います.

方法 : ASP.NET Web ページにユーザー コントロールを含める

属性設定

Labelなんかだと,次のような形でコントロールに値をセットできます.
<asp:Label runat="server" Text="ラベルに表示する文字列" ID="sample" />
ユーザーコントロールで次のようにLabelと同じことをするにはどうするか.
<uc:SampleWebUserControl runat="server" ID="sample" sampleField="5" />
@IT:.NET TIPS [ASP.NET]ユーザー・コントロールで属性を設定するには? - C# VB.NET Webフォーム

PublicなPropertyをユーザーコントロールのクラスで宣言すればOK.
Propertyの宣言例
Protected _foo As Integer
Public WriteOnly Property foo() As Integer
	Set(ByVal value As Integer)
		Me._foo = value
	End Set
End Property
WriteOnlyである必要はありませんが,値をセットするだけならGetterは必要無いので.

Validator

何らかの入力を処理するプログラムは,
その入力がシステムで想定した条件を満たしているかを検証してから処理を行う必要があります.
Webフォームの場合においても同様に入力値の検証は非常に重要です.
しかし,全ての項目に対して行う必要があるので実装に手間がかかり
不具合が起こると大きな問題に繋がりやすいという問題があります*3
Validatorは,Webフォームの検証を半自動化することによってこの問題を軽減することが可能なコントロールです.

ASP.NET2.0には,目的別に5種類のValidatorがあります.
Validator種別概要
RequiredFieldValidator必須チェック
RangeValidator数値の範囲チェック
RegularExpressionValidator正規表現チェック
CompareValidator数値の比較チェック
CustomValidatorユーザが処理内容を定義できるValidator
それぞれの詳細は次のサイト辺りを見たりValidatorの名前で検索したりするといいです.
  • 入力された値をチェックする方法
    • http://www.microsoft.com/japan/msdn/asp.net/tips/InputCheck/
      • RequiredFieldValidator
  • 入力された値をより高度にチェックする方法
    • http://www.microsoft.com/japan/msdn/asp.net/tips/InputCheck2/
      • CompareValidator,RangeValidator,RegularExpressionValidator
  • @IT:解説:ASP.NETで学ぶVisual Studio .NETの魅力 第2回 Visual Studio.NETでプログラム・レス開発を学ぶ(前半)
    • http://www.atmarkit.co.jp/fdotnet/aspandvs/aspandvs02/aspandvs02_04.html
また,Validator機構をクライアントサイドで実際にどのように処理しているかについては,次の記事にメモりました.

CustomValidator

CustomValidatorは,他のValidatorでは対応できないようなチェック.
例えば文字列の長さチェックなんかをチェック用のロジックを埋め込むだけで実装できるValidatorです.
使い方は簡単で,クライアントサイドとサーバサイドでチェック用の関数を作り,それを指定する以外は他のValidatorと同じです.

クライアントサイドでのチェック用関数は,JavaScriptの関数をWebページから利用可能な場所に書きます.
.jsファイルに書いて読み込ませてもいいですし,.aspxファイルに直書きしてもいいです.
このコードは,次のページを参照しました.
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=41062&forum=7
// 文字列長をチェックするValidator
function customValidator_StringLength(source, arguments) {
	var val = arguments.Value;
	if (val == "") { 
		arguments.IsValid = true;
		return;
	}

	var len = 0;
	var c;
	for (i = 0; i < val.length; i++) {
		c = escape(val.charAt(i));
	}
	if(c.length > 3) {
		//len += 2;
		len += 1;
	} else {
		len += 1;
	} 
	
	// CustomValidatorオブジェクトのcontroltovalidateフィールドで検証対象のIDを取得している.
	// ここでは,検証対象(<input type="text"などを想定)のmaxlengthを許可文字列長の閾値としている.
	var limit = document.getElementById(source.controltovalidate).maxLength;
	if (len <= limit) {
		arguments.IsValid = true;
	} else {
		arguments.IsValid = false;
	}
} 
arguments.Valueに入力値が入っているので,この値に対して何らかの検証を行い,
検証の結果問題が無いと判断したならarguments.IsValidをTrueに.
問題があるならFalseにすることで検証結果をASP.NETの検証システムに通知します.

引数のsourceは,CustomValidatorのオブジェクトが入っています.
クライアントサイドにおいて,CustomValidatorの実体はspan要素になっています.

サーバサイドでは,次のシグネチャを持つメソッドを定義します.
' 文字列長の検証
Protected Sub customValidator_StringLength(ByVal source As Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs)
	' ここでもJavaScriptと同じように検証対象のMaxLengthから許容文字列長を取得している
	Dim limit As Integer = DirectCast(Me.FindControl(source.ControlToValidate), TextBox).MaxLength
	If limit >= args.Value.Length Then
		args.IsValid = True
	Else
		args.IsValid = False
	End If
End Sub
JavaScriptと同じく,第二引数のIsValidにTrueを設定すれば検証OK,Falseを設定すれば問題ありとなります.

CustomValidatorの実際の利用は,次のようになります.
<asp:TextBox ID="txtSample" runat="server" MaxLength="30"/>
<asp:CustomValidator ID="sampleCutomValidator"
	runat="server" ClientValidationFunction="customValidator_StringLength"
	OnServerValidate="customValidator_StringLength"
	ErrorMessage="文字列が長すぎます" ControlToValidate="txtSample" />

検証処理を実行してから任意のスクリプトを走らせる

ボタンをクリック→Validatorによって入力チェック→確認ダイアログを出す(OK/キャンセル)→OKならsubmit
という流れを想定するとします.
ボタンのOnClientClick属性を次のようにしてみると良さそうに見えます.
OnClientClick="if (confirm('登録します.よろしいですか?') == false) return false;"
しかし,実はこれだと次のようになってしまいます.
ボタンをクリック→確認ダイアログを出す(OK/キャンセル)→Validatorによって入力チェック→OKならsubmit
これを回避するには,自力で上の処理を書く必要があります.

具体的には,次のコードのようにします.
OnClientClick="if (Page_ClientValidate('mainGroup')) { if(confirm('登録します.よろしいですか?') == false) return false; }"
'mainGroup'には,検証対象のValidationGroupを指定して下さい.
if ( (function(){ return confirm('登録します.よろしいですか?');})() == false ) return false;

*3 : Webフォーム固有の問題というわけではないですが,構築するシステムの多くが個人情報を扱うデータベースと連携していたりしている上に入力項目が非常に多くて実装が大変かつ不具合が混入しやすく,しかも狙われやすかったりと大きな問題です.

Validatorの動的追加

Validatorは,普段表のコード(.aspx)に"<asp:CustomValidator"みたいなタグを書いて設定しますが,
ビハインドコード(.aspx.vb|aspx.cs)の実行中にValidatorを追加するにはどうすればいいのでしょうか.
普通は,検証対象とどのように検証するかははっきりしているので使わないことが多いような気もしますが,
例えばWebユーザーコントロールを作っているとこれが必要なことが結構あります.
Webユーザーコントロールは,色々な所で色々な目的で呼ばれます.
例えば,年月日のコンボボックスを提供するWebユーザーコントロールは,
あるところでは単に年月日を表示するだけで検証は必要ないかもしれないし,
別のところでは他の入力要素と連動して何かを検証する必要があるかもしれません.

書いてきて息切れしてきたので例だけ載せます.
今回の例はユーザーコントロールにCustomValidatorを動的に追加するなら?という題材ですが,
他の状況でも同様にできる筈です.

ユーザーコントロールに次のような公開メソッドを追加します.
//ユーザーコントロールに公開メソッドを追加
Public Sub registerValidator(ByRef sourceValidator As CustomValidator)
	// validationTargetControlは,検証対象のコントロール
	sourceValidator.ControlToValidate = Me.validationTargetControl.ID
	// Validatorコントロールを検証対象のコントロールの次に追加
	Me.Controls.AddAt(Me.Controls.IndexOf(Me.validationTargetControl) + 1, sourceValidator)
End Sub
ユーザーコントロールを使う側に次のような処理を追加します.
//変数名 Validator: "sampleValidator" ユーザーコントロール: "sampleUserControl"
//ユーザーコントロールに追加するValidator
Dim sampleValidator As New CustomValidator()
//通常のVaildatorと同じように検証用の設定をする
sampleValidator.ID = "sampleValidatorID"
sampleValidator.ErrorMessage = "<br />検証結果が不正です"
sampleValidator.ClientValidationFunction = "customValidator_Function"
sampleValidator.ValidationGroup = "SampleValidationGroup"
sampleValidator.EnableClientScript = True
sampleValidator.Enabled = True
sampleValidator.Visible = True
//サーバーサイドの検証メソッドを関連づける
AddHandler sampleValidator.ServerValidate, AddressOf Me.customValidator_Function
//ユーザーコントロールにValidatorを追加
Me.sampleUserControl.registerValidator(sampleValidator)

ADO.NET

DbNull?

DbNullをDbParameterのValueに渡そうとして次のようなコードを書くとエラーになります.
DbNullはクラスであって値ではないからです.
objParam.Value = DbNull
次のようにすればOK
objParam.Value = DbNull.Value

DbCommandとDbParameterでクエリを発行

注意:超はしょってます.

DbCommandとDbParameterを使うとSQL文をわかりやすく書けます.具体的には次のような感じ.
' SQL文の作成
sql += "UPDATE SAMPLE"
sql += " SET "
sql += " FOO = #{@FOO}"
sql += " ,BAR = #{@BAR}"
sql += " WHERE FOOBAR = #{@FOOBAR}"

' コマンドをセット
objCommand.CommandText = sql

' パラメータをセット
objParam = objCommand.CreateParameter()
objParam.ParameterName = "@FOO"
objParam.Value = True
objParam.DbType = Data.DbType.Boolean
objCommand.Parameters.Add(objParam)

objParam = objCommand.CreateParameter()
objParam.ParameterName = "@TO_PAYMENT_DATE"
objParam.Value = "対応する値"
objParam.DbType = String
objCommand.Parameters.Add(objParam)

objParam = objCommand.CreateParameter()
objParam.ParameterName = "@FOOBAR"
objParam.Value = id
objParam.DbType = Data.DbType.Int32
objCommand.Parameters.Add(objParam)
で,このDbCommandをDB接続オブジェクトに渡すとクエリが発行できるというわけです.
これは,ちょうど次のようにSQL文を作成する作業に置き換えられます.
sql += "UPDATE T_ORDERS"
sql += " SET "
sql += " TO_PAYMENT_FLAG = 'True'"
sql += " ,TO_PAYMENT_DATE = '対応する値'"
sql += " WHERE TO_ID = '" + id.toString() + "'"
行数はこっちのが少なくて見通しが良さそうですが,
文が複雑になってきたり変数の数が多くなってくるとDbCommandを使った方が管理しやすくなる場合もあるようです.
あと,DbParameterを使うと入っちゃいけない文字列なんかも監視してくれるみたい.
有名なシングルクォーテーションとか.

Transact-SQL

文字列連結

SELECT FOO + BAR AS FOOBAR, FOO, BAR
FROM SAMPLE
文字列結合には+演算子を使います.

+ (文字列連結) (Transact-SQL)

NULL値対策

文字列結合時にFOOの値がNULLだと,例えBARがNULLでなくてもFOO+BARはNULLになってしまいます.
(NULLに何かを足してもNULLにしかなりません)
これを回避するには,次のようにしろとリファレンスには書いてあります.
現在のセッションの CONCAT_NULL_YIELDS_NULL の設定を変更することにより、この動作を変更できます。
しかし,設定まで変えなくても次のようにすることで回避可能です.
SELECT #{ISNULL(FOO,'')} + BAR AS FOOBAR, FOO, BAR
FROM SAMPLE
ISNULL関数は,第一引数に対象の列名を取り,この列の値がNULLだった場合に第二引数の値を返します.
つまり,例文のようにすることでFOOがNULLの場合は空文字を返すようにすれば問題がなくなるわけです.

ISNULL (Transact-SQL)


名前:  非公開コメント   

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