10. 掲示板

トップどんと来い! ASP.NET > 10. 掲示板

はじめに

掲示板くらいは作りたいものだ
私はそう思っていました。自作掲示板をサイトで使わないにしても、「.NET デベロッパーを目指して!!」という副題を掲げるステップアップC#の 管理人としてそれくらいは当然の義務なのかもしれないと、微妙な心境にもなっていたものです。

そう考えているのは私だけではないでしょう。むしろそういう方のほうが多いかもしれません。
投稿できるだけの掲示板でもいいから作ってみたい。私はそう思っていたのです。

なんとも拍子抜けするようですが、その掲示板がたったの2時間でできてしまいました。そろそろ どんと来い も更新するかなと思って、1週間計画で簡易掲示板を作ろうと思ったのに、作り始めた初日にできてしまったのです。

正常に動いたときはなんとも言えぬ感動を覚えました。ああ、ついにこのときが来たか、とまで言うとうそっぽいのですがそれに似た思いはありました。
これがあるからプログラミングはやめられないと、久しぶりに感じました。

私の感慨はさておき、今回はすべての過程をゆっくり解説していくとともに、皆さんにもしっかり理解していただきたいのです。
ソースを見ると非常に簡単なので、掲示板がどうやって掲示板の機能を実装しているか、 そのアルゴリズムを自分で考えていただきたいのです。

プログラミングはこのアルゴリズムを考える部分が重要です。プロならば「自分の意思をコードに変えられる」のが当然だろうと思うのですが、 日曜プログラマにとっては自分のしたいことをコードに変える段階こそがプログラミングの中枢 です。

その部分を「ハイ、コード」と書いてしまっては実際に付く能力は半分以下でしょう。できるだけ、コードを見る前に掲示板の動作を見極め、考えておいてくだ さい。

見る

敵を知り、己を知れば、百戦危うからず
誰と戦うのかは気にせず、まずは掲示板を知らなければなりません。

かといって、普通の掲示板はどう動いているのか、ソースコードを見せてはくれません。
ステップアップC# の掲示板も私がケチなのではなく、そもそも掲示板はコードが見れないようになっているのです。

だから知るといっても、見るしかできません。ステップアップC# の掲示板「独学掲示板」を探ってみ たいと思います。

まず上に投稿フォームがあります。その他の題名などは「最低必要な部分」には 属しませんから、見ないほうがいいでしょう。邪魔なものは見ないほうが惑わさ れずにいいのです。
フォームの中にはボタンがあります。「リセット」もなくてもいいですが、「投稿する」は絶対必要です。

このボタンを押すことによって、投稿内容が下の投稿記事一覧に追加されていきます。
投稿記事一覧では、上に新しいものが追加されていることに注目します。

記事同士は水平線「<hr>」で区切られています。これはなくてはなりません。ないと非常に見にくくなってしまいます。

記事要素は
・番号
・タイトル
・投稿者
・メールアドレス
・投稿日
・返信
の5つです。この中で番号と返信以外はちょっと難しいですが、やろうと思えばできます。

ただし、掲示板の必須要素は
・投稿者
・投稿日

の2つで、投稿者オプションとして「メールアドレス」も付ければ、多少実用的になるかもしれません。

上に述べた投稿記事一覧は .NET 環境なら MySQL などを利用すれば多機能になりますが、そうすると動作が遅くなる上、難しいためここではテキストファイルを利用したいと思います。

パパッとかいつまんで掲示板を見てきましたが、掲示板がどのような要素で構成されているのか大体把握できたことと思います。
実はこの過程は私が掲示板を作ろうと思い立ち、自分の掲示板を10分ほど眺めていたものです。

ここに述べた過程はアルゴリズムを考えたのではありません。概略把握とでもいいますか、「ど んなもんかな~?」と考えるだけの部分です。

考える

今度は実際にどう動かすのかを考えてみます。ただし、まだコードは書きません。

掲示板に行ったときに何も表示されていないのではお話になりません。投稿フォーム投稿記事一覧は必ず表示されている必要が あります。
それはつまり、掲示板の各要素の表示ということになります。

テキストボックスボタンを含むフォームはただ配置するだけで 表示できます。これらの表示の仕方はもうわかっていると思いますし、わからなければ掲示板に 手をつけるのは早いため、復習しましょう。まだコードを書かなくてもかまいません。

投稿記事一覧はフォームほど簡単にはいきません。なぜなら、記事は刻一刻と変わるものなの で、フォームを配置するように記事一覧を表示することはできない のです。
そのため、全項目で述べた「テキストファイル」を利用します。

記事一覧をテキストファイルで一括管理しておけば、ただテキストファイルをリード(読み取 り)して、表示するだけで記事を表示できます。
記事を表示する際は、Response.Write() を利用せず、ラベルを利用します。

次に発生する動作はボタンを押したときです。ここでは「送信」と書かれたボタンしかありません。
このボタンを押したときに、記事一覧にフォームに記述された内容を追加します。

ここでも「テキストファイル」を利用しなければなりません。記事一覧はテキストファイルで一括管理するため、これに追加しさえすれば記事の 追加を行ったこ とになります。
ここまでが掲示板の骨格部分です。これ以降は細かいものについて考えてみます。

保存するテキストファイルは「bbs.txt」として、掲示板と同じディレクトリに 置くことにしましょう。この辺はカウンタの知識がフル利用されます。
テキストボックスは
・おなまえ
・Eメール
・本文

が必要になりますし、本文に関しては複数行入力可能にする必要があります。

ボタンが押されたら、現在ある記事一覧にフォームの内容を追加するのですが、一度ファイルを空にしてから一 気に全部を書き込みしましょう。その方が簡単で す。

メールアドレスはそのまま表示しても面白くないので、名前の部分にリンクをはるようにしましょう。この際、"mailto:"をつけることも忘れずに。
これを利用すれば、メールアドレスを入力していなくても、お名前の部分がすべて青色で表示され、均一感がでます。リンクの意味はありませんが。

投稿時間はもちろん
System.DateTime.Now().ToString()
で取得しますが、残念ながら私のサーバも、どんと来いの始めで紹介したサーバが海外サーバな ので、アメリカ時間で取得されてしまいます。

以前、私は非常に時間の問題で悩みましたが、GDNS で質問した結果、解決できました。ここでは私の解決例を用いてみます。
その解決例は、時差分を足すという簡単なものですが、まあ使えるのでいいでしょう。

あらかじめ考慮しておく点はこれで十分です。もちろん、製作中に問題が発生する場合もありますが、それはその時に対処していきます。

部分コーディング

上に述べたことをコードに変えていきます。ただし、始めから全部をやることは難しいので、その機能を部分的に考え、コーディングしていきます。
この作業は適当なファイルにメモとして記述しておくと便利です。ASP.NET ファイル自体にばらばらと書き込むよりも整理できます。

まず記事一覧管理用のテキストファイルのパスを取得しなければなりません。パスの文字列は変数 path に保存することにします。
string path;
path = MapPath(".") + "\\" + "bbs.txt";
カウンタの部分でマスターしたはずですから、難しくはないでしょう。

テキストファイルを読み書きするには StreamReader / StreamWriter が必要です。この部分もカウンタでの知識です。
// StreamReader
System.IO.StreamReader streamReader;
streamReader = new System.IO.StreamReader(path);
// --------------
// StreamWriter
System.IO.StreamWriter streamWriter;
streamWriter = new System.IO.StreamWriter(path);
コンストラクタ引数に渡した path は、上で準備したテキストファイルへのパスの変数です。
System.IO 名前空間はあらかじめ宣言されていないので、上のように絶対指定します。

記事一覧はテキストファイルから読み取り、変数 contents に入れるようにします。その後、Label1.Text に割り当てれば表示することができます。
また、ボタンクリック時はすでに記事一覧が表示されていますからわざわざ StreamReader を準備する必要はありませんLabel1.Text プロパティから読み取るだけで記事一覧が取得できます。
// Page_Load
string contents;
contents = streamReader.ReadToEnd();
// -----------------
// Button1_Click
string contents;
contents = Label1.Text;
ReadToEnd() メソッドは使い慣れたかと思います。StreamReader からすべてを読み取ります。

おなまえメール本文のテキストボックスから文字列を取得しますが、その際使う変数は
name,mail,text
とします。ちなみに、これらの投稿内容を1つの文字列にまとめて扱いたいので、plus という変数を用意しておきます。
string name,mail,text;
string plus;

name = TextBox1.Text;
mail = "mailto:" + TextBox2.Text;
text = TextBox3.Text;

plus = "<hr><b>お名前:</b><a href=\"" + mail + "\">" + name + "</a>" +
" <b>投稿時間:</b>" + System.DateTime.Now.ToUniversalTime().AddHours(9).ToString() +
"<br>" + text + "<hr>";
mail にはメールアドレスの前につける「mailto:」をあらかじめ付け足しておきま す。

plus の内容は以下のようになります。
<hr>
<br>
<b>お名前:</b><a href="maito:メールアドレス">入力した名前</a> <b>投稿時間:</b>投稿日時<br>
           :
        本文
           :
<br>
<hr>
HTML タグがわからなければ残念ながら ASP.NET をやるのは、はやいです。上のタグは寝ててもわかるくらいであるべきでしょう。
System.DateTime.Now.ToUniversalTime().AddHours(9).ToString()
この部分はわからなくてもいいでしょう。サーバから現在時間を UniversalTime で取得したうえで、WebMatrixHosting サーバとの時差9時間を足して文字列にするという処理です。
日本サーバ・自宅サーバを持っている場合は
System.DateTime.Now.ToString()
これでいいですし、自分のパソコンで試す間はこれでなければ逆に時間がずれます。

plus に追加分が代入された状態で、記事一覧を扱う変数 contents を新しくしなければなりません。ついでに、追加した分を含めて Label1.Text を更新します。
contents = plus + contents;
plus = "";
Label1.Text = contents;
1番上の行は
contents += plus;
ではいけません。私は始めこれでやっていたのですが、これだと追加分が下に追加されていきます。
また、plus は空にしておかないと再びボタンが押されたときに変な動作をしてしまいます。

記事一覧を更新しただけでは今度呼び出された時に有効ではないので、テキストファイルに書き込むのを忘れてはいけません。
ただし、ただ書き込むだけだと下のほうに書き込まれてしまうため、全部消去して新たに contents を書き込みます。もっぱら、plus は空文字にしているので使えませんが。
streamWriter.Write("");
streamWriter.Write(contents);

記事も追加表示され、テキストファイルに追加されたにもかかわらず、入力された値がテキストボックスに残ったままだと連続投稿されかねませ ん。
よって、全部を消去します。
TextBox1.Text = "";
TextBox2.Text = "";
TextBox3.Text = "";

上はすべてコード部分ですから、ビジュアル部分を示しておきましょう。WebMatrix を利用すれば簡単に配置できます。
<html>
<head>
</head>
<body bgcolor="#e0e0e0">
<form runat="server">
<h1>
初めての掲示板
</h1>
<p>
おなまえ:<asp:TextBox id="TextBox1" runat="server" Width="160px"></asp:TextBox>
</p>
<p>
Eメール:<asp:TextBox id="TextBox2" runat="server" Width="160px"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox3" runat="server" Width="368px" TextMode="MultiLine"
Height="163px"></asp:TextBox>
</p>
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="送信">
</asp:Button>
</p>
<p>
<br />
<asp:Label id="Label1" runat="server">Label</asp:Label>
</p>
</form>
</body>
</html>
こんなに <p> を利用するのは私は大嫌いなのですが、まあユーザには余り関係のないことです。変えたければ <br> を使って表現できます。

これらのコードを利用して、何とか自分で組んでみると力が付きますし、不良部分が把握できま す。
上のコードには簡易掲示板であるとしても、つけておきたい箇所が2箇所あります。これは私が判断したものですが、この2つは少なくとも必須です。

修正

修正すべき箇所は人によって違います。修正したり、追加していくことで洗練されたプログラムが出来上がります。
ここでは洗練とはいかないまでも、2つの箇所を修正・追加していきます。

まずは本文の文字列を取得する際です。文字列に改行がある場合、C# では "\n" と、エ スケープシーケンスで表現され、エスケープシーケンスが普通の改行になってテキストファイルにライトされます。

よって、\n を <br> に変換すればいいのですが、ここではすべて <pre> で囲むように処理しましょう。
<pre> で囲んだところでも HTML タグは整形済みテキストには値しないため、HTML タグでも利用できます。

text = "<pre>" + TextBox3.Text + "</pre>";
ただこうするだけで本文の部分が <pre> で囲まれ、改行はそのまま表示されますし、HTML タグも利用可能です。
私自身は始め Replace() を使って置換していたのですが、こっちのほうが便利っぽいのでこちらを採用しなおします。

これで1つ目の問題は解決しました。もう1つはエラー対処です。
現在のプログラムでは本文に何も入力されていなくても plus には文字列が代入されるため、新しい記事として認められます。

これは悪用されると無意味な投稿が連発する恐れがあります。その回避方法として、plus の代入の後に以下のコードを挿入します。
if (TextBox3.Text == "")
plus = "";
もし、本文に何も書いていなければ plus を空にする。この処理を入れることで
contents = plus + contents;
のステートメントが実質的に
contetns = contetns;
となり、記事一覧が一切変わらないことになります。

また、この簡易掲示板にはクッキーがないため、名前やメールだけ入力しておいて後は連続投稿するということもできません。
名前やメールは隠しておきたい人はいても、本文を隠しておきたい人は書き込みをしませんから、かなり効果を発揮します。

このように、ちょっとしたコードだけでよりよいプログラムを作ることができます。みなさんも「こうしたい」という点が見つかれば自分で修正・追加していき ましょう。
私自身の簡易掲示板はこれだけで十分ですからこれを完成品としておきましょう。

完成

ついに完成です。ここまであえてソースを伏せ続けてきましたが、今ここでソースを見せるともはやすべての箇所を皆さんはわかっているはずです。
これまでの過程を読み飛ばし、一気にスクロールしてここに到達した方はわからない部分があるかもしれません。とにかく上の過程を順に追って読み進めてくだ さい。
<%@ Page Language="C#" %>
<script runat="server">
    void Page_Load(object sender, EventArgs e) {
    if (!IsPostBack){
        string path;
        string contents;
        System.IO.StreamReader streamReader;
    
        path = MapPath(".") + "\\" + "bbs.txt";
    
        streamReader = new System.IO.StreamReader(path);
        contents = streamReader.ReadToEnd();
    
        streamReader.Close();
    
        Label1.Text = contents;
     }
    }
    
    void Button1_Click(object sender, EventArgs e) {
        string path;
        string contents;
        string name,mail,text;
        string plus;
    
        System.IO.StreamWriter streamWriter;
    
        path = MapPath(".") + "\\" + "bbs.txt";
    
        streamWriter = new System.IO.StreamWriter(path);
        contents = Label1.Text;
    
        name = TextBox1.Text;
        mail = "mailto:" + TextBox2.Text;
        text = "<pre>" + TextBox3.Text + "</pre>";
    
        plus = "<hr><b>お名前:</b><a href=\"" + mail + "\">" + name + "</a>" +
                " <b>投稿時間:</b>" + System.DateTime.Now.ToUniversalTime().AddHours(9).ToString() +
                "<br>" + text + "<hr>";
    
        if (TextBox3.Text == "")
            plus = "";
    
        contents = plus + contents;
        plus = "";
        Label1.Text = contents;
    
        streamWriter.Write("");
        streamWriter.Write(contents);
        streamWriter.Close();
    
        TextBox1.Text = "";
        TextBox2.Text = "";
        TextBox3.Text = "";
    }

</script>
<html>
<head>
</head>
<body bgcolor="#e0e0e0">
    <form runat="server">
        <h1>
            初めての掲示板
        </h1>
        <p>
            おなまえ:<asp:TextBox id="TextBox1" runat="server" Width="160px"></asp:TextBox>
        </p>
        <p>
            Eメール:<asp:TextBox id="TextBox2" runat="server" Width="160px"></asp:TextBox>
        </p>
        <p>
            <asp:TextBox id="TextBox3" runat="server" Width="368px" TextMode="MultiLine" 
                Height="163px"></asp:TextBox>
        </p>
        <p>
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="送信">
            </asp:Button>
        </p>
        <p>
            <br />
            <asp:Label id="Label1" runat="server">Label</asp:Label>
        </p>
    </form>
</body>
</html>
どうですか?わからない部分はありますか?
これまでまとまったコードを見せず、部分的に解説してきたのでここではそれぞれのつながりを把握しておきましょう。プログラムの詳細を見るのではなく、全 体を1つのプログラムとして見ましょう。

そして、このプログラムの中に Web アプリ独特のコードが
・IsPostBack
・MapPath
だけであることも確認してください。皆さんは Windows アプリの技術も無駄にすることなく Web アプリケーションを製作できますし、逆もいえます。

最後に、掲示板がいかに簡単なコードで構成されるかも確認しましょう。これでは「掲示板が作れるぞ!!」などと自慢できないかもしれません が、簡単であるに越したことはありません。このプログラムを大きく改良して、自分なりの掲示板を作ってください。