2009年5月23日土曜日

プログラミングLINQ

本買っちゃいました。今回は、約8000円、(ADO.NETの1部の機能だけなのに)
プログラミングASP.NET3.5も1万円ぐらいとバージョンが上がるたびに分厚く高くなっていきます。
次回のプログラミングADO.NETは、1500ページ1万5000円ぐらいになるのかな?
最新技術を追っかけるのも大変です。
(Visual Studio2010ももうすぐ出るし、.net frameworkも作り直しが行われるみたい。プログラミング.NET Framwowork4.0も出るのかな?)

asp.netのカスタムコントロールをVisual Stuidoのデザイナで編集する方法


流れとしては、
1.ASP.NETサーバーコントロールを含むアセンブリ(exe,dll)を作成する
2.Visual Studioのツールボックスで、右クリック→アイテムの選択で、参照ボタンを押して1.で作成したアセンブリを選択。
→ツールボックスに歯車アイコンでカスタムコントロールが表示される。
3.上の(2.の)操作によって出てきた、歯車アイコンをaspxやascxに張り付ける。

ふつうは、コントロール用のアセンブリと利用するWebプロジェクトはアセンブリを分けますが、一緒でもちょっとは動くみたい。
でも、Webプロジェクトとカスタムコントロールのアセンブリが一緒だと、デザイン時のレンダーとか、プロパティセットが正しく表示できないみたい。
(コントロールの描画エラー-Label4 ハンドルされていない例外が発生しました。'SANITIZE'をプロパティ'DisplayMode'で設定できませんでした。みたいな表示となる。)
アセンブリを分けてれば、こんなエラーは出ないんだけど。。。しょうがないのかなぁ。



ま、しょうがないとして、
今回は、カスタムのLabelコントロールを元にカスタムコントロールを作成します。Labelコントロールに2個プロパティを追加します。
1個は、表示方法(そのまま表示/サニタイズ表示/タグ抜き表示)もう1個は、改行文字変換(改行文字をBR変換するかどうか)です。


カスタムラベルのソースはこんな感じ。
各メタデータの意味は、以下のURLを参考になります。
http://msdn.microsoft.com/ja-jp/library/ms178658(VS.80).aspx


----ここからMyCustomControl.cs------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebControlTest.WebParts
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:Label runat=server></{0}:Label>")]
public class Label : System.Web.UI.WebControls.Label
{
/// <summary>
/// 文字列の変換指定列挙子
/// </summary>
public enum TranslateString
{
NONE,
SANITIZE,
DELETE_TAG
}

/// <summary>
/// 改行変換の列挙子
/// </summary>
public enum TranslateNewLine
{
NONE,
BR_TRANSLATION
}

private TranslateString displayMode=TranslateString.NONE;
private TranslateNewLine newLineMode=TranslateNewLine.NONE;

[Bindable(true)]
[Category("Appearance")]
[Description("文字変換")]
[DefaultValue("NONE")]
[Localizable(true)]
public TranslateString DisplayMode
{
get { return displayMode; }
set { displayMode = value; }
}

[Bindable(true)]
[Category("Appearance")]
[Description("改行モード変換")]
[DefaultValue("NONE")]
[Localizable(true)]
public TranslateNewLine NewLineMode
{
get { return newLineMode; }
set { newLineMode = value; }
}

protected override void RenderContents(HtmlTextWriter output)
{
string outputText = Text;
switch (displayMode)
{
case TranslateString.SANITIZE:
outputText = System.Web.HttpUtility.HtmlEncode(outputText);
break;
case TranslateString.DELETE_TAG:
outputText = System.Text.RegularExpressions.Regex.Replace(outputText, "<[^>]*>", "");
break;
default:
break;
}
switch (newLineMode)
{
case TranslateNewLine.BR_TRANSLATION:
outputText = outputText.Replace("\r\n", "<br>").Replace("\r", "<br>").Replace("\n", "<br>");
break;
}
output.Write(outputText);
}
}
}
----ここまでMyCustomControl.cs------

SQL Server2005のインデックスのソート順についての調査


確かSQL SERVER2000から、インデックスが双方向連結リストとなっていたと思うのでこれの調査
まずはデータ作成から。

testデータベースを作って、testtblを作って、1万件データを入れます。
ここまでのsqlはこんな感じ。
create database test
go
use test
create table testtbl
(
pk1 int primary key identity(1,1),
id1 int,
id2 int,
id3 int
)

declare @a as int, @b as int, @c as int, @i as int

set @i=1
while (@i <> 10000)
begin
set @a=rand()*10000
set @b=rand()*10000
set @c=rand()*10000
insert testtbl (id1,id2,id3) values(@a,@b,@c)
set @i=@i+1
end


次に、インデックスを張らずに、以下のSQLを実行し実行プランを確認する。
select * from testtbl order by id1




当然、SORTという処理が入っている。
続いて、以下のSQLにてid1昇順でid2,id3を付加列に先ほどのselect文をカバーするインデックスを作成する。
create index testtbl_id1 on testtbl (id1 asc) include(id2,id3)
インデックスにpk1が入っていないけど、testtblがクラスタ化インデックステーブルのため行ロケータとしてキー値が使われるので今作成したインデックスに必ず含まれているから指定しなくても付加列と同じ振る舞いが期待できるので、クエリをカバーするインデックスといえる。
で、同じSQL実行。
以下が実行プラン。






実行結果は、ソート処理がなくなっている。
続いて、今のインデックスのままソートキーで逆順に表示する以下のSQL文を発行。
select * from testtbl order by id1 desc
以下が実行プラン。




ちゃんとインデックスを使ってsort処理が不要となっている。
これによりインデックスが単方向リストではないことが確認できた。ソート項目が1項目であれば昇順/降順は注意しなくてもよさそうです。
続いて今のインデックスのまま、以下のSQLにて2つの項目でソートしてみます。
select * from testtbl order by id1,id2
以下が実行プラン




インデックスは使っているが、SORT処理が行われている。しかも第1キーではソート済みであるにもかかわらず、コストの95%はソート処理。

次に複数列のインデックスを張ってみる。
create index id1_id2 on testtbl (id1 asc,id2 asc) include (id3)

で先ほどのsqlを実行。




今回は、SORT処理は不要。
同様にid1 desc,id2 descではSORT処理は不要でした。ただし、id1 asc,id2 descやid1 desc,id2 ascでは、インデックスを使うがSORTも発生する。
同様に、id2,id1のソートでもSORTが発生する。

というわけで、インデックスのソート順はソート指定が複数ある場合にソートの向きが異なる場合、意識したほうがよい程度のものでした。

最後に、クエリをカバーしていないインデックスのみの場合、インデックスを使うかテーブルスキャンになるかを確認。
drop index id1_id2 on testtbl
drop index testtbl_id1 on testtbl
create index id1 on testtbl (id1) include (id2)
最後に作成したインデックスは、id3が付加列にないため、以下のクエリをカバーしていないインデックスになります。
select * from testtbl order by id1
実行プランは以下。





クエリをカバーしていないと全権検索ではインデックスを使わないみたい。

asp.netでモーダルウィンドウ



お仕事で、モーダルダイアログを表示する箇所があったので、その開発メモ。


1.ポップアップブロック対応

ieでは、以下のような処理を行うと、「ポップアップはブロックされました。このポップアップまたは追加オプションを表示するにはここをクリックしてください」という表示がされてしまう。

webページのボタン押下

サーバのポストバック処理

サーバのボタンイベント処理

PCへjavascript起動を含めたページ返信

javascriptのwindow.openやshowModalDialogの実行

これは、ユーザ操作ではなく、サーバからページが送られた直後にウィンドウを新たに起こそうとしているため発生するので、ユーザ操作によるウィンドウオープンにすれば回避できそう。


具体的には、以下のようなaタグを作って表示しておくか、

<a href="javascript:window.showModalDialog('ポップアップウィンドウ?次画面への受け渡し用クエリストリング')">ウインドウオープン</a>


ボタンを配置し、Page_Loadで、ボタンのonClickイベントにdialogを仕込み、
protected void Page_Load(object sender, EventArgs e)
{
Button.Attributes["onClick"] = "javascript:window.showModalDialog('ポップアップウィンドウ?次画面への受け渡し用クエリストリング');return false;";
}
これらを押すことにより『ユーザの操作により』ウィンドウを開くようにすればよさそう。
だけど、上のようにすると作りが複雑になること、リンクやボタン押下時に自画面でのポストバック処理やイベント処理が動かないという制限がある。


今回この制限が回避不可能な見込みのためポップアップブロック回避は見送りになりそう。





2.モーダルダイアログを経由する画面遷移
今回、以下の4つのページを使っていろいろ実験をしてみる。(すべてのページにボタンが1つづつある)
FirstPage.aspx:起動時のページ1つボタンが置かれており、ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{
Page.ClientScript.RegisterClientScriptBlock(GetType(), "CreateModalDialog",
"<script language='javascript'>window.onload=function(){window.showModalDialog('SecondPage.aspx');}"
+ "</script>");
}


SecondPage.aspx :FirstPage.aspxから呼ばれるページ、ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{
Response.Redirect("ThirdPage.aspx");
}

ThirdPage.aspx:SecondPage.aspxから呼ばれるページ。ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{
Page.ClientScript.RegisterClientScriptBlock(GetType(), "CreateModalDialog",
"<script language='javascript'>window.onload=function(){window.showModalDialog('ForthPage.aspx');}"
+ "</script>");
}

ForthPage.aspx:ThirdPage.aspxから呼ばれるページ。ボタン処理は以下の通り。
protected void Button1_Click(object sender, EventArgs e)
{

}

これで、画面遷移をしてみると、「ポップアップブロック」以外に以下の問題が発生した。
・FirstPage.aspx→SecondPage.aspx→SecondPage.aspxを×で閉じる→FirstPage.aspx→SecondPage.aspxでSecondPage.aspxのPage_Load()が呼ばれないでSecondPage.aspxが表示される。(キャッシュの影響か?)
・ThirdPage.aspxが(画面遷移ではなく)新ウィンドウで表示される。

上の2点の対応で、SecondPage.aspxのヘッダタグに以下の記述を追加。
<meta http-equiv="pragma" content="no-cache" />
<base target="_self" />
一応こうすることで、SecondPage.aspxの2回目の呼び出しもPage_Load()が動いてくれたし、ThirdPage.aspxも自ウィンドウで表示された。

ただ、ThirdPage.aspxからForthPage.aspxに遷移するとき、ThirdPage.aspxが別のウィンドウで開いてしまう。
このため、ThirdPage.aspxにも、<base target="_self" />をつけてみたところ、今度は、ForthPage.aspxが開く前に、「オブジェクトでサポートされていないプロパティまたはメソッドです」のメッセージが。。。
showModalDialog()中に、「ポップアップブロック」が走るとエラーとなるみたい。
ThirdPage.aspxで、ボタンの2個目を追加して、aspx.csに以下の通りにすると、エラーなく、showDialogが2重で意図通りの動きになった。
protected void Page_Load(object sender, EventArgs e)
{
Button2.Attributes["onClick"] = "javascript:window.showModalDialog('ForthPage.aspx');return false;";
}


というわけで、showModalDialogのまとめ。

・showModalDialogで表示する画面とModalDialogウィンドウで遷移する画面すべてのヘッダタグ内に、<base target="_self" />を入れる必要がある。
・showModalDialogで表示する画面は、キャッシュされないように、ヘッダタグ内に、<meta http-equiv="pragma" content="no-cache" />を入れる必要がある。
・window.openと同様に、ユーザ操作なしにウィンドウを開く場合は「ポップアップブロック」が動く。
・showModalDialogの処理から「ポップアップブロック」が動くと、javascriptがエラーとなってしまう。

なので、今回、2重のshowModalDialog()を利用できるかは微妙になっちゃいました。

2009年5月21日木曜日

SQL ServerのDBに入れる空白文字?

テーブルの項目varchar(10)でnull許可の列。
空白を入れて、といわれた場合何を入れればいいんだろう。
感覚でいうと、
string.Empty.(いわゆる'') : 50%
null : 40%
半角スペース1文字:5%
半角スペース10文字:2%
全角スペース5文字:1%
全角スペース1文字:1%
上のすべて不正解:1%
の確立であたるんじゃなかろうか。。
要は、使っているところに確認が必要ってこと。

セッション変数で嵌る。


セッション周りのコードを見ていたら、

1.クラスのインスタンスをセッション変数に格納していた。
2.セッション変数から、インスタンスを取り出し、セッション変数に再格納していた。

こんな感じ

CSession.Book cb1 = new CSession.Book();
cb1.Id=bookId;
cb1.Title=TxtBookTitle.Text;
cb1.Author=TxtAuther.Text;
:
Session["BOOK"]=cb1;


別の処理で、
CSession.Book cs = (CSession.Book)Session["BOOK"];
if(cs!=null)
{
cs.Id="...";
cs.Title="....";
cs.Author="...";
:
if(regFlg)
{
Session["BOOK"] = cs;
}
}

気になった点は、CSession.Bookのクラスがシリアル化可能かということと、
Session["BOOK"] = cs;の行。

ASP.NETの状態管理のサービスを立ち上げて、web.configで、
<sessionState mode="StateServer" />
と記述すると、セッション格納時に例外が発生する。

CSession.Bookのクラス定義の上に[Serializable()]をつけることにより、
例外が発生しなくなったが、問題はSession["BOOK"] = csの行。この行は
意味がないように見える。

直前のif文で条件分岐をしているんだけど、cs自体が参照のためcsのメンバー
に値を代入した時点で、セッションに格納したインスタンスのメンバーの値
も変わっていることをプログラム作成者が認識してるかな?

2009年5月20日水曜日

Application_Errorで嵌る。

今回のはまりはGlobal.asaxのApplication_Error()とエラーページについて、

最初の実装では、
Global.asax.csのApplication_Error()でこんな感じにして、

protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
Session["errormessage"] = ex.Message;
Server.Transfer("Error.aspx");
}

Error.aspx.csでは、こんな感じにしてみた。
protected void Page_Load(object sender, EventArgs e)
{
TextBox1.Text = (string)Session["errormessage"];
}

そうすると、なぜか、エラーページのメッセージにはいつもハンドルされていないエラーの旨のメッセージしかでなかった。
なぜかはわからないが、とりあえず、Server.GetLastError()で取得したエラーについて、innerErrorが存在する場合はinnerErrorを発生した例外として扱ってみた。

で、うまくいくように見えたのだけれど、nopage.aspx(存在しないページ)にアクセスすると、Application_Error()の処理のSessionに値を詰め込むところでエラーが発生した。
多分、aspxページが存在しないとSessionが扱える状態になる前にApplication_Errorが呼ばれたからじゃないかな?
というわけで、Application_Errorを以下のような感じに、
Server.Transfer("Error.aspx?errormessage="+ HttpUtility.UrlEncode(ex.Message));

Error.aspx.csのPage_Loadでは以下のような感じにしてみた。
TextBox1.Text = HttpUtility.UrlDecode( Request["errormessage"]);


そうすると、存在しないページにアクセスしたときも意図通りの動作になったが、、、

次は、@Pageディレクティブで、ValidateRequest="true"のページで、<a>などの入力をしてみると、Error.aspx(このページは@PageディレクティブにValidateRequest="false"としているにもかかわらず)の処理で、Request["errormessage"]のタイミングでも、ValidateRequestの例外が発生する。
多分、Server.Transfer()で行っているので、Error.aspxの処理の前の前段パイプライン処理が走っていないから、Error.aspxのページディレクティブを参照していなくて、元ページのValidateRequestの値が引き続き使われているからだろうなぁと想像しますが、かといってResponse.Redirect()にして、URLをさらすわけにもいかず、、、結局web.configでValidateRequest="false"にしてしまって様子見。。。はぁ。