タダで作る .net アプリ2(アンチ Java 企画)

サイト全体の目次

稿


稿

目的

(untitled)

フリーでそろう環境だけで ASP.NET アプリケーションを作成するためのチュートリアルです。ともかくフリーで作れるということに主眼を置いているため、プログラミングや言語についての解説は詳しく行っていません。感覚的に「ふ~ん、そんなことが出来るんだ。面白そうじゃん。」と思っていただくことを目的としています。もしこのページを読んで興味をもたれましたら、付属のドキュメントや市販の参考書などを手にとって見てください。

また、ASP.NET アプリケーションを Linux 上で動作させる方法についても解説します。

なお、このページでは言語として C# を使用しております。

IIS の用意

(untitled)

まず、IIS で ASP.NET を実行するための設定を説明します。

最初に「プログラムの追加と削除」で「Windows コンポーネントの追加と削除」から IIS をインストールしてください。インストール後、とりあえず IIS が動いていることを確認するにはブラウザで「http://localhost/」と入力してください。正常に動作していればデフォルトのトップページが表示されます。

インストールが完了すると、C ドライブの下に「Inetput」というディレクトリが作成されす。そしてこの中の「wwwroot」というディレクトリが HTTP サーバーのルートとなります。この「wwwroot」の下に「sample」というディレクトリを作ってください。今回、そこで作業することにします。

次に「sample」ディレクトリ下で Web アプリケーションが動作するように IIS を設定します。「コントロール パネル」→「管理ツール」→「インターネット インフォメーション サービス」と選択し、IIS の設定画面を開いてください。設定画面が開いたら、コンピュータのアイコンを展開し、さらに「Web サイト」→「規定の Web サイト」と展開して「sample」ディレクトリのプロパティを開きます。.ここで、「アプリケーションの設定」にある「作成」ボタンを押して、プロパティを閉じます。

最後に、IIS で ASP.NET が使えるように .net framework SDK のツールで登録を行います。「C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322」のようなディレクトリ下にある「Aspnet_regiis.exe」を使って

>Aspnet_regiis.exe -i
				

のようにしてください。IIS に関する準備は以上で完了です。

mono の用意

(untitled)

IIS でとりあえず試してみたいという人はこのセクションは飛ばしてください。

前回、Linux で使用できる .net 環境として DotGNU を紹介しましたが、ASP.NET をやるには mono を使用することになります。DotGNU をインストールしている場合はアンインストールしておいてください。また、Apache と併用することを想定するので、Apache をインストールしておいてください。

私のテスト環境は RedHat9.0 です。まず、.. から適切な RPM パッケージを拾ってきます。私は、必要なパッケージが一通りそろっている mono-all.zip をダウンロードし、全てインストールしました。ただし、依存関係により libpixman だけは Fedora Core 2.0 用のものを使用します。また、必要なパッケージとして.を要求されるかもしれませんので、その場合はインストールしておいてください。さらに libgal2 は. のアップデートを要求するかもしれません。その場合は.と一緒にアップデートしてください。

mono 公式サイト http://www.mono-project.com/
ダウンロードページ http://www.mono-project.com/downloads/
libgal2 http://rpmfind.net/linux/RPM/fedora/2/i386/libgal2-1.99.11-1.i386.html
libgnomecanvas http://rpmfind.net/linux/RPM/redhat/taroon/ws/i386/libgnomecanvas-2.2.0.2-2.i386.html
libgnomecanvas-devel http://rpmfind.net/linux/RPM/redhat/taroon/ws/i386/libgnomecanvas-devel-2.2.0.2-2.i386.html

mono のインストールが完了したら mono の Apache 用モジュールをインストールします。先ほどの.の「Source Code」から「Apache Mono module 1.0 (mod_mono)」をダウンロードしてきます。これは ./configure、make、make install でインストールできます。configure は apache のインストール先ディレクトリを自動検出してくれるため、ライブラリは apache の modules ディレクトリ下にインストールされます。

ダウンロードページ http://www.mono-project.com/downloads/

続いて、Apache の httpd.conf を編集して mono のモジュールを登録します。httpd.conf に

LoadModule mono_module libexec/libmod_mono.so
AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx
				

を追加します。ただし、LoadModule の行は mono のモジュールをインストールする際に自動的編集され、すでに追加されているかもしれません。

追記(04/10/04):今日現在、mono の最新バージョンは 1.0.2 ですが、この場合 mono-all では mod_mono が httpd を要求します。Web サーバーを rpm でなく別途インストールしている場合は mod_mono を除外し、mod_mono だけ別途ソースからインストールするようにします。また、mod_mono のモジュール名が「libmod_mono.so」ではなく「mod_mono.so」になっています。

次に mono 用の設定ファイルを作成します。場所はどこでもかまいませんが、ファイル名は「filename.webapp」のように拡張子を「.webapp」とします。このファイルへ

<web-application>
        <name>sample</name>
        <vpath>/sample</vpath>
        <path>/www/sample/</path>
        <vhost>www.parof.jp</vhost>
        <vport>80</vport>
</web-application>
				

のように記述します。vpath はブラウザでアクセスするときの仮想パスで、path は実際にプログラムがおさめられるディレクトリの絶対パスです。それぞれの項目を環境に合わせて適切な値に設定してください。

最後に mono を起動します。

# mono /usr/bin/mod-mono-server.exe --appconfigdir [appconfigdir] --nonstop &

として起動してください。appconfigdir は .webapp ファイルのあるディレクトリです。さらに
# chmod 666 /tmp/mod_mono_server

として自動生成されたファイルのパーミッションを調整します。このファイルは起動ごとに新規作成されるので、パーミッションの変更も起動ごとに行う必要があります。なお、停止は kill や killall などで行ってください。

これで mono で ASP.NET を利用する準備が整いました。ただ、実際に ASP.NET を使用してみると、変にキャッシュが働いて再コンパイルした結果が反映されないことがあります。動作がおかしいと思ったら mono を再起動してみるとよいでしょう。

仕組み

構成

ASP.NET プログラムは「デザインファイル(HTML)」「コードファイル(ソースファイル)」「アセンブリファイル(DLL)」によって構成されます。ここではデザインファイルを「Main.aspx」、コードファイルを「Main.aspx.cs」、アセンブリファイルを「Main.dll」として話を進めます。

処理の流れ

ブラウザで aspx ファイルを呼び出すと、

1. コードファイル内(実際にはアセンブリファイル内)のメインとなる
   クラスが aspx ファイル全体を表すオブジェクトとして作成される。
2. コードファイル内の Load イベントに対応するイベントハンドラが
   呼び出され、aspx ページの初期化を行う。
3. コントロールなどのイベントによる呼び出しであれば対応するイベント
   ハンドラを呼び出す。
4. HTML に変換され、ブラウザへ出力される。

となります。つまり、aspx ファイルは HTML で書いていますが実は aspx.cs 内のクラスをデザインしていることになるのです。そして、aspx のオブジェクトが作成された後に、ページの読み込みが行われている事をしめす Load イベントに対応したハンドラを呼び出します。次に、もし呼び出しが「ボタンを押した」などのイベントによるものであれば対応するイベントハンドラが呼ばれます。最後に、aspx を表す aspx.cs 内のクラスのオブジェクトが HTML に変換されて出力されます。

構造

このとき、aspx.cs のオブジェクトをいじると出力結果を操作できるという仕組みです。

また、aspx 内のボタンやブロック要素タグは System.Web.UI.WebControls.WebControl の派生クラスや System.Web.UI.HtmlControls.HtmlGenericControl クラスのオブジェクトとして表されます。イメージとしては

Page クラス
↑
aspx.cs 内のクラス
	メンバ:aspx 内のボタンなどのオブジェクト
	メンバ:aspx 内のtable div span などの要素

のようになります。これだと、aspx 内のボタンや要素などのオブジェクトを操作できないように思えますが、これらを aspx.cs から操作できる仕組みが用意されているので問題ありません。

簡単なプログラム

(untitled)

最も簡単なプログラムとして、ボタンを押すと文字が変化するというプログラムを作成してみます。なお、作成するファイルはいずれも UTF-8 で保存するとよいでしょう。これは Windows 上と Linux 上とで文字コードに起因する問題がおきないようにするためです。

aspx ファイル

aspx ファイルは基本的に HTML を記述します。

<%@ Page language="c#" AutoEventWireup="false" Inherits="sample.Main"%>
<html>
<head>
</head>
<body>
	<form id="form" method="post" runat="server">
        <asp:Button id="btn" runat="server" Text="ボタン"/>
		<div id="block" runat="server">
			<div id="label" runat="server"/>
		</div>
	</form>
</body>
</html>

このファイルには、アセンブリファイルとの関連付けを行う Page ディレクティブの記述と、aspx.cs 内のクラスからオブジェクトを扱うための記述を行います。

Page ディレクティブは「<% Page」で始まり、「langueage」で言語を指定し、「Inherits」で aspx.cs 内のクラスを指定しています。「Inherits」での指定は「[aspx.cs 内の名前空間].[aspx.cs 内のメインとなるクラス]」となります。「AutoEventWireup」を true にすると aspx.cs 内で Load イベントのハンドラを登録しなくても自動的に「Page_Init」メソッドと「Page_Load」メソッドが呼ばれるようになりますが、今回は理解を深めるために false としておきます。

要素をオブジェクトとして扱うには2種類の方法があります。一つは .net framework で用意されたクラスのオブジェクトとして扱う方法で、例えば「<asp:Button」タグです。これは System.Web.UI.WebControls.Button であることを表しています。もう一つの方法は普通の HTML を System.Web.UI.HtmlControls.HtmlGenericControl として扱う方法です。これは HTML タグをそのまま使用します。いずれの方法も一意な id 属性を割り振ります。この属性名が aspx.cs 内での変数名になります。また、runat 属性で server と指定する必要があります。ここではボタンオブジェクトとして btn を、ブロック要素のオブジェクトとして block と label を用意しました。

aspx.cs ファイル

次に、aspx に対応した aspx.cs ファイルを作成します。内容は以下のとおりです。

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace sample {
	public class Main : System.Web.UI.Page {
		protected Button btn;
		protected HtmlGenericControl block;
		protected HtmlGenericControl label;

		public Main() {
			this.Load += new EventHandler(Page_Load);
		}
		
		private void Page_Load(object sender, EventArgs e) {
			label.InnerText = "Hello";
			btn.Click += new EventHandler(Clicked_Hello);
		}

		private void  Clicked_Hello(object sender, EventArgs e) {
			label.InnerText += " World";
		}
	}
}

まず、最初の using により ASP.NET で使いそうな一般的な名前空間を用意しておきます。なお、ここにあげた名前空間は Visual Studio .NET のプロジェクトとして作成した際に自動生成されたものを拝借してたものです。

今回、sample という名前空間を作成し、その中に Main というメインになるクラスを作成することにします。この Main は aspx のページを表すクラス Page を継承してます。

メンバ変数として、aspx ファイルで用意したオブジェクトを宣言します。それぞれ対応するクラスの変数として宣言します。変数名は id 属性で指定した値にあわせます。今回 form は使用しないので宣言しません。(block は後ほど使います。)

コンストラクタで、ページが読み込まれたときに発生する Load イベントに対してイベントハンドラ Page_Load を設定しています。(なお Visual Studio .NET のプロジェクトとして作成した場合、コンストラクタの代わりに.のようなコードが生成されます。

#region Web フォーム デザイナで生成されたコード 
override protected void OnInit(EventArgs e)
{
	//
	// CODEGEN: この呼び出しは、ASP.NET Web フォーム デザイナで必要です。
	//
	InitializeComponent();
	base.OnInit(e);
}

/// <summary>
/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
/// コード エディタで変更しないでください。
/// </summary>
private void InitializeComponent()
{    
	this.Load += new System.EventHandler(this.Page_Load);

}
#endregion
	

この Page_Load が事実上の初期化処理になります。ここでは btn オブジェクトのボタンが押されたことを表す Click イベントに対してイベントハンドラ Hello_Click を設定しています。また label オブジェクト(div 要素)のテキストに「Hello」を設定しています。

Clicked_Hello では label のテキストに「 World」を付加して「Hello World」となるようにしています。

コンパイルと実行

aspx ファイルと aspx.cs ファイルが用意できたらコンパイルして実行してみることにしましょう。ただし、コンパイルでは実行可能ファイルではなく DLL 形式のアセンブリファイルを作成します。また、この DLL ファイルは bin ディレクトリの中に格納します。そのため、bin ディレクトリをあらかじめ作成して置いてください。

コンパイルは

>csc /t:library /out:bin\Main.dll Main.aspx.cs

のようにします。「/t:library」で DLL の作成を指示し、「/:out」で bin ディレクトリ下に Main.dll というファイル名で作成するようし指示します。注意して欲しいのは、DLL のファイル名は何でもよいのですが、一度作成された DLL のファイル名を変更してはいけないという点です。

これで用意が出来ました。あとはブラウザから「http://localhost/sample/Main.aspx」のようにしてアクセスするだけです。

セッション情報の利用

オブジェクトの動的生成

さて、上述のプログラムを実行してみて何かおかしい点に気付かれた方がいるかもしれません。このプログラムの内容からすれば、ボタンを複数回押せば表示が「Hello World」「Hello World World」「Hello World World World」のように「World」が増えていきそうなのに、そうはなりません。

これは、プログラムが呼ばれるたびに Page_Load が実行され、label のテキストが「Hello」に初期化されてしまうためです。なぜこのようなことになってしまうのでしょうか。それは考えてみれば単純な話で、一度ブラウザに表示されたものはそれだけで一枚のページとして完結しているからです。従って、ボタンを押すなどのアクションが行われた場合には再び新しいページとしてプログラムが実行されるため Page_Load によって初期化しなければならないのです。

同様に次のようなケースを考えてください。

		protected void  Clicked_Hello(object sender, EventArgs e) {
			Button btn2 = new Button();
			btn2.ID = "btn2";
			btn2.Text = "ステップ2";
			btn2.Click += new EventHandler(Clicked_Step2);
			block.Controls.Add(btn2);
		}
		
		protected void Clicked_Step2(object sender, EventArgs e) {
			label.InnerText += " World";
			block.Controls.Remove(block.FindControl("btn2"));
		}

Main.aspx.cs の Clicked_Hello メソッドを修正したものです。Button オブジェクト btn2 を生成し、Click イベントに対してイベントハンドラ Clicked_Step2 を設定して block オブジェクトの配下へ追加しています。HtmlGenericControl などのブロック要素を管理するクラスは配下のオブジェクトをおさめた Controls メンバを持っていて、このメンバの Add メソッドによってオブジェクトを配下におさめています。これにより、block の位置(div 要素の中)に btn2 ボタンが現れることになります。

呼ばれないイベントハンドラ

さて、ここで期待するのは

1. btn ボタンを押す。
2. block の位置に btn2 ボタンが追加される。
3. btn2 ボタンを押す。
4. label のボタンが削除され「Hello World」と表示される。

という流れです。しかし実際には Clicked_Step2 が呼ばれることはありません。なぜでしょうか。それはボタンが押されると Page_Load メソッドによりページが再構成されたのち、イベントハンドラが呼ばれるからです。つまり Page_Load で btn2 が生成されない、即ち btn2 が存在しないことになってしまうのです。ですから、イベントを受け取る対象が存在しなくなってしまうので、Clicked_Step2 が呼ばれないわけです。

Session

これらの問題を回避するには Session により現在の状態を管理し、Page_Load で適切な処理を行う必要があります。まず期待通りの動作を行う、修正された aspx.cs ファイルを示します。

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace sample {
	public class Main : System.Web.UI.Page {
		protected Button btn;
		protected HtmlGenericControl block;
		protected HtmlGenericControl label;

		public Main() {
			this.Load += new EventHandler(Page_Load);
		}
		
		private void Page_Load(object sender, EventArgs e) {
			String str;
			if((str = (String)Session["label"]) == null){
				str = "Hello";
				Session["label"] = str;
			}
			label.InnerText = str;

			Button btn2;
			if((btn2 = (Button)Session["btn2"]) != null){
				btn2.Click += new EventHandler(Clicked_Step2);
				block.Controls.Add(btn2);
			}
			
			btn.Click += new EventHandler(Clicked_Hello);
		}

		private void  Clicked_Hello(object sender, EventArgs e) {
			Button btn2 = new Button();
			btn2.ID = "btn2";
			btn2.Text = "ステップ2";
			btn2.Click += new EventHandler(Clicked_Step2);
			block.Controls.Add(btn2);
			Session["btn2"] = btn2;
		}
		
		private void Clicked_Step2(object sender, EventArgs e) {
			block.Controls.Clear();
			label.InnerText += " World";
			block.Controls.Add(label);
			Session.Remove("btn2");
			Session["label"] = label.InnerText;
		}
	}
}

先に Click_Hello メソッドの下の方を見てください。Session というプロパティを操作しています。この Session は Web アプリーケーションにアクセスしている間のセッション全体にわたって利用できる情報の、辞書型(キーと値のペアのリスト)コンテナです。ここで「btn2」というキーで Button オブジェクト btn2 を保存しています。

次に Clicked_Step2 メソッドの下の方を見てください。Session に対して「label」というキーで label のテキストを保存しています。

では Page_Load メソッドを見てください。まず、Session の「label」キーを調べます。このキーが無ければ、初期値として「Hello」を設定します。このキーが存在するなら、その値を使用します。次に Session の「btn2」キーを調べます。このキーが存在するなら、その値(Button)を btn2 として block に追加します。このとき、イベントハンドラを改めて設定しなおしていることに注意してください (*1) 。こうすることにより、文字列が保存され、また btn2 がイベントを発生させた後の呼び出しでも Page_Load で btn2 が作成されるため、イベントハンドラが正しく呼び出されます。

これは推測ですが、オブジェクトが作成されるたびにメソッドのアドレスが変わってしまうため毎回設定しないと挙動がおかしくなるのだと思います。

上の例では Session にはオブジェクトも保存できる点、イベントハンドラは再設定する必要がある点を示すために Session へ「btn2」として Button オブジェクトを保存しましたが、あるいは「btn2」キーに対して Button オブジェクトを保存するのではなく、これをフラグのかわりに使用し、「btn2」の値が「true」なら Page_Load の中で新しく btn2 オブジェクトを作る、というふうにしてもよいでしょう。

なお、Clicked_Step2 メソッドは

			label.InnerText += " World";
			block.Controls.Remove(block.FindControl("btn2"));
			Session.Remove("btn2");
			Session["label"] = label.InnerText;

とすることもできるのですが、mono では FindControl メソッドが正しく動作しないため、次善策として上述のようなプログラムとしました。

ViewState Request Response

今回、情報を保存する手段として Session を使いましたが、他にも主にコントロールの状態(チェックボックスやリストボックスなどの状態)を保存する ViewState プロパティ、HTTP リクエストを処理する Request プロパティ HTTP レスポンスを処理する Response プロパティがあります。これらをうまく利用することでもアプリケーションを制御することが出来るでしょう。

(untitled)

(untitled)

足早ではありますが、ASP.NET の概要を分かっていただけたでしょうか。私自身、手探り状態で試したため至らぬ点が多々あるかとは思いますが、Java 以外でも Web アプリケーションの開発が出来るということをご理解いただき、また ASP.NET に興味を持ってみてもらえればと思います。