2010年9月11日土曜日

プロコン2010-1その5

今回は、ユーザ追加画面。
こんな感じの画面です。

で、今回も先に、コードをさらします。
まずは、~/Admin/AddUser.aspxの内容から。

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
<asp:CreateUserWizard ID="CreateUserWizard1" runat="server" OnCreatedUser="CreateUserWizard1_CreatedUser">
<WizardSteps>
<asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
<ContentTemplate>
<table border="0">
<tr>
<td align="center" colspan="2">
新しいアカウントにサインアップ
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="UserNameLabel" runat="server" AssociatedControlID="UserName">ユーザー名:</asp:Label>
</td>
<td>
<asp:TextBox ID="UserName" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="UserNameRequired" runat="server" ControlToValidate="UserName"
ErrorMessage="ユーザー名が必要です。" ToolTip="ユーザー名が必要です。" ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="PasswordLabel" runat="server" AssociatedControlID="Password">パスワード:</asp:Label>
</td>
<td>
<asp:TextBox ID="Password" runat="server" TextMode="Password"></asp:TextBox>
<asp:RequiredFieldValidator ID="PasswordRequired" runat="server" ControlToValidate="Password"
ErrorMessage="パスワードが必要です。" ToolTip="パスワードが必要です。" ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="ConfirmPasswordLabel" runat="server" AssociatedControlID="ConfirmPassword">パスワードの確認入力:</asp:Label>
</td>
<td>
<asp:TextBox ID="ConfirmPassword" runat="server" TextMode="Password"></asp:TextBox>
<asp:RequiredFieldValidator ID="ConfirmPasswordRequired" runat="server" ControlToValidate="ConfirmPassword"
ErrorMessage="パスワードの確認入力が必要です。" ToolTip="パスワードの確認入力が必要です。" ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="EmailLabel" runat="server" AssociatedControlID="Email">電子メール:</asp:Label>
</td>
<td>
<asp:TextBox ID="Email" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="EmailRequired" runat="server" ControlToValidate="Email"
ErrorMessage="電子メールが必要です。" ToolTip="電子メールが必要です。" ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="QuestionLabel" runat="server" AssociatedControlID="Question">セキュリティの質問:</asp:Label>
</td>
<td>
<asp:TextBox ID="Question" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="QuestionRequired" runat="server" ControlToValidate="Question"
ErrorMessage="セキュリティ質問が必要です。" ToolTip="セキュリティ質問が必要です。" ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td align="right">
<asp:Label ID="AnswerLabel" runat="server" AssociatedControlID="Answer">セキュリティ返答:</asp:Label>
</td>
<td>
<asp:TextBox ID="Answer" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="AnswerRequired" runat="server" ControlToValidate="Answer"
ErrorMessage="セキュリティ返答が必要です。" ToolTip="セキュリティ返答が必要です。" ValidationGroup="CreateUserWizard1">*</asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td colspan="2">
<asp:CheckBoxList ID="CheckBoxList1" runat="server" RepeatDirection="Horizontal"
DataSourceID="ObjectDataSource1" DataTextField="RoleName" DataValueField="RoleName">
</asp:CheckBoxList>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetAllRole"
TypeName="Library.DAC.RoleInfo"></asp:ObjectDataSource>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<asp:CompareValidator ID="PasswordCompare" runat="server" ControlToCompare="Password"
ControlToValidate="ConfirmPassword" Display="Dynamic" ErrorMessage="パスワードと確認用パスワードは一致しなければなりません。"
ValidationGroup="CreateUserWizard1"></asp:CompareValidator>
</td>
</tr>
<tr>
<td align="center" colspan="2" style="color: Red;">
<asp:Literal ID="ErrorMessage" runat="server" EnableViewState="False"></asp:Literal>
</td>
</tr>
</table>
</ContentTemplate>
</asp:CreateUserWizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
</asp:CreateUserWizard>
</asp:Content>


もちろん、こんな長いコードをコードで打ったわけではありません。VisualStudioのデザインビューで操作していたらこんな長くなっちゃっただけです。あとから、デザインビューの操作を記述します。

続いて、コードビハインド。

using System;
using System.Web.Security;
using System.Web.UI.WebControls;

namespace Library.Admin
{
public partial class AddUser : LibPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
CheckBoxList cbl = CreateUserWizardStep1.Controls[0].FindControl("CheckBoxList1") as CheckBoxList;
if (cbl != null)
{
foreach (ListItem tmp in cbl.Items)
{
if (tmp.Selected)
{
Roles.AddUserToRole(CreateUserWizard1.UserName, tmp.Text);
}
}
}
}
}
}



今回のシステムでは、ユーザ登録と同時にロール登録もしてしまうようにしたのでその部分だけをコード化しています。そういえば、コードビハインドの部分、スケルトンでは冒頭のusing行がいっぱい出てくるんだけど、要らないものがいっぱいあるので、いったんコード作成が完了したら、コード画面で右クリックしてUsingの整理(削除および並び替え)をしておいたほうがすっきりします。


今回も、AddUser2.aspxを元にデザイン操作を見ていきます。

準備として、何時も通りの編集で、こんなコードにしておきます。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AddUser2.aspx.cs" Inherits="Library.Admin.AddUser2"
MasterPageFile="~/Admins.Master" StylesheetTheme="Standard" %>

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
</asp:Content>



次にデザイン、今回は並べて表示で見ていきます。
こんな画面から、


まず左の方、CreateUserWizardコントロールを配置。


続いて、ユーザ作成ステップのカスタマイズを選びます。(ここでデザインのファイルサイズがぐぐっと増大)



ドキュメントアウトラインを参考に、並べて表示の上の画面で、tr,tdタグを入れて場所を確保します。保存すれば、デザインの方も反映されます。

続いて確保した場所にCheckBoxListコントロールを配置します。

データソースの選択で、新しいデータソース、オブジェクトを選択します。


DACのコードにデータコンポーネント指定のアトリビュートを付けておけばチェックボックスを外さなくても
選べたんだけど。。。付けてなかったのでチェックを外して選びます。(DAC,DataSet,DataAdapterの作成は次回に解説します。)
で、Selectの部分で、作っておいたメソッドを指定します。


このWizardの最後に、チェックボックスに指定する値を選択します。


このコントロールを選んで、イベントを設定。
CreateUserWizardを選択した状態で、プロパティタブで稲妻アイコンをクリックして、CreateUserの右のドロップダウンリストが出ているところでダブルクリックするとイベントハンドラが設定され、コードビハインド画面に行く。


ここで最初のコードを記述する。

2010年9月9日木曜日

プロコン2010-1その4

今回は、ログイン画面。
こんな感じの画面です。


先にコードを見て、作る過程をコードの後に記します。~/Login.aspxの中身はこんな感じ。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="Library.Login"
MasterPageFile="~/Users.Master" StylesheetTheme="Standard" %>

<asp:Content runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
<asp:Login ID="Login1" runat="server">
</asp:Login>
※ログインOKでも、権限の無いページには行けません。その場合本画面に戻されます。
</asp:Content>



コードビハインド(~/Login.aspx.cs)はこんな感じ。

using System;

namespace Library
{
public partial class Login : LibPage
{
protected void Page_Load(object sender, EventArgs e)
{

}
}
}



次に作り方。~/Login2.aspxで作ってみます。
以下の画面のように、ソリューションエクスプローラから、Webアプリケーションのルートのアイコンを右クリックし、追加、新しい項目を選びます。




んで、Login2.aspxのソースをみるとこんな感じ。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login2.aspx.cs" Inherits="Library.Login2" %>

<!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>

</div>
</form>
</body>
</html>


ここから、こんな感じで編集します。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login2.aspx.cs" Inherits="Library.Login2"
MasterPageFile="~/Users.Master" StylesheetTheme="Standard" %>
<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
</asp:Content>


で、デザインタブをクリックするとこんな感じになります。


ここで、ツールボックスから、ログインパーツをドラッグアンドドロップで、ContentsPlaceHolderに配置するとこんな風になります。


引き続きデザイン画面で、※ログインOKでも・・・の文字を入れれば冒頭のaspxが出来上がりです。

2010年9月8日水曜日

プロコン2010-1その3

久しぶりに更新。
今回は、共通画面構成を作ります。

で、先に完成画面から、
こんな感じで作ってみます。



実際はこんな感じで分割してみたいと。


上から順に、LoginStatus区画、UserTitle区画、TopicPath区画、Contents区画とします。
で、Contents区画は入れ子構造でLeftSideContents(左側)、PageContents(中央側)を持つものとします。
早速コードを見てみます。以下のコードが、~/Users.Masterの内容です。


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Users.Master.cs" Inherits="Library.Users" %>

<!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>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div id="LoginStatus">
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
<asp:LoginName ID="LoginName1" runat="server" />
でログイン中。
</LoggedInTemplate>
<AnonymousTemplate>
未ログインです。
</AnonymousTemplate>
</asp:LoginView>
<asp:LoginStatus ID="LoginStatus1" runat="server" />
</div>
<div id="UserTitle">
書籍貸し出し管理システム
</div>
<div id="TopicPath">
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>
</div>
<div id="Contents">
<div id="LeftSideContents">
<asp:Menu ID="Menu1" runat="server">
<Items>
<asp:MenuItem NavigateUrl="~/User/SearchBook.aspx" Text="検索" Value="書籍の検索"></asp:MenuItem>
<asp:MenuItem NavigateUrl="~/User/RentalBook.aspx" Text="貸出" Value="書籍の貸し出しと返却">
</asp:MenuItem>
<asp:MenuItem Text="書籍" Value="書籍のメンテナンス">
<asp:MenuItem NavigateUrl="~/User/AddBook.aspx" Text="追加" Value="書籍の追加"></asp:MenuItem>
<asp:MenuItem NavigateUrl="~/User/ModifyBook.aspx" Text="変更" Value="書籍の変更"></asp:MenuItem>
</asp:MenuItem>
</Items>
</asp:Menu>
</div>
<div id="PageContents">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</div>
</form>
</body>
</html>




スタイルシートの指定は、、各ページのページディレクティブに書いています。まずは、コンテンツページのデザインから。
例えば、~/Default.aspxはこんな感じ。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Library._Default"
MasterPageFile="~/Users.Master" StylesheetTheme="Standard" EnableTheming="True" %>

<asp:Content runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
<p>
・左のメニューより利用したい機能を選択してください。</p>
<p>
・なお、このシステムを利用するためには権限が必要です。</p>
<p>
・ログインしても、権限のないページには移動できません。</p>
<p>
・ログインがOKでもユーザに権限が無い場合ログイン画面に戻されます。</p>
<p>
・書籍関係の操作には、Usersロール,ユーザ関係の操作にはAdminsロールが必要です。</p>
<p>
・ログイン中かの確認には、画面右上をご覧ください。</p>
<p>
 </p>
</asp:Content>


こんな感じで各ページが、右下のPageContentsしか触らないでよくなると、ページの統一感が出せそうだし、効率的に開発できそうな錯覚を感じることができそうですよね。

で、上のDefault.aspxの1行目Pageディテクティブで、StylesheetTheme="Standard"としていますね、
そこで、テーマStandardが選ばれているので、次にこのテーマのスタイルシートを見てみます。
~/App_Themes/Standard/Stylesheet1.cssにおいてあるファイル。

body
{
}
div#LoginStatus
{
height: 20px;
text-align: right;
position: relative;
}
div#UserTitle
{
height: 40px;
background-color: #00FF00;
border: solid medium Black;
font-size: x-large;
position: relative;
}
div#AdminTitle
{
height: 40px;
background-color: #FF0000;
border: solid medium Black;
font-size: x-large;
position: relative;
}
div#TopicPath
{
height: 20px;
position: relative;
border-bottom: solid thin Black;
}
div#Contents
{
}
div#LeftSideContents
{
width: 100px;
height:400px;
float: left;
position: relative;
}
div#PageContents
{
position: relative;
}


この3ファイルで、最初の画面が作られています。
こんな感じでマスターページを作ると、システム全体の構成の統一感を保てます。
、、、UserControlでもできるんじゃないかと突っ込まれることはあります。
もちろんUserControlでもマスターページと同様、入れ子構造で大きなパーツを
構成することができます。でも、UserControlは融通が利く分、ちょっと困難な場面があります。
例えば、、、なんだろう、ひょっとして無いのかな。(まじめに話すと、話が入り組んで来そうなので解説は止めときます。)

で、上のスタイルシートにある、AdminTitleとは何か?というと、以下の~/Admins.Masterというマスターページで使っています。

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Admins.Master.cs" Inherits="Library.Admins" %>

<!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>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div id="LoginStatus">
<asp:LoginView ID="LoginView1" runat="server">
<LoggedInTemplate>
<asp:LoginName ID="LoginName1" runat="server" />
でログイン中。
</LoggedInTemplate>
<AnonymousTemplate>
未ログインです。
</AnonymousTemplate>
</asp:LoginView>
<asp:LoginStatus ID="LoginStatus1" runat="server" />
</div>
<div id="AdminTitle">
書籍貸し出し管理システム(管理者がログイン)
</div>
<div id="TopicPath">
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>
</div>
<div id="Contents">
<div id="LeftSideContents">
<asp:Menu ID="Menu1" runat="server">
<Items>
<asp:MenuItem NavigateUrl="~/User/SearchBook.aspx" Text="検索" Value="書籍の検索"></asp:MenuItem>
<asp:MenuItem NavigateUrl="~/User/RentalBook.aspx" Text="貸出" Value="書籍の貸し出しと返却">
</asp:MenuItem>
<asp:MenuItem Text="書籍" Value="書籍のメンテナンス">
<asp:MenuItem NavigateUrl="~/User/AddBook.aspx" Text="追加" Value="書籍の追加"></asp:MenuItem>
<asp:MenuItem NavigateUrl="~/User/ModifyBook.aspx" Text="変更" Value="書籍の変更"></asp:MenuItem>
</asp:MenuItem>
<asp:MenuItem Text="ユーザ" Value="ユーザのメンテナンス">
<asp:MenuItem NavigateUrl="~/Admin/AddUser.aspx" Text="追加" Value="ユーザの追加"></asp:MenuItem>
<asp:MenuItem NavigateUrl="~/Admin/ModifyUser.aspx" Text="変更" Value="ユーザの変更"></asp:MenuItem>
<asp:MenuItem NavigateUrl="~/Admin/DeleteUser.aspx" Text="削除" Value="ユーザの削除"></asp:MenuItem>
</asp:MenuItem>
</Items>
</asp:Menu>
</div>
<div id="PageContents">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</div>
</form>
</body>
</html>



Users.masterとの違いは、タイトルの背景色、メニューアイテムの個数ぐらいです。
で、Admins.masterとUsers.masterはどんなページで切り替えるか?というと、ページ毎ではなく、ログインユーザ毎に切り替えようにします。
具体的には、Adminロールを持つユーザでログインしているときのみAdmins.masterを、それ以外はUsers.masterを使うようにします。
なので、プログラム構造に少し工夫を。。
Pageディテクティブに書いてあるマスターページの指定変更は、PagePreInitイベントの処理で切り替えることができます。ここで、ロールプロバイダを参照して切り替えることにします。
具体的には、以下のサブページクラスを作ります。(ソースコード配置は、~/App_Code/LibPage.csです。)

using System;

namespace Library
{
public class LibPage:System.Web.UI.Page
{
protected void Page_PreInit(object sender, EventArgs e)
{
if (User.IsInRole("Admins"))
{
Page.MasterPageFile = "~/Admins.Master";
}
}
}
}


で、全てのページのコードビハインド(~.aspx.cs)で、System.Web.UI.Pageから派生しているところを、すべてLibPageから派生させます。例えば、~/Default.aspx.csはこんな感じ

using System;

namespace Library
{
public partial class _Default : LibPage
{
protected void Page_Load(object sender, EventArgs e)
{

}
}
}




今回の記事の最後に、パンくずリストを構成するための~/Web.sitemapを付けておきます。

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/Default.aspx" title="書籍貸出システム">
<siteMapNode url="~/Login.aspx" title="ログイン画面"/>
<siteMapNode url="" title="書籍管理">
<siteMapNode url="~/User/SearchBook.aspx" title="検索画面"/>
<siteMapNode url="~/User/AddBook.aspx" title="追加画面"/>
<siteMapNode url="~/User/ModifyBook.aspx" title="変更画面"/>
<siteMapNode url="~/User/DeleteBook.aspx" title="削除画面" />
</siteMapNode>
<siteMapNode url="" title="書籍貸し出し・返却">
<siteMapNode url="~/User/RentalBook.aspx" title="貸出画面"/>
<siteMapNode url="~/User/ReturnBook.aspx" title="返却画面"/>
</siteMapNode>
<siteMapNode url="" title="ユーザ管理">
<siteMapNode url="~/Admin/AddUser.aspx" title="追加画面"/>
<siteMapNode url="~/Admin/ModifyUser.aspx" title="変更画面"/>
<siteMapNode url="~/Admin/DeleteUser.aspx" title="削除画面"/>
</siteMapNode>
</siteMapNode>
</siteMap>