2010年9月12日日曜日

プロコン2010-1その8

ユーザの削除画面。
ユーザを削除すると、そのユーザに紐づく書籍を他の人に移管するかシステムから削除するようにするため、ちょっと考慮が必要でした。


で、DACは、前回書いたUserInfo.csなので今回は省略して、
UI(~/Admin/DeleteUser.aspx)から

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

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
<br />
削除対象のユーザを選択してください。<asp:RadioButtonList ID="RadioButtonList1" runat="server" DataSourceID="ObjectDataSource1"
DataTextField="UserName" DataValueField="UserName">
</asp:RadioButtonList>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetAllData"
TypeName="Library.DAC.UserInfo"></asp:ObjectDataSource>
<br />
書籍の引継ぎユーザを指定してください。<asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="ObjectDataSource1"
DataTextField="UserName" DataValueField="UserName" AppendDataBoundItems="True">
<asp:ListItem Value="">引き継ぎ無し</asp:ListItem>
</asp:DropDownList>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
</asp:Content>



次にコードビハインド

using System;
using System.Web.Security;
using System.Web.UI;
using Library.DAC;

namespace Library.Admin
{
public partial class DeleteUser : LibPage
{
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = "";
}

protected void Button1_Click(object sender, EventArgs e)
{
Membership.DeleteUser(RadioButtonList1.SelectedValue);
UserInfo.InheritBookOwner(RadioButtonList1.SelectedValue, DropDownList1.SelectedValue);
UserInfo.DeleteUser(RadioButtonList1.SelectedValue);
Page.DataBind();
Label1.Text = "削除が完了しました";
}
}
}



本当は、ACIDの一貫性を保つために、UserInfo.InheritBookOwner()と、UserInfo.DeleteUser()を連続したよびだしし、トランザクションスコープをくくったUserInfoのメソッドで用意するべきだったけど、まいいかぁ。

プロコン2010-1その7

今回は、ユーザの変更の画面。
DataSetのコードは省略し、DACと、UIのコードを。
まずは、DAC(~/DAC/UserInfo.cs)から。

using System.Collections.Generic;
using System.Web.Security;
using Library.DataSet;
using Library.DataSet.LibraryDataSetTableAdapters;
namespace Library.DAC
{
public static class UserInfo
{
public static LibraryDataSet.UsersDataTable GetAllData()
{
LibraryDataSet.UsersDataTable dt = new LibraryDataSet.UsersDataTable();
using (UsersTableAdapter ta = new UsersTableAdapter())
{
dt = ta.GetData();
foreach (MembershipUser msu in Membership.GetAllUsers())
{
if ((dt.Select("UserName='" + msu.UserName + "'")).Length == 0)
{
dt.AddUsersRow(msu.UserName, string.Empty, false, false);
ta.Update(dt);
}
}
dt = ta.GetData();
foreach (LibraryDataSet.UsersRow ur in dt)
{
ur.AdminRoleFlag = Roles.IsUserInRole(ur.UserName, "Admins");
ur.UserRoleFlag = Roles.IsUserInRole(ur.UserName, "Users");
}
return dt;
}
}
public static void Update(string userName, string information, bool adminRoleFlag, bool userRoleFlag)
{
using (UsersTableAdapter ta = new UsersTableAdapter())
{
ta.Update(information, userName);
}
if (adminRoleFlag && !Roles.IsUserInRole(userName, "Admins"))
{
Roles.AddUserToRole(userName, "Admins");
}
else if (!adminRoleFlag && Roles.IsUserInRole(userName, "Admins"))
{
Roles.RemoveUserFromRole(userName, "Admins");
}
if (userRoleFlag && !Roles.IsUserInRole(userName, "Users"))
{
Roles.AddUserToRole(userName, "Users");
}
else if (!userRoleFlag && Roles.IsUserInRole(userName, "Users"))
{
Roles.RemoveUserFromRole(userName, "Users");
}
}
public static void InheritBookOwner(string oldOwner, string newOwner)
{
using (BookOwnerTableAdapter ta = new BookOwnerTableAdapter())
{
if (string.IsNullOrEmpty(newOwner))
{
ta.DeleteByUserName(oldOwner);
}
else
{
ta.InheritBookOwner(newOwner, oldOwner);
}
}
}
public static void DeleteUser(string userName)
{
using (UsersTableAdapter ta = new UsersTableAdapter())
{
ta.Delete(userName);
}
}
public static LibraryDataSet.UsersDataTable GetDataFirstByGivenUser(string GivenUserName)
{
using(UsersTableAdapter ta=new UsersTableAdapter())
{
return ta.GetDataFirstByGivenUserName(GivenUserName);
}
}
}
}



ユーザ変更のコード部分(~/Admin/ModifyUser.aspx.cs)


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

<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
<asp:GridView ID="GridView1" runat="server" DataKeyNames="UserName" AutoGenerateColumns="False"
DataSourceID="ObjectDataSource1" OnRowUpdated="GridView1_RowUpdated" AllowPaging="True"
AllowSorting="True" CellPadding="4" ForeColor="#333333" GridLines="None">
<RowStyle BackColor="#FFFBD6" ForeColor="#333333" />
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:BoundField DataField="UserName" HeaderText="ユーザ名" ReadOnly="True" SortExpression="UserName" />
<asp:BoundField DataField="Information" HeaderText="ユーザ情報" SortExpression="Information" />
<asp:CheckBoxField DataField="AdminRoleFlag" HeaderText="管理者権限"
SortExpression="AdminRoleFlag" />
<asp:CheckBoxField DataField="UserRoleFlag" HeaderText="ユーザ権限"
SortExpression="UserRoleFlag" />
</Columns>
<FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#FFCC66" Font-Bold="True" ForeColor="Navy" />
<HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetAllData"
TypeName="Library.DAC.UserInfo" UpdateMethod="Update">
<UpdateParameters>
<asp:Parameter Name="userName" Type="String" />
<asp:Parameter Name="information" Type="String" />
<asp:Parameter Name="adminRoleFlag" Type="Boolean" />
<asp:Parameter Name="userRoleFlag" Type="Boolean" />
</UpdateParameters>
</asp:ObjectDataSource>
</asp:Content>



最後に、コードビハインド

using System;
using System.Web.Security;

namespace Library.Admin
{
public partial class ModifyUser : LibPage
{
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = "";
}

protected void GridView1_RowUpdated(object sender, System.Web.UI.WebControls.GridViewUpdatedEventArgs e)
{
Label1.Text = "更新しました";
}
}
}

プロコン2010-1その6

asp.netの開発では、たとえ1人で作る個人的なプログラムであっても
プログラム開発効率を上げるためにある程度の決め事をしておきます。
(まあ設計というほどのものではないですが。)
たとえばこんな感じ。

A)画面デザインと共有パーツやロジックを複数のアセンブリ(DLLやexe)に分けず、1本のアセンブリで作成する。
 →Visual Studioのプロジェクトは1本。
B)プログラムソースコード群を以下のカテゴリに分ける。
・画面デザイン((UI)1画面内で判断できる画面固有のロジックも含む。フォルダを切らない。)
・ビジネスロジック(DACとBCは分けない。全てDACフォルダに格納する。ファイルアクセスがあればここで行う。)
・データベースアクセス(DBに対するSQL発行。全てDataSetフォルダで行う。)
上記のカテゴリは、隣接するカテゴリのみアクセスを許す。(画面デザインからストレージアクセスへの直呼び出しは不可)
上記のカテゴリのプログラムは、自分より上のカテゴリの構造を前提としてはいけない。(このビジネスロジックを呼び出すためには、呼び出し元がこうなっていなければならないとかはNG)
上記のカテゴリ間の複雑な構造のデータインタフェース(BEC)は、データセットやデータテーブルを用いる。
※データセットやテーブルっていうのは、.netのなかの、ado.netの機能で、データベースやテーブルをメモリ上で実装したものです。
ま、たんに決め事です。

で、データセット作成から、ソリューションエクスプローラで、"DataSet"というフォルダを作って、このフォルダを右クリック、追加でデータセットを作成する。
今回は、Library.xsdという名前で作成しています。
Library.xsdをダブルクリックし、編集画面で、右クリック→追加→DataTableを選択。DataTable1と表示されている部分を左クリックしRolesと変更。
その後、列の追加を行いRoleNameという列を追加。追加した列をクリックし、プロパティ(表示されていなければF4で表示させる。)で、文字列であることを確認する。
これでLibrary.DataSet.LibraryDataSetというクラスの内部クラスで、RolesDataTableクラスが作成される。
なお、Library.DataSet.LibraryDataSetの最初のLibraryは、現プロジェクト名で、次のDataSetは、プロジェクトに作ったフォルダの名前です。
で、RoleDataTableについては、TableAdapterを作っていません。
RoleDataTableへのデータ投入は、ビジネスロジックで行います。
~/DAC/RoleInfo.csの作りは、こんな感じ。これが前回の記事で指定したRoleInfoの内容です。


using System.Web.Security;
using Library.DataSet;

namespace Library.DAC
{
public static class RoleInfo
{
public static LibraryDataSet.RolesDataTable GetAllRole()
{
LibraryDataSet.RolesDataTable dt = new LibraryDataSet.RolesDataTable();
foreach (string s in Roles.GetAllRoles())
{
dt.AddRolesRow(s);
}
return dt;
}
}
}

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>

2010年7月20日火曜日

プロコン2010-1その2

一日で終わると思っていたら、3連休まるまる潰してしまいました。素直にLinqを使っておけば、インピーダンスミスマッチ解消に時間をかけなくても済んだのに、ちょっと選択を誤ったみたい。

インピーダンスミスマッチっていうのは、MSさんがLINQなりEntityFrameworkを売るためにひたすら呟いていたものです。要は、SELECT文は、抽出列や検索条件もろもろは即席で作れるのに、プログラムで開発したとたんに、それぞれ(入れ物や検索条件)で個別に定義が必要になり、しかもDataBaseアクセスクラスとBusinessLogicクラスでそれぞれおんなじようなものを作らなければならないので、開発時、この部分に時間をとられ、開発工数や注視が必要な範囲が大きくなってしまう、というのが論点(だとおもってます。)
これを、DBで扱う構造(Table構造)とプログラムで扱う構造(Select文で指定する構造)が異なることが問題ととらえて、電子工学になぞり、インピーダンスミスマッチが発生して伝達効率が悪くなるとたとえているものですが、基本的には昔から言われているインターフェース不一致の問題と同じです。


ま、今回は、完成した(んだけど、ドキュメントが少ない)場合に即席で設計資料を作るリバースエンジニアリングについてです。データベースとクラスについて。今回扱う2つの図は、修正すると、コードに反映されるもので、本来は設計・開発時に使うものなんだけど、完成した後でも一応使えるよってことで。

そんな即席で作った設計書など誰が見たがるか?という問題はさておき、資料を作ってみましょう。
一応、完成時のVisualStudioの画面はこんな感じ。


ここから、SQL Server2005Expressの機能を使って、DB構造についての図を作ってみます。
上の画面左のデータベースダイアグラムの部分で、マウス操作でアイテムを作り、それをダブルクリックして、開いた画面に対して、テーブル(BookOwner~VoiceForTitleまで)をドラッグ&ドロップします。
その後、配置をいろいろ配置した結果がこんな感じ。


これを、そのまま印刷するなり、右クリックしてクリップボードにコピーした後、Word,Excel,Paint等に貼り付けて編集したりして、DBの概要資料を作ります。


続いて、クラス図。

Diagramフォルダを作って、その中に、クラスダイアグラムを追加します。(ClassDiagram.cdとDataSetDiagram.cdの2ファイル。)
ファイルを追加した直後の画面はこんな感じ。


まずは、ClassDiagram.cdの方から。ClassDiagram.cdを開いた状態から、ソリューションエクスプローラの「Admin」フォルダ、「App_Code」フォルダ、「DAC」フォルダ、「User」フォルダ、「Admins.Master,Default.aspx,Login.aspx,Users.Master」ファイルをドラッグ&ドロップします。

それで、いろいろこねくり回した結果が以下。これは、クリップボードにコピーできるのは画像だけで、Word,Excel,Paint等に貼り付けてテキストを変えるなど図の編集みたいなことは出来なさそう。


続いて、DataSetDiagram.cdの方、こちらは、DataSetフォルダをドラッグ&ドロップ。

テーブルアダプタについては、メソッドが重要なので、フィールドとプロパティは隠してメソッドだけ表示ただ、メソッドは引数、戻り値など完全な表示にしておきます。
逆にデータセットの方は、内部クラスのデータテーブルのプロパティが重要なのでそれだけ表示させます。
で、こんな感じです。



上に書いたとおり、これらの図は、VisualStudio(やSQL Server Management Studio)で触ると、コードやDBに反映されることを知っておいた方がいいですね。(プログラムやデータベースを意図せずに壊さないように。)

2010年7月17日土曜日

プロコン2010-1その1

最近コードを書いていないので、プロコン題材にコードを書いてみようかと。
課題1から、今回のおだいは、Webアプリでユーザ管理と本の貸し出し管理とのこと。

HHHでもらったVisual Studio 2008 Standard Edition相当を使います。(asp.net 3.5ってことね。)

簡単なところから、ユーザの管理とログイン部分だけを先に作ってみます。下の画面の1~4までの部分。

【設計方針】
まずは以下の点ぐらい。
・asp.net標準機能を多少無理してでも極力使う。
・画面間引継ぎ項目は基本クエリストリングで行う。

【画面】
CSV形式で、画面番号,日本語画面名,物理aspx名,格納フォルダ,前提画面番号,引継ぎ内容,画面機能説明
1,ログイン画面,Login.aspx,~/,,,ログインする(ASP.NETではデフォルトで未権限のページに遷移した場合、アプリケーションルートのlogin.aspxにリダイレクトするので名前はこのままにする。)
2,ユーザ追加画面,AddUser.aspx,~/Admin/,,,ユーザーを登録する
3,ユーザ変更画面,ModifyUser.aspx,~/Admin/,,,ユーザーを変更する
4,ユーザ削除画面,DeleteUser.aspx,~/Admin/,,,ユーザーを削除する
5,検索画面,SearchBook.aspx,~/User/,,,書籍を検索する
6,追加画面,AddBook.aspx,~/User/,,,書籍を登録する
7,変更画面,ModifyBook.aspx,~/User/,,,書籍を変更する
8,削除画面,DeleteBook.aspx,~/User/,,,書籍を削除する
9,貸出画面,RentalBook.aspx,~/User/,,,書籍を借りる
10,返却画面,ReturnBook.aspx,~/User/,,,書籍を返す

あれ、前提画面や引継ぎ内容がなくなっちゃった。

【ユーザロール】
 システムにはAdminsとUsersのロールがあり、ユーザにロールが複数付けれるものとする。

【セキュリティ】
未ログインユーザは、~/参照可能
Adminsのロールを持つユーザのみ、~/Admin/参照可能
Usersのロールを持つユーザのみ、~/User/参照可能

【ユーザ,ロール管理のDB構造】
 asp.net標準の(SQL Server Expressを使った)ユーザ管理を利用

【本の管理のDB構造】
 あとで

【本の貸し出しの内部クラス設計】
 あとで



で、Visual Studioを起動。
新規プロジェクトで以下を設定。


セキュリティ設定







ここで、ローカルからインターネットに変更して


終了ボタンを押すと、


画面は戻って、Web.configのauthentication modeがWindowsからFormsに変わる。

続いて、「ロールの有効化」を選ぶと、ロールの管理が選べれるようになって、Web.configのroleManager enabled="true"が追加される。

ユーザの追加でこんな感じでユーザを追加する。


manoとmano2を追加する。

セキュリティタブで戻って、ロールの作成または管理でAdminsとUsersを追加する。
ユーザの管理でmanoにAdminsとUsersロールの両方を、mano2にUsersロールだけを与える。
以下の感じになる。


この設定は、MembershipプロバイダとRoleプロバイダを経由した設定なので、Web.configに変更はなし。

以下のようにAdminとUserフォルダを作成し、



asp.netの構成の方で、アクセス規則のアクセス規則の作成を選択
こんな感じで、AdminフォルダにAdminsロールの許可と全てのユーザの拒否を、UserフォルダにUsersロールの許可と全てのユーザの拒否を付与。


アクセス規則の管理で、順番がロールユーザを許可→すべてのユーザを拒否になっていることを確認する。



こうすると、~/User/や~/Admin/フォルダ配下にWeb.configが作成され以下のようになる。(VSのプロジェクト外なので全てのファイルを表示ボタンを押さないと表示されないのですが。)


で、セキュリティ設定は終わり。
もっともWeb.configの変化を見るために1ステップずつやったわけですが、
「手順に従ってセキュリティを構成するには、セキュリティ設定ウィザードを使用してください。 」リンクをクリックすると上のことがもっと簡単に出来たんだけどね。
今回はここまで

2010年5月22日土曜日

英字ドキュメント読み支援ツール その6

昨日は、疲れてて更新が出来なかったので、本日になってしまいました。
ちょっと飛ばしすぎたかも。

で、今回は、英字ドキュメント読み支援ツールの最終回、アプリケーションの配布です。
(ExpressEditionでは使えないかも、ですが。)

クリックワンスだけです。
インストーラーの方法はいろいろ調べたんだけどやりたいことが出来なかった。
 やりたいこと:代替文字列|DataDirectory|をユーザのアプリケーションデータフォルダに変更すること
 理由:プログラムと同じフォルダにmdfファイルを格納するとUVCの機能により読み込み専用の扱いになってしまう。なので、mdfのインストール先を変更したい。プログラムで使っているSQL Serverのファイル接続に|DataDirectory|を利用しているので、インストール時にこの差し先を変えたいが、、、

クリックワンスについてのみ(覚書としてインストーラの方も記述しておきます。)
こちらは、殆ど設定が要らなくて、ソリューションエクスプローラーから[WindowsFormsApplication44]を右クリック→プロパティを選ぶと出てくる画面の一番下のタブの[発行]が全てです。


最短の手順でおこなうなら、発行場所のフォルダ場所をc:\temp\publishに変えて、[今すぐ発行]ボタンを押すと、c:\temp\publish\setup.exeができる。これを実行すると、インストールできて、スタートメニューに登録される。アンインストールは、プログラムの追加と削除から実行。

少しだけ、インストール設定変更の説明。
[アプリケーションファイル]ボタンでは、インストールするファイルの一覧が出てきます。



(もし配布するなら、認証が面倒なmdfファイルを使うよりモバイルの方の、sdfファイルを使ったほうが良かったかもですね。)

[更新]ボタンでは、発行したものが変わったときに、アプリケーション起動のタイミングで自動更新を行うかの設定。デフォルトでは更新はチェックしない、です。(更新する場合はこのボタンを使います。)
[オプション]ボタンでは、インストール情報のいろんな設定ができるけどすることは無いでしょう。(Webサーバ以外に配布するようにしているんだけど、発行のたびに、ブラウザを立ち上げてページが存在しないという表示されるのがうっとうしいというのなら、オプション設定でブラウザを立ち上げない設定ができるとおもいます。)


以上がクリックワンスです。




続いて、覚書で、インストーラの作成。※これではmdfへの書き込みのところで落ちちゃいますが。。。


こちらはちょっと面倒。
まず、セットアッププロジェクトを追加します。
VisualStudioのメインメニューより、[ファイル]→[新しいプロジェクト]で、表示される「新しいプロジェクト」ダイアログで、[その他のプロジェクト]から[セットアップと配置]のグループから、[セットアッププロジェクト]を選びます。下の方で、[新しいソリューションを作成する]から[ソリューションに追加]に変更して、OKボタン。


セットアッププロジェクトのプロパティウィンドウでは、セットアップの情報を記載できます。
(サブフォルダ名は何にするか?とかプログラムの追加と削除画面に出すメッセージとかアイコンとか、、、)


また、ソリューションエクスプローラで、セットアッププロジェクトを選んだ状態にすると、ソリューションエクスプローラのタイトルバーの直下のツールボタンで、以下のボタンが表示されます。(ボタンの上にマウスカーソルを移動するとボタン名がバルーン表示されます。)
・ファイルシステムエディタ、
・レジストリエディタ、
・ファイルの種類エディタ、
・ユーザインタフェースエディタ
・カスタム動作エディタ
・起動条件エディタ

上のボタンを押すと、対応したエディタが表示されるので、変更が必要な場合はエディタで編集。
今回の場合だと、使うのはファイルシステムエディタだけです。

[アプリケーションフォルダ]を右クリック→[追加]→[プロジェクト出力]を選んで、[プライマリ出力]を追加します。
追加した[プライマリ出力]を右クリック、[...ショートカットを作成]を選択。作成されたショートカットを、ユーザのプログラムメニューにドラッグ&ドロップします。
[アプリケーションフォルダ]を右クリック→[追加]→[ファイル]を選んで、Icon1.ico、Database1.mdf,Database1.ldfのファイルを追加します。




こんな感じで、Setupのビルドをするとsetup.exeと~.msiが出来る。手動でインストールする場合は、どちらをクリックしても同じ。

※上で言ったとおり、セットアッププロジェクトで作成したほうは、ちゃんと動きません。

なんか、こんな感じで終わるのも変な感じですが、一応終了。

次回からは、asp.netでのwebページの練習として、掲示版プログラムを題材にして、
キャッシュ(キャッシュオブジェクトではなく、ページ出力キャッシュの方)
をメインでやってみます。

余裕があれば、これのWindowsフォームクライアントでSQL Server2005のクエリ通知を
利用して、プッシュ型のもの、(更新があるか問い合わせるものではなく。。)を作ってみたいと思います。
今度は、かなりゆったりのペースで。。。


以上です。

2010年5月20日木曜日

英字ドキュメント読み支援ツール その5

今回は、通知領域でのアイコンと右クリックメニューと、大量データ保存に耐えられるストレージを作ります。
まずは、前回の終了時のところから


通知領域でのアイコン表示をさせます。Form1のデザイナ画面で、
ツールボックスの[コモンコントロール]からNotifyIconをダブルクリック。
ソリューションエクスプローラのWindowsFormsApplication44で右クリック→追加→新しい項目で、アイコンファイル"Icon.ico"を追加。
notifyIcon1のプロパティで、Iconをクリック...をクリックして、ファイル選択で、先ほど追加したIcon.icoを指定。
こんな感じ



続いて右クリックで表示するメニューを追加します。
ツールボックスの[メニューとツールバー]より、COntextMenuStripをダブルクリック
[Windows表示]のメニューを追加、追加メニューの(name)プロパティをShowWindowToolStripMenuItemとする。


notifyIcon1をクリックしプロパティContextMenuStripにさっき追加したcontextMenuStrip1を指定する。

実行すると、左下通知領域にアイコンがでて、これを右クリックするとメニューが表示されることを確認。




続いて、ストレージの件。
まずは、プロジェクトアイテムを追加。
【ウィンドウ追加】
ソリューションエクスプローラで、WindowsFormsApplication44を右クリック→追加→Windowsフォームをクリックし、そのまま追加ボタンを押して、Forms3.csを追加。
【ストレージ追加】
ソリューションエクスプローラで、WindowsFormsApplication44を右クリック→追加→新しい項目をクリックし、サービスベースのデータベースをクリック、Database1.mdfと表示されている状態で、追加ボタンをクリック。直後に表示されるデータソース構成ウィザードではすぐにキャンセルボタンを押す。
続いて、サーバエクスプローラを表示。今の状態はこんな感じ。



続いて、格納領域の構造を作成。
サーバエクスプローラで、「データ接続」→「Database1.mdf」の左の+ボタンをクリックして現れるフォルダ一覧より[テーブル]を右クリックし、新しいテーブルの追加をクリック。以下の4列を追加
1列目:列名:Id,データ型:int,NULLを許容しない、IDENTITYの指定:はい、右クリック→主キーの設定
2列目:列名:OriginalText,データ型:varchar(MAX),NULLを許容しない
3列目:列名:TranslateText,データ型:nvarchar(MAX),NULLを許容する
4列目;列名:PageNum,データ型:int,NULLを許容する
5列目:列名:Paragraph,データ型:int,NULLを許容する。
ここまで打ち込んだら[Ctrl]+Sで、テーブル名をTranslationDataとする。


次に、このテーブル(ファイル上のDB構造)に対応するデータテーブル(インメモリのDB構造であるデータセット上のテーブル)を作成する。
データソースタブをクリックする。


続いて、ソリューションエクスプローラからデータソースにしたいもの(Database1.mdf)をクリック。


データソースウィンドウに現れた「新しいデータソースの追加」リンクをクリック。
表示されるダイアログ「データソース構成ウィザード」で、データベース選択のまま[次へ]→データ接続をDatabase1.mdfのまま[次へ]→接続文字列はそのままにして[次へ]→データベースオブジェクトの選択で、テーブルのTranslationDataにチェックを入れて[完了]
これで、データソースにDatabase1DataSetが表示され、ソリューションエクスプローラに、Database1DataSet.xsdが追加される。

続いてDBデータ編集のWindowsフォーム用に先ほど追加したForm3.csを利用します。
デザイナでForm3.csを編集し、ウィンドウサイズを横に伸ばす。
以下の画面の状態から、



データソースの[TranslationData]をドラッグして、Form3にドロップして、こんな感じにします。


続いて、translationDataDataGridViewの大きさ、配置を変更して、
その下部に2つのTextBoxを配置し大きさを調整。(ともにmultilineをtrueに変更)
左のTextBoxのTabIndexの値+1を右のTextBoxのTabIndexの値に設定。


この後、
データソースのOriginalTextをドラッグして、左側のTextBoxにドロップ。
データソースのTranslateTextをドラッグして、右側のTextBoxにドロップ。
それぞれのTextBoxの(DataBinding)にtranslationDataBindingSourceでそれぞれの列がアサインされていることを確認。

最後にForm3の表示をさせる部分を作りこむ。
Form1のデザインに戻って、contextMenuStip1を選択、メニューの[Windows表示]をダブルクリックして、以下のコードを記述。
private void ShowWindowToolStripMenuItem_Click(object sender, EventArgs e)
{
new Form3().Show();
}

で、実行。
NotifyIconを右クリック、メニューよりWindows表示をクリックすると現れるデータ登録画面で、
新規データ登録する場合は、+ボタン。既存行を変更する場合は、その行をクリック。
で、グリッドビューでは入れにくい改行はテキストボックスを使って編集。
行削除は赤色のバッテンマーク。
これらは、すべてオンメモリ上の操作なので、DB(ファイル)に書き出すには、フロッピーディスクマークをクリック。(これで、次開いたときにも、保存したデータが表示される。)


マイクロソフトのWebサービスの翻訳APIを使ってみても面白そうだけど、これは70-529の範囲だと思うので今回はパス。
で、次回は、最後に、今回のもののインストーラを作ってみます。