Asp.Net フォーム認証
2008/02/28 § コメントする
フォーム認証
http://www.atmarkit.co.jp/fdotnet/aspnet/aspnet19/aspnet19_01.html
フォーム認証を使う場合に選択することが二つある。
1・ID、パスワードをどこに保存するか?
2・どこまでweb.configの設定を使うか?
1・ID、パスワードをどこに保存するか?
ID、パスワードをweb.configに保存するか、XMLなりRDBなり他の場所に保存するかの選択になる。
web.configのauthentication/forms/credentialsにID、パスワードを保存すればFormsAuthentication.Authenticateを使ってID、パスワードの検証を行ってくれる。
が、web.configにID、パスワードを書きたい人はほとんどいないんじゃないかと思うし、web.configのauthentication/forms/credentialsにはロールの設定まではかけない。
まぁ大半はFormsAuthentication.Authenticateはあきらめて認証は自前でするんだと思う。
2・チケットやcookieにどこまでweb.configの設定を使うか?
FormsAuthentication.RedirectFromLoginPageを使えばweb.configの設定を使って認証coookieを発行してくれるが、汎用性にはかける。
UserDataに任意の値を渡したい、発行する認証チケットやcookieの有効期限の設定を自身のロジックで設定したい、などのニーズがあるなら、認証チケットの発行とcookieへの細んは自分で行う必要がある。
以下はサンプル
「web.configにID、パスワードを保存+RedirectFromLoginPage使用」
web.configにユーザ情報まで書いてしまう方法。
ほとんどAsp.Netが面倒を見てくれるけど、さすがに相当小規模なサイトでないとこの方法は使えないと思う。
サンプルを作ってたとき、認証cookie発行前なのに、ログインページへリダイレクトされずに悩んだ。
結局、authorization/allow/@usersを?にしていたせいで非認証ユーザのアクセスが許可されてただけでした。orz
web.config
ユーザIDやパスワードのアカウント情報もここに書いてしまう。
この辺が実際には使わない理由。configSourceでここだけ外に出す手はあるかもしれないけどそれでも・・・。
configuration/system.web
にこんな記述をする。
<authentication mode="Forms"> <forms name="auth" loginUrl="login.aspx" protection="All" path="/" timeout="30"> <credentials passwordFormat="Clear"> <user name="aaa" password="aaa"/> <user name="bbb" password="bbb"/> </credentials> </forms> </authentication>
authentication/@modeは文字通り認証のモードをForm認証にしている。
authentication/formsはForm認証の設定。
authentication/forms/@name:認証cookieの名前
authentication/forms/@loginurl:認証cookieが見つからない場合に、リダイレクトするurlを指定。
認証せずにコンテンツを要求すると、Asp.Netがログインページに飛ばしてくれる。便利やな。
authentication/forms/@protection:cookie暗号化の種類。allにしとけ。
authentication/forms/@path:cookieのパス
authentication/forms/@timeout:認証cookieのタイムアウト。単位は分。
ここでいうタイムアウトは認証cookieのタイムアウトなので、Asp.Netのセッションオブジェクトのタイムアウトとは別物みたいなので注意。
authentication/forms/credentials以下は認証に使うアカウントとパスワード。
authentication/forms/credentials/@passwordFormatを変えればハッシュ値をパスワードにできたりもする。一応。
その他はこちら。
http://msdn2.microsoft.com/ja-jp/library/1d3t3c61(VS.80).aspx
login.aspx
<%@ Page Language="C#" EnableViewStateMac="true" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %> <!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 ID="txtId" runat="server"></asp:TextBox> <br> <asp:TextBox ID="txtPas" runat="server"></asp:TextBox> <br> <asp:Label ID="lblPersist" runat="server" Text="次回から自動ログインする"></asp:Label><asp:CheckBox ID="cbPersist" runat="server" /> <br /> <br /> <asp:Button ID="btnLogin" runat="server" Text="Login" onclick="btnLogin_Click" /> <br /> <asp:Label ID="lblMsg" runat="server" Text=""></asp:Label> </div> </form> </body> </html>
login.aspx.cs
using System; using System.Web.Security; public partial class Login : System.Web.UI.Page { protected void btnLogin_Click(object sender, EventArgs e) { if (!FormsAuthentication.Authenticate(this.txtId.Text, this.txtPas.Text)) { this.txtPas.Text = string.Empty; this.lblMsg.Text = "ログイン失敗"; return; } FormsAuthentication.RedirectFromLoginPage(this.txtId.Text, this.cbPersist.Checked); } }
aspxはシンプルなログイン画面。
認証のロジックはbtnLogin_Clickなんだけど、とても簡単。
System.Web.Security.FormsAuthentication#Authenticateメソッドがweb.configのauthentication/forms/credentialsの定義をみてユーザ名、パスワードの検証をやってくれる。
System.Web.Security.FormsAuthentication#RedirectFromLoginPageは認証cookie発行し、ログイン画面に遷移する前のページにリダイレクトしてくれる。
Default.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:Button ID="btnLogout" runat="server" Text="Logout" onclick="btnLogout_Click" /> <a href="page.html">page.html</a> <a href="page.aspx">page.aspx</a> </div> </form> </body> </html>
Default.aspx.cs
using System; using System.Linq; using System.Web.Security; using System.Security.Principal; public partial class _Default : System.Web.UI.Page { protected void btnLogout_Click(object sender, EventArgs e) { FormsAuthentication.SignOut(); Response.Redirect("login.aspx"); } protected void Page_Load(object sender, EventArgs e) { Response.Write("ページ要求を行っている認証されたユーザの情報"); Response.Write("<hr>"); IIdentity iid = User.Identity; iid.GetType().GetProperties().Where( pi => pi.Name == "AuthenticationType" || pi.Name == "ImpersonationLevel" || pi.Name == "IsAuthenticated" || pi.Name == "IsGuest" || pi.Name == "IsSystem" || pi.Name == "IsAnonymous" || pi.Name == "Name" || pi.Name == "Groups").ToList().ForEach( pi => this.Response.Write(pi.Name + ":" + pi.GetValue(iid, null) + "<br>")); //グループも表示 //iid.GetType().GetProperties().Where( // pi => pi.Name == "Groups").ToList().ForEach( // pic => // { // IdentityReferenceCollection ic = pic.GetValue(iid, null) as IdentityReferenceCollection; // if (ic == null) // { // return; // } // ic.OfType<System.Security.Principal.IdentityReference>() // .ToList().ForEach( // t => Response.Write(t.Value + "<br>"+ t.Translate(typeof( NTAccount)).Value + "<br>")); // }); Response.Write("<br>"); Response.Write("<br>"); Response.Write("プロセスを実行しているユーザの情報"); Response.Write("<hr>"); WindowsIdentity wi = WindowsIdentity.GetCurrent(); wi.GetType().GetProperties().Where( pi => pi.Name == "AuthenticationType" || pi.Name == "ImpersonationLevel" || pi.Name == "IsAuthenticated" || pi.Name == "IsGuest" || pi.Name == "IsSystem" || pi.Name == "IsAnonymous" || pi.Name == "Name" || pi.Name == "Groups").ToList().ForEach( pi => this.Response.Write(pi.Name + ":" + pi.GetValue(wi,null) + "<br>")); } }
btnLogout_Clickでサインアウト処理しているの以外は認証の情報を表示するだけ。
「ID、パスワードを自前で確認+チケット、cookieを自前で発行(あとロールも)」
多少書かないといけないけど、それでも大した量ではないと思う。
web.config
<authentication mode="Forms"> <forms name="auth" loginUrl="login.aspx" path="/"> </forms> </authentication>
web.configの情報はあまり使わないので記述が減った。
Login.aspx
<%@ Page Language="C#" EnableViewStateMac="true" AutoEventWireup="true" CodeFile="Login.aspx.cs" Inherits="Login" %> <!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 ID="txtId" runat="server"></asp:TextBox> <br> <asp:TextBox ID="txtPas" runat="server"></asp:TextBox> <br> <asp:Label ID="lblPersist" runat="server" Text="次回から自動ログインする"></asp:Label><asp:CheckBox ID="cbPersist" runat="server" /> <br /> <br /> <asp:Button ID="btnLogin" runat="server" Text="Login" onclick="btnLogin_Click" /> <br /> <asp:Label ID="lblMsg" runat="server" Text=""></asp:Label> </div> </form> </body> </html>
Login.aspx.cs
using System; using System.Web; using System.Web.Security; public partial class Login : System.Web.UI.Page { protected void btnLogin_Click(object sender, EventArgs e) { if (!Validate(this.txtId.Text, this.txtPas.Text)) { this.txtPas.Text = string.Empty; this.lblMsg.Text = "ログイン失敗"; return; } string role = "admin"; //ロール var ticket = new FormsAuthenticationTicket( 1, //チケットのバージョン this.txtId.Text, //ユーザID DateTime.Now, //発行日時 DateTime.Now.AddMinutes(30), //有効期限 this.cbPersist.Checked, //永続的なcookieに保存するかどうか role); //UserData領域。好きなものを入れればいい。 var ck = new HttpCookie( FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket)); if (this.cbPersist.Checked) { ck.Expires = DateTime.Now.AddYears(50); } ck.Path = FormsAuthentication.FormsCookiePath; Response.Cookies.Add(ck); Response.Redirect(FormsAuthentication.GetRedirectUrl(this.txtId.Text, this.cbPersist.Checked)); } //ユーザのチェック private bool Validate(string id, string pass) { return (id == "aaa" && pass == "aaa") || (id == "bbb" && pass == "bbb"); } }
チケットの発行、cookieへの格納を自前でやった上で元ページへリダイレクトしている。
Default.aspxとDefault.aspx.csは前と同じ。
これに加えてロールでのauthorizationをしたいのであればもう少しやらなければならないことがある。
Windows認証ではWindowsアカウントのグループがロールにマップされたけど、フォーム認証では自分でPrincipalにロールを設定しないといけない。
別にロールを設定しなくてもいい場合もあるだろうけど、ロールでのauthorizationをしたり、Page.User.IsRoleでプログラムの挙動を変えたいのであればロールの設定が必要。
認証後でauthorizationの前にロールを設定しなければならないのでロールの設定場所はHttpApplicationのAuthenticateRequestイベント、ということになる。
AuthenticateRequestイベントをハンドルするにはIHttpModule実装を用意する方法とglobal.asaxがあるみたい。
IHttpModule実相は受信フィルタパターンで使う予定だから(つうかもともと受信フィルタパターンをやってたのに。。。)今回はglobal.asaxに書いた。
global.asax
<%@ Application Language="C#" %> <script runat="server"> protected void Application_AuthenticateRequest(object sender, EventArgs e) { var ck = Context.Request.Cookies[FormsAuthentication.FormsCookieName]; if (ck == null) { return; } var ticket = FormsAuthentication.Decrypt(ck.Value); if (ticket == null) { return; } string role = (string)ticket.UserData; var fid = new FormsIdentity(ticket); var p = new System.Security.Principal.GenericPrincipal( fid, new[] { role }); Context.User = p; } </script>
Asp.Net Windows認証
2008/02/28 § コメントする
Windows認証
ページ要求を行っている認証されたユーザの情報
プロセスを実行しているユーザの情報
を表示するページを用意して、認証方法を変えて確認。
テスト用にTestUserというアカウントを用意した。
runasでTestUserでIEを起動してテストサイトにアクセスした。
C:\Program Files\Microsoft Visual Studio 9.0\VC>runas /user:Gerbera\TestUser ”C\Program Files\Internet Explorer\iexplore.exe”
01
IIS認証方法
匿名アクセス
page.html、page.aspxのACL
IUSER_XXX
ASP.NET
まぁこれはうまくいく。
認証されたアカウントはIsAnonymousだけど、プロセスを実装しているユーザはASP.NETなのは注意。
02
IIS認証方法
匿名アクセス許可
page.html、page.aspxのACL
ASP.NET
page.html、page.aspxともにアクセス失敗。
page.htmlは単純にIUSER_XXXでアクセスにいったが、アクセスが許可されていないので失敗。
page.aspxはasp.netがIUSER_XXXAclのチェックにいって失敗。
03
IIS認証方法
匿名アクセス許可
page.html、page.aspxのACL
IUSER_XXX
page.htmlはIUSER_XXXのアクセスが許可されているから表示できる。
page.aspxはasp.netのアクセス許可がないのでエラーになっている。
04
IIS認証方法
windows統合認証
page.html、page.aspxのACL
ASP.NET
TestUser
これはうまくいく。
認証されたユーザがTestUserになっている。
プロセスを実行しているユーザはやっぱりASP.NET
05
IIS認証方法
windows統合認証
page.html、page.aspxのACL
ASP.NET
TestUserはアクセスできないので、ダイアログがでた。
このダイアログでアクセス権のあるユーザでログインすればその後のページも表示される。
06
IIS認証方法
windows統合認証
page.html、page.aspxのACL
TestUser
page.htmlはTestUserのアクセスが許可されているから表示できる。
page.aspxはasp.netのアクセス許可がないのでエラーになっている。
07
IIS認証方法
windows統合認証
impersonate=”false”
偽装を有効にした。
認証されたユーザもプロセスを実行しているユーザTestUserになった。
ソース
default.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>
<a href="page.html">page.html</a>
<a href="page.aspx">page.aspx</a>
</div>
</form>
</body>
</html>
default.aspx.cs
using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Security;
using System.Security.Principal;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("ページ要求を行っている認証されたユーザの情報");
Response.Write("<hr>");
IIdentity iid = User.Identity;
iid.GetType().GetProperties().Where(
pi => pi.Name == "AuthenticationType" ||
pi.Name == "ImpersonationLevel" ||
pi.Name == "IsAuthenticated" ||
pi.Name == "IsGuest" ||
pi.Name == "IsSystem" ||
pi.Name == "IsAnonymous" ||
pi.Name == "Name" ||
pi.Name == "Groups").ToList().ForEach(
pi => this.Response.Write(pi.Name + ":" + pi.GetValue(iid, null) + "<br>"));
//グループも表示
//iid.GetType().GetProperties().Where(
// pi => pi.Name == "Groups").ToList().ForEach(
// pic =>
// {
// IdentityReferenceCollection ic = pic.GetValue(iid, null) as IdentityReferenceCollection;
// if (ic == null)
// {
// return;
// }
// ic.OfType<System.Security.Principal.IdentityReference>()
// .ToList().ForEach(
// t => Response.Write(t.Value + "<br>"+ t.Translate(typeof( NTAccount)).Value + "<br>"));
// });
Response.Write("<br>");
Response.Write("<br>");
Response.Write("プロセスを実行しているユーザの情報");
Response.Write("<hr>");
WindowsIdentity wi = WindowsIdentity.GetCurrent();
wi.GetType().GetProperties().Where(
pi => pi.Name == "AuthenticationType" ||
pi.Name == "ImpersonationLevel" ||
pi.Name == "IsAuthenticated" ||
pi.Name == "IsGuest" ||
pi.Name == "IsSystem" ||
pi.Name == "IsAnonymous" ||
pi.Name == "Name" ||
pi.Name == "Groups").ToList().ForEach(
pi => this.Response.Write(pi.Name + ":" + pi.GetValue(wi,null) + "<br>"));
}
}
page.aspx
<!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> ;Page</title>
</head>
<body>
<h1>Page.aspx</h1>
</body>
</html>
page.html
<hr>
<!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>Page</title>
</head>
<body>
<h1>Page.html</h1>
</body>
</html>
Asp.Net 認証
2008/02/28 § コメントする
やっと認証がまとまった。
正直よくわかってなかったので、すっきりできてよかった。
いい機会だったんだな。
windows認証
フォーム認証
パスポート認証
がある。
パスポート認証は調べなくていいや
http://www.atmarkit.co.jp/fdotnet/aspnet/aspnet17/aspnet17_01.html
●認証 Authentication
ユーザがアクセス権を持つユーザ本人であるか確認する。プロセス。
windows認証
windowsのアカウントで認証する。独自にユーザ管理の仕組みをつくらなくていい。
IISで認証をかけてしまうから、ASP.NETページにアクセスがあったときにはすでに認証済み。
認証プロトコルには
・基本認証
・ダイジェスト認証
・windows統合認証
がある。
ファイルなどのリソースへのアクセスする時に認証したWindowsアカウントのACLがチェックされる。
ASP.NETアプリのためにWindowsアカウントを作るのはよくない
イントラネット向け
フォーム認証
自分で認証を作るのだけど、ASP.NETのサポートが充実している。
IIS認証は匿名アクセスにする。
*偽装
web.configの
configuration/system.web/identity/@impersonate
をtrueにすると、ユーザが偽装され、IISによって認証されたユーザとしてコードが実行される。
フォーム認証では通常匿名アクセスなのでIUSER_XXX(インターネットゲストアカウント)としてコードが実行されるようになる。(これは意味がない)
windows認証だと、WindowsのユーザとしてIISに認証され、コードもWindowsのユーザとして実行されるため便利。
*ただし、偽装を有効にした場合には偽装するユーザがASP.NETのテンポラリディレクトリを読み書きできるようにACLの設定を行う必要がある。
●認定 Authorization
ファイルなどのリソースへのアクセスの可否の確認。
ファイル認定
Windows認証を使っているときだけ使える。
Windows認証にすれば自動的に有効化される。
ファイルなどのリソースへのアクセスする時に認証したWindowsアカウントのACLがチェックされる。
ファイルへのアクセス時に認証したWindowsアカウントでアクセスするわけではない。
アクセスするのはあくまでASPNETアカウント(XPなら、2003はNetwork Service)。
単に認証したWindowsアカウントのACLでチェックをかけるだけ。
URL認定
URLでのアクセス制御
web.configの
configuration/system.web/authorization
で指定。
authorizationで指定すると、web.configのあるディレクトリとそのサブディレクトリに適用される。
サブディレクトリのweb.configで違う設定があればそちらが優先。
?は非認証ユーザを表す。
rolesでロールでの指定もできる。
roleはWindows認証であればWindowsアカウントのグループになる。
フォーム認証でもroleの指定ができるがこちらは自分で管理。
例.
<authorization> <allow users="user1" /> <deny users="*" /> </authorization> <authorization> <deny users="?" /> </authorization> <authorization> <allow roles="DomainName\SampleGroup" /> <deny users="*" /> </authorization> <authorization> <allow roles="admin" /> <deny users="*" /> </authorization>
Asp.Net ViewStateのセキュリティ
2008/02/27 § コメントする
いつの間にかViewStateのセキュリティを調べてた。
えーと確か。。。
1.受信フィルタパターンを読んでて、HttpApplicationにAuthenticateRequestとAuthorizeRequestというイベントが出てきた。
↓
2.AuthenticateRequestとAuthorizeRequestがいつ発生するのかよくわからない。
↓
3.ASP.NETの認証回りをしらないからわかんないんだろうと判断。
↓
4.認証を調べだす。
↓
5.どこかで迷路に迷い込む。
↓
6.ViewStateのセキュリティについてまとた。←いまココ
4と5の間に何があったのやら。
ViewStateには改ざん対策としてSHA1のハッシュ値がチェックサムとして付加されている。
下記の鍵付きハッシュを使っていない場合、シリアライズされたデータがBase64になってるだけだし、ハッシュ値は誰でも計算できるので安全ではない。
安全対策 その1 鍵付きハッシュにする(MAC エンコーディング) たぶんデフォルトはここ。
サーバ上に保存された秘密鍵をデータにつけてハッシュを計算する
改ざんの検知はできるけど、再送攻撃(同じデータを送ってしまう)、データの解読はできる。
各ページのPageディレクティブのEnableViewStateMacをtrueに、もしくはweb.configの
configuration/system.web/pages/@enableViewStateMac
をtrueにすると有効になる。
安全対策 その2 ViewStateの暗号化
viewstateを暗号化する。
デフォルトで使用される秘密鍵はサーバごとに異なるため、webファーム構成にするなら各ノードの秘密鍵をそろえておくこと。
そうしなければ他のサーバにポストバックしたときにセキュリティエラーとなってしまう。
web.configの
configuration/system.web/machineKey/@validation
で暗号化アルゴリズムを指定
configuration/system.web/machineKey/@validationKey
で暗号化のキーを指定
http://www.atmarkit.co.jp/fdotnet/entwebapp/entwebapp03/entwebapp03_04.html
また、各ページのPageディレクティブのViewStateEncryptionMode、もしくはweb.configの
configuration/system.web/pages/@viewStateEncryptionMode
で暗号化のモードを選択する。
*Pageディレクティブの設定はweb.configの設定をOverrideする。
Always:ビューステートを常に暗号化します。
Auto:コントロールが要求した場合に、ビューステートを暗号化します。
Never:コントロールが要求した場合でも、ビューステートを暗号化しません。
安全対策 その3 ユーザ固有情報の付加
http://msdn2.microsoft.com/ja-jp/library/ms178199(VS.80).aspx
http://www.microsoft.com/japan/msdn/net/aspnet/securitybarriers.aspx#securitybarriers_topic2
暗号化しても再送攻撃、ワンクリック攻撃には弱い。
ハッシュ計算、暗号化をしても、データの書き換え、読み込みはできないが、そのデータが誰のものであるかは認識できない。
このため、
あるユーザ向けに出力されたviewStateを別のユーザがサーバに再送した場合、正常なViewStateと認識されてしまう。
ViewStateUserKeyにユーザ固有情報(ログイン名、セッションID)を含めることでこれを回避できる。
Page_InitメソッドでViewStateUserKeyにユーザ固有情報を設定すれば、viewstateの復元時に、asp.netが
ViewStateとViewStateUserKeyのつき合わせをやって、ずれていれば例外にしてくれる。
void Page_Init (object sender, EventArgs e) {
ViewStateUserKey = Session.SessionID;
}
としておくだけでいい。(たぶん)
規定値
●EnableViewStateMac
@Pageディレクティブ:false
http://msdn2.microsoft.com/ja-jp/library/950xf363(VS.80).aspx
web.config:true
http://msdn2.microsoft.com/ja-jp/library/system.web.ui.page.enableviewstatemac.aspx
●configuration/system.web/machineKey/@validation
web.config:SHA1
http://msdn2.microsoft.com/ja-jp/library/w8h3skw9(VS.80).aspx
●configuration/system.web/machineKey/@validationKey
web.config:AutoGenerate,IsolateApps
http://msdn2.microsoft.com/ja-jp/library/w8h3skw9(VS.80).aspx
●ViewStateEncryptionMode
@Pageディレクティブ:Auto
http://msdn2.microsoft.com/ja-jp/library/system.web.ui.page.viewstateencryptionmode.aspx
web.config:Auto
http://msdn2.microsoft.com/ja-jp/library/950xf363(VS.80).aspx
Asp.Netエンタープライズソリューションパターン その3 フロントコントローラ
2008/02/25 § コメントする
その3。
こんどはフロントコントローラ。
ASP.NETは全力でページコントローラを支援している感じなので、あえてフロントコントローラにするメリットは薄そうな気がする。
大規模なサイトなんか作ったことないからそう思うのかなぁ。
18:47
さて、3回目。ピッチを上げなくては。
とりあえず本を読むか。
18:49
ページコントローラではなく、フロントコントローラにしたくなる動機。
●ページコントローラのベースクラスが複雑化してきた。
ロジックの複雑化。
継承関係が深くなる。
*リクエストを検証し、パラメタによってぺージを遷移させるようなことはページコントローラの基本クラスでするべきではない。
すべてのページに共通の処理ではないから。ページコントローラの基本クラスでは全ページでの共通機能を書く。
●アクションやナビゲーションを外部ファイルで構成
ページコントローラではリテラルで埋めることになりがち??
*すべてのページに特定のアクションを適用するのが困難って書いてあるけど、これはページコントローラでも解決できるやろ。
URLとコントローラ。。。メリットわからん。
実装概要
ハンドラ:リクエストからパラメタを取得
↓
コマンド:パラメタに対応したコマンドの実行。コマンド実装後、適切なビューに要求を転送
↓
ビュー:HTMLの構築
19:09
.Netでの実装部分に入った。
19:24
実装内容をざっと読んだ。
なるほど。実行するアクションはcookieかQueryString、formかどっかに入ってて、その後の遷移先のページはUrlからマッピングするという方針らしい。
処理後に移動するページはコマンド内で決めた方がいいんじゃないか?
コマンドのアクションを実行してみないと移動先がわかんないことがありそう。
たとえば注文のエントリの内容によって、クレジット番号いれさすページに行くか、それをスキップするか、とか。。。
まぁ、遷移先のページをUrlからMapするのはRedirectingCommandの挙動だから、ヤなら違うのつくればいいということ。
19:32
本で使ってるIHttpHandlerは同期。
IHttpAsyncHandlerもあって、こっちは非同期。
IHttpAsyncHandlerを調べる。
19:50
ググったがあんまり日本語の情報が引っかからない。
あえてIHttpAsyncHandlerでやるか。
http://codezine.jp/a/article/aid/1081.aspx?p=2
http://www.ailight.jp/blog/kazuk/articles/6312.aspx
19:51
実装を始めよ。
・IHttpAsyncHandlerを使う。
・マスタページも使う。
くらいが目標でいいか。
19:58
CommandFactoryがsiteをとってきてコマンドを選んでるのはちょっと。。。変えよ。
20:01
もう少しボトムアップにクラスのソース載せる・・・わけにもいかんかぁ。
20:07
あえてXLINQも使うことにする。
XMLNodeからXElementにするにはどうすりゃいいんだ??
20:17
http://blogs.wankuma.com/pinzolo/archive/2007/04/22/72442.aspx
XDocumentとXmlDocumentの相互変換はちょっと微妙らしい。
20:22
Web.Configのどこにcontroller.mappingの記述追加すりゃいいのかわからん。
まえこれやったはずだけど、覚えてないよ。
調べた。configrationの直下でいいのか。
20:48
sectionにも書いた上でcontroller.mappingを追記しないとコンパイルエラーになるぞ。
しかしsectionに書くとタイプの指定もしないといけないな。
タイプはUrlMapでいいのか。
App_CodeにUrlMapを入れてるんだけど、コンパイル後のdllの名前がわからんぞ。
困った。
21:03
MSBuildでコンパイルしたら、PrecompiledWebにdllができるみたいなので、それでdllの名前を調べてみた。
App_Code.dllだって。そのままといえばそのまま。
とりあえずtypeにはApp_Codeを指定してみる。うまくいくのか??
21:21
XmlNodeからXElementにはこんな感じで変換した。うまくいってるっぽい。
XElement xe = XElement.Parse(section.OuterXml);
var ele = from e in xe.Descendants(“entry”)
select e;
21:24
ConfigurationSettings.GetConfigは古い形式だって。
じゃあ新しいのは何なんだよ。
調べる。。。System.Web.Configuration.WebConfigurationManager.GetSectionだそうだ。
もしくはSystem.Configuration.ConfigurationManager.GetSection
21:45
食事のため中断。
12:55
再開。
01:01
中断。寝る。
10:24
再開。
コリコリとコーディングを始める。
。。。VSの起動が重い。
サンプルにはないエラー発生時のページを用意したい。
10:30
IHttpAsyncHandlerを使うことにしたの忘れてた。
BeginProcessRequest内では非同期処理を開始して、すぐに制御を戻す。
EndProcessRequestで終わる。でいいのかな??
delegateを用意して非同期にしてみよう。
EndProcessRequestは処理が終わったら呼ばれるのか、中で自分で終わるのを待つのか?
どっちだ??
#処理が終わってからEndProcessRequestが呼ばれるので、自分でまたなくていいみたい。
FrontController向けにマスタページをもう一度つくらなきゃ。メンド。
コピペするか。
さて、できた。テストを開始。
10:45
web.configにHandler登録忘れてた。orz
web.configを手でいじりがちだけど、VSから変えてみる。
メニューの
webサイト→ASP.NET構成
と、思ったら、そんなに細かいところまではいじれないみたい。
既定のエラーページの構成は
アプリケーションタブでできそう。
httpHandlers 要素 (ASP.NET 設定スキーマ)
http://msdn2.microsoft.com/ja-jp/library/bya7fh0a.aspx
httpHandlers の add 要素 (ASP.NET 設定スキーマ)
http://msdn2.microsoft.com/ja-jp/library/7d6sws33.aspx
10:58
パラメタ渡す画面がいるな。
11:06
DBにデータいれないと。
11:07
マシン遅いなぁ。新しいのほしいなぁ。でもvista搭載マシンは・・・。
11:26
ページがでてくれない。。。なんで。。。
11:49
できた。
結局
httpHandlersでの指定が、
<add verb="*" path="ActualPage*" type="Handler,App_Code"/>
になってて、
Server.Transfer先のページがActualPage1.aspxになってたために、処理するhttpHanderがまた、Handlerになってしまっていたのでエラーになってたみたい。
ただ、処理中の例外はEndProcessRequestの中で自分でdelegateのEndInvokeを呼ばないと例外が発生しない。
IAsyncResult resultをSystem.Runtime.Remoting.Messaging.AsyncResultにキャストして
AsyncDelegateからDelegateをとってきて、EndInvokeを呼べば例外が発生した。
13:05
エラーページをカスタムにしよう。
調べる。
おお。三種類も方法があるのか。
http://support.microsoft.com/kb/308132/ja
http://japan.internet.com/developer/20050906/25.html
●ページのPage_Error、OnErrorで処理
Server.GetLastErrorでエラーを拾って同一ページに表示するなり、別のエラーページに飛ばすなりする
●global.asaxのApplication_Error
Server.GetLastErrorでエラーを拾ってエラーのぺージに飛ばすなりする。
エラーのロギングするならここがよさそう。
●web.configのcustomErrorsでカスタムエラーページを定義
モードに
On:カスタムエラー有効
Off:カスタムエラー無効
RemoteOnly:リモートクライアントにはカスタムエラーが表示される。デバッグに便利。
がある。
今回はPage_Errorでとって、エラーページに飛ばす。
13:26
できた。ふぅ。
ソース長いな。まぁいいか。一応載せとく。
DBアクセスはDLINQでやった。
ConfigのパースはXLINQで。あんま意味ないけど。
FcCaller.aspx(呼び出す画面)
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FcCaller.aspx.cs" Inherits="FcCaller" %> <!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> <a href="ActualPage1?cmd=macro">macro</a> <br /> <a href="FrontControllerPage1?cmd=macro">macro</a> <br /> <a href="FrontControllerPage2?cmd=micro">micro</a> </div> </form> </body> </html>
FcCaller.aspx.cs(呼び出す画面)
using System; public partial class FcCaller : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } }
BasePageForFrontController.master(マスタページ)
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="BasePageForFrontController.master.cs" Inherits="BasePageForFrontController" %> <!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="Head1" runat="server"> <asp:ContentPlaceHolder id="head" runat="server"> </asp:ContentPlaceHolder> </head> <body> <form id="headerForm" runat="server"> <div style="background-color: #9c0001"> <span style="font-size: small; color:#ffffff">ようこそ</span> <asp:Label ID="eMail" runat="server" Font-Size="Small" ForeColor="White" Text="xxx"></asp:Label> </div> <div style="font-size: x-large; color: #ffffff; background-color: #d3c9c7;"> <asp:Label ID="siteName" runat="server" Text="xxx"></asp:Label> </div> <div> <asp:ContentPlaceHolder id="body" runat="server"> </asp:ContentPlaceHolder> </div> </form> </body> </html>
BasePageForFrontController.master.cs(マスタページ)
using System; public partial class BasePageForFrontController : System.Web.UI.MasterPage { protected void Page_Load(object sender, EventArgs e) { if (this.IsPostBack) { return; } this.eMail.Text = (string)Context.Items["address"]; this.siteName.Text = (string)Context.Items["site"]; } }
ActualPage1.aspx(コンテンツ1)
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ActualPage1.aspx.cs" Inherits="ActualPage1" MasterPageFile="~/BasePageForFrontController.master" %> <asp:Content ID="hederContent" ContentPlaceHolderID="head" runat="server"> <title>ページ1</title> </asp:Content> <asp:Content ID="bodyContent" ContentPlaceHolderID="body" runat="server"> <div style="font-size: xx-large"> ページ<asp:Label ID="pageNumber" runat="server" Text="xxx"></asp:Label> </div> </asp:Content>
ActualPage1.aspx.cs(コンテンツ1)
using System; public partial class ActualPage1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { //throw new Exception("エラーのテスト"); this.pageNumber.Text = "1"; } private void Page_Error(object sender,EventArgs e) { Context.Items["error"] = Server.GetLastError(); Server.Transfer("FcError.aspx"); } }
ActualPage2.aspx(コンテンツ2)
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ActualPage2.aspx.cs" Inherits="ActualPage2" MasterPageFile="~/BasePageForFrontController.master" %> <asp:Content ID="hederContent" ContentPlaceHolderID="head" runat="server"> <title>ページ1</title> </asp:Content> <asp:Content ID="bodyContent" ContentPlaceHolderID="body" runat="server"> <div style="font-size: xx-large"> ページ<asp:Label ID="pageNumber" runat="server" Text="xxx"></asp:Label> </div> </asp:Content>
ActualPage2.aspx.cs(コンテンツ2)
using System; public partial class ActualPage2 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { this.pageNumber.Text = "2"; } }
FcError.aspx(エラーページ)
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FcError.aspx.cs" Inherits="FcError" %> <!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:Label ID="lblError" runat="server" Text="xxx"></asp:Label> </div> </form> </body> </html>
FcError.aspx.cs(エラーページ)
using System; public partial class FcError : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { this.lblError.Text = "予期しないエラーが発生しました"; if (!Context.Items.Contains("error")) { return; } this.lblError.Text = ((Exception)Context.Items["error"]).Message; } }
Handler.cs(HttpHandler)
using System; using System.Web; public delegate void AsyncExecute(HttpContext context); public class Handler : IHttpAsyncHandler { public Handler() { } public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData) { Command cmd = CommandFactory.Make(context.Request.Params); AsyncExecute ae = new AsyncExecute(cmd.Execute); return ae.BeginInvoke(context, cb, extraData); } public void EndProcessRequest(IAsyncResult result) { System.Runtime.Remoting.Messaging.AsyncResult ar = result as System.Runtime.Remoting.Messaging.AsyncResult; if(ar==null) { return; } if(ar.EndInvokeCalled) { return; } AsyncExecute ae = ar.AsyncDelegate as AsyncExecute; if(ae == null) { return; } try { ae.EndInvoke(result); }catch(Exception e) { System.Diagnostics.Debug.WriteLine(e.ToString()); throw e; } } public bool IsReusable { get { return true; } } public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); } }
Command.cs(Commandインターフェース)
using System.Web; public interface Command { void Execute(HttpContext context); } CommandFactory.cs using System.Collections.Specialized; public class CommandFactory { private CommandFactory() { } public static Command Make(NameValueCollection prms) { string cmd = prms["cmd"]; Command act = new UnknownCommand(); if (cmd == "micro") { act = new MicroSite(); } else if (cmd == "macro") { act = new MacroSite(); } return act; } }
RedirectingCommand.cs
using System.Web; public abstract class RedirectingCommand :Command { public abstract void OnExecute(HttpContext context); public void Execute(HttpContext context) { UrlMap map = UrlMap.Instance; OnExecute(context); string url = string.Format( "{0}{1}", map.Map[context.Request.Url.AbsolutePath], context.Request.Url.Query); context.Server.Transfer(url); } }
MacroSite.cs
using System.Web; public class MacroSite :RedirectingCommand { public override void OnExecute(HttpContext context) { string id = context.User.Identity.Name; context.Items["address"] = AspnetStudyGateway.GetAddressFromCustomer(id); context.Items["site"] = "Macro-Site"; } }
MicroSite.cs
using System.Web; public class MicroSite :RedirectingCommand { public override void OnExecute(HttpContext context) { string id = context.User.Identity.Name; context.Items["address"] = AspnetStudyGateway.GetAddressFromWebUsers(id); context.Items["site"] = "Micro-Site"; } }
UnknownCommand.cs
using System; using System.Web; public class UnknownCommand :RedirectingCommand { public override void OnExecute(HttpContext context) { throw new Exception("Urlは無効です"); } }
UrlMap.cs
using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Web.Configuration; using System.Xml.Linq; public class UrlMap :IConfigurationSectionHandler { public static UrlMap Instance { get { return (UrlMap)WebConfigurationManager.GetSection("controller.mapping"); } } private Dictionary<string, string> map_; private UrlMap() { } public Dictionary<string, string> Map { get { return map_; } } public UrlMap(object parent, object configContext, System.Xml.XmlNode section) { map_ = new Dictionary<string, string>(); XElement xe = XElement.Parse(section.OuterXml); var ele = from e in xe.Descendants("entry") select e; foreach(var e in ele) { map_.Add(e.Attribute("key").Value, e.Attribute("page").Value); } } public object Create(object parent, object configContext, System.Xml.XmlNode section) { return new UrlMap(parent, configContext, section); } }
AspnetStudyGateway.cs
using System.Linq; public class AspnetStudyGateway { public AspnetStudyGateway() { } public static string GetAddressFromCustomer(string id) { var u = from user in Context.customer where user.id == id select user; return u.First().address; } public static string GetAddressFromWebUsers(string id) { return GetAddress(id); } public static string GetAddress(string id) { var u = from user in Context.webusers where user.id == id select user; return u.First().address; } private static Aspnet_studyDataContext Context { get { var con = new Aspnet_studyDataContext( "Data Source=Gerbera;Initial Catalog=aspnet_study;User ID=sa;Password=p"); return con; } } }
web.configに追記したもの
configuration/configSections/に追加
<section name="controller.mapping" type="UrlMap,App_Code"/>
configuration/に追加
<controller.mapping> <entries> <entry key="/Work/ActualPage1" page="ActualPage1.aspx" /> <entry key="/Work/FrontControllerPage1" page="ActualPage1.aspx" /> <entry key="/Work/FrontControllerPage2" page="ActualPage2.aspx" /> </entries> </controller.mapping>
configuration/system.web/httpHandlersに追加
<add verb="*" path="FrontControllerPage*" type="Handler,App_Code"/>
Asp.Netエンタープライズソリューションパターン その2 ページコントローラ
2008/02/22 § コメントする
前から十日あいてるじゃん。Lispの方が面白くてさ・・・。
勉強するにあたって、自分がやってるあいだ自分が何考えてるのか詳細に記録したくなった。
なんか異様に時間かかるから。
作業のログをとりながらやってみた結果がこれ。
脇道にそれて調べるてて時間がかかってるっぽい。
まぁそれはしょうがないか。
ページコントローラ
MVCのコントロラ部の実装指針の一つ。別解がフロントコントローラ。
ASP.NETはページコントロラ指向。Winformぽく開発させたかったらそうなるよな。
本ではヘッダ部分をインクルードファイルにしているが、これをマスターページに変更したい
マスターページ(Header.Master)、ページ(Page1.aspx)を作成して、Page1.aspxのMasuterPageFileにHeader.Masterを設定したが、
「コンテンツ コントロールは、コンテンツ ページまたはマスタ ページを参照する入れ子にされたマスタ ページでトップ レベルのコントロールでなければなりません。」
とエラー。
こういう使い方ではないらしい。
11:54
http://www.microsoft.com/japan/msdn/thisweek/300×10/Phase3/masterpage/cs.aspx
を見る。
マスターページ
ContentPlaceHolderタグを書く。
これがコンテンツを置く場所になる。ContentPlaceHolderは複数配置可能。
<asp:contentplaceholder id=”xxx”>はなく、マスターページのものが使われるらしい。
(これってどうなんだろ??htmlのタイトルはマスターページじゃなくてコンテンツになるページで定義したくないか??
Loadで書き直せばいいにせよ・・・)
コンテンツページ
ページディレクティブでマスターぺージを指定
<%@Page MasterPageFile=”~/xxx.master” …. %>
Contentコントロールに各ページのコンテンツを書く。
ContentPlaceHolderIDにマスタぺージのContentPlaceHolderのidを指定して配置場所を指定。
ふむ。関連付けの方法はわかった。
しかしこれでは、マスターページを使用する場合としない場合でaspxファイルの内容がまったく変わるじゃん。
コンテンツになるページはコンテンツページとかいう名前にして、拡張子もわけちゃった方が良かったのではないか?
と疑問がわく。
12:05
やり方はわかったから実装。
新規aspxを作るときにマスターぺージを指定するチェックをつけると、マスタページを使用する内容でデフォルトのaspxファイルを作ってくれるけど、天の邪鬼なので手でやることにする。
12:08
あー、そういうことか。
マスターページのヘッダ部分にもContentPlaceHolderタグがある。
タイトルとか、各ページに持たせたい部分はこの中にかけってことか。
納得。
12:13
HTMLコントロールにSapnがないのはなんでだろう??
12:19
スタイルシートの指定はxxx:値の形式で;区切りであることを忘れていた。記憶力が落ちてないか
12:22
マスタページを作り中・・・。
aspxコントロールでデザインをスタイルシートで指定したいときにはどうしたらいんだ??
VS上での設定がどうhtmlに展開されるのかわからない。
動かすしかないか。
ForeColorはStyleでの指定になってた。
外部スタイルシートを使いたいときにはどうすんだろ?
おお、CssClassというのがあった。
あとSkinIDも怪しい。
12:32
マスタページできた。時間かかりすぎ・・・。
いや、マシンが非力だし、あちこちいじりながらだし、このテキストも書いてるし・・・。
ページを作る。
12:34
デフォでマスタページにFormがあるのはどうなんだ??
コンテンツに持たせるケースのが多くないのか?
いや、まてよ。どうせポストバックして自分にポストするのがAsp.Netだからマスタページにあっていのか。
12:40
ページできた。
ノートPC遅え。
ちょっと休憩
12:44
復帰。ロジック書くか。
Loadが呼ばれる順番ってどうなんだろ。
マスタページ→コンテンツだよね。
確かめる。
IEの起動遅い。
コンテンツ→マスタ
だった。えぇえぇ。逆じゃないの??
調べる。
http://msdn2.microsoft.com/ja-jp/library/dct97kc3.aspx
Initはマスタページが先なのか。
うーん。きっとコンテンツの方が先に走った方がいいからこうなってるんだよね?
Initっていつ呼ばれるんだ??
http://msdn2.microsoft.com/ja-jp/library/ms178472(VS.80).aspx
ここに説明があった。
が、InitとLoadの差がわかんない。調べる。
Initはボトムアップ(子コントロールから)
Loadはトップダウン(親コントロールから)
の順でイベントが起きるらしい。
http://blogs.wankuma.com/jitta/archive/2005/11/24/19572.aspx
Init→ViewState復元→ポストバックデータ処理→Loadとなるのか。
ふむ。
なら、Initではテキストボックスの値とかまだ取れないのかな?
やってみる。
つか、Page_LoadとかPage_Initとイベントの関連付けってどうやってるんだろ。
そっちさきに調べる。
http://msdn2.microsoft.com/ja-jp/library/ms227455.aspx
PageディレクティブのAutoEventWireupがTrueだと、勝手にイベントとの関連付けをするらしい。
シグニチャだけで関連付けるから、タイプミスには注意。
この辺のオートマ具合がMSだなぁ。個人的には気持ち悪い。
勝手に関連づけられるのは
</asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_Init</asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_DataBind</asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_PreRender</asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_Unload</asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_Error</asp:contentplaceholder> <asp:contentplaceholder id="xxx">マニュアルには書いてない(らしい)けど以下も </asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_AbortTransaction</asp:contentplaceholder> <asp:contentplaceholder id="xxx">OnTransactionAbort</asp:contentplaceholder> <asp:contentplaceholder id="xxx">Page_CommitTransaction</asp:contentplaceholder> <asp:contentplaceholder id="xxx">OnTransactionCommit</asp:contentplaceholder> <asp:contentplaceholder id="xxx">で、Init。</asp:contentplaceholder> <asp:contentplaceholder id="xxx">試してみたら確かにポストバックされたデータはまだ復元されてない。
よしよし。
13:31
調べてただけでこんな時間に。
またロジック書いてないじゃん。つか、その前にDBつくらな。
今回もDLINQで行くつもり。
14:17
エアコン工事が来てたので中断しつつも完成。
まとめ
MVCと特に変わりはなくて、この章で追加されたことは各ページの共通処理を抽象クラスにまとめようね、ということだけ。
ただ、マスタページを使えば大抵は事足りる、のかもしれない。
PageのLoad前後に共通処理がある場合なんかはやっぱりBasePageとなる抽象クラスを作った方がいいのかも。
ソース
PageBase.master
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="PageBase.master.cs" Inherits="Header" %> <!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"> <asp:ContentPlaceHolder id="head" runat="server"> </asp:ContentPlaceHolder> </head> <body> <form id="headerForm" runat="server"> <div style="background-color: #9c0001"> <span style="font-size: small; color:#ffffff">ようこそ</span> <asp:Label ID="eMail" runat="server" Font-Size="Small" ForeColor="White" Text="xxx"></asp:Label> </div> <div style="font-size: x-large; color: #ffffff; background-color: #d3c9c7;"> <asp:Label ID="siteName" runat="server" Text="xxx"></asp:Label> </div> <div> <asp:ContentPlaceHolder id="body" runat="server"> </asp:ContentPlaceHolder> </div> </form> </body> </html>
PageBase.master.cs
using System; using System.Collections; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Xml.Linq; public partial class Header : System.Web.UI.MasterPage { protected void Page_Load(object sender, EventArgs e) { if (this.IsPostBack) { return; } this.eMail.Text = AspnetStudyGateway.GetAddress( this.Context.User.Identity.Name); this.siteName.Text = "MacroSite"; } }
Page1.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Page1.aspx.cs" Inherits="Page1" MasterPageFile="~/PageBase.master" %> <asp:Content ID="hederContent" ContentPlaceHolderID="head" runat="server"> <title>ページ1</title> </asp:Content> <asp:Content ID="bodyContent" ContentPlaceHolderID="body" runat="server"> <div style="font-size: xx-large"> ページ<asp:Label ID="pageNumber" runat="server" Text="xxx"></asp:Label> </div> </asp:Content>
Page1.aspx.cs
using System; using System.Collections; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; public partial class Page1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { this.pageNumber.Text = "1"; } }
AspnetStudyGateway.cs
using System; using System.Data; using System.Configuration; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; public class AspnetStudyGateway { public AspnetStudyGateway() { } public static string GetAddress(string id) { var u = from user in Context.webusers where user.id == id select user; return u.First().address; } private static Aspnet_studyDataContext Context { get { var con = new Aspnet_studyDataContext( "Data Source=Gerbera;Initial Catalog=aspnet_study;User ID=sa;Password=p"); return con; } } }
Asp.Netエンタープライズソリューションパターン その1 MVC
2008/02/11 § コメントする
仕事で使いそうなのでASP.NETを詰め込み勉強。
Web系から5年くらいは遠ざかってるし、.NetでもAsp.Netは触ってないので素振りしておくことにした。
で、「.Netによるエンタープライズソリューションパターン」を読む。
最初の方、訳が良くないのか、概念的な話だからなのか読み辛い。
パターンのカタログだと思って3章から読んでもいいのかも。
まずはMVCから。
●モデル
テーブル(DBのビュー)と一対一対応
DLINQならデータクラス?
●ビュー
情報の表示を管理。
xxx.aspxファイル
●コントローラ
ユーザからの入力を解釈して、モデルやビューに変更を通知
ここでは≒ページコントローラ
コードビハインドしたcsファイル。
モデルはビューにもコントローラにも依存しない
ビューはモデルに依存
コントローラはビュー、モデル、双方に依存
サンプルコードで
string selectCmd = string.Format("select.... Where recordingId={0}...",recordingId);
とかやってるのはヤめてほしいな。
ASP.NETでのモデル ビュー コントローラ の実装、を写経しようかと思ったんだけど、さすがに本が古いので少し変える。
モデルクラスではDLINQを使ってみた。
データバインドできるのかなーと、DataSourceに突っ込んでみたらあっさりバインドできた。
mvc.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MVC.aspx.cs" Inherits="MVC" %> <!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>MVC</title> </head> <body> <form id="form" runat="server"> <h3>楽曲</h3> 楽曲の選択:<br /> <asp:DropDownList ID="recordingSelect" runat="server" /> <asp:Button ID="Button1" runat="server" Text="submit" onclick="Button_Click" /> <p /> <asp:DataGrid id="MyDataGrid" runat="server" Width="600" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding="3" CellSpacing="0" Font-Names="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" EnableViewState="false" /> </form> </body> </html>
mvc.aspx.cs
using System; using System.Collections; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; public partial class MVC : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (IsPostBack) { return; } this.recordingSelect.DataSource = DatabaseGateway.GetRecorginds(); this.recordingSelect.DataTextField = "title"; this.recordingSelect.DataValueField = "id"; this.recordingSelect.DataBind(); } protected void Button_Click(object sender, EventArgs e) { MyDataGrid.DataSource = DatabaseGateway.GetTracs(Convert.ToInt32(recordingSelect.SelectedValue)); MyDataGrid.DataBind(); } }
DatabaseGateway.cs
using System; using System.Data; using System.Configuration; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; public class DatabaseGateway { private DatabaseGateway() { } public static IQueryable<recording> GetRecorginds() { RecordingsDataContext recordings = new RecordingsDataContext("Data Source=Gerbera;Initial Catalog=recordings;Persist Security Info=True;User ID=sa;Password=p;Pooling=False"); return from rec in recordings.recording select rec; } public static IQueryable<trac> GetTracs(int recordingId) { RecordingsDataContext recordings = new RecordingsDataContext("Data Source=Gerbera;Initial Catalog=recordings;Persist Security Info=True;User ID=sa;Password=p;Pooling=False"); return from trac in recordings.trac where trac.recordingId == recordingId select trac; } }