不快感を与えるテキストデータの入力を排除するはじめに例えば、ユーザーからテキスト入力を受け取り、データベースへの保存とWebサイトへの表示を行うWebアプリケーションを作成しているとします。このようなアプリケーションの代表例はディスカッションフォーラムです。 このアプリケーションを多様なユーザーに公開する場合には、不快感を与えるテキストを受け付けない(あるいはWebサイトに表示しない)ようにする必要があるかもしれません。 そのためには、さまざまな対策が考えられます。
つまり、アプリケーションの使用前にユーザーにログイン/登録を求めます。このようなシステムにすると、入力されたテキストを特定の登録ユーザーに結び付けることができるため、無責任なルール違反をするユーザーが減少します。仮に問題行動があったとしても、正しい情報でユーザー登録されていれば、そのユーザーを追跡することができます。未登録ユーザーを追跡することも不可能ではありませんが、確実性は劣ります。
管理者がテキストを承認してからサイトに発行するという方法です。この方法は管理者の負担が大きくなるため、あまり現実的ではありません。
これは、問題を根本から絶つ効果的な方法です。本稿ではこの方法を取り上げます。
これから紹介するソリューションでは、不快感を与える言葉のリストをXMLファイルとして定義し、このリストに基づいてテキスト入力を制限する独自の複合コントロールを作成します。実装言語としてはVB .NETを使用し、テキストエディタとコマンドラインコンパイラ(vbc)を使ってコードの記述とコンパイルを行います。 コントロールについての復習まず、ASP.NETで使用できるコントロールの種類について簡単に復習しておきましょう。 私たちが目にするコントロールはすべてサーバーコントロールです。これらはサーバー上で動作し、クライアントに対してHTMLをレンダリングします。サーバーコントロールは、Webフォームページに組み込まれているか(そのためオンデマンドでコンパイルされる)、プリコンパイルの状態で存在するかで大きく2つに分類されます。 Microsoftは、次の種類のコントロールをASP.NETサーバーコントロールとして分類および区別しており、これらは前者のカテゴリに属します。
おそらく最初の3つはおなじみだと思います。これらは(少なくともプログラマにとっては)最も単純な種類のコントロールであり、ASP.NET内であらかじめプログラミングされています。しかし、4つ目のユーザーコントロールはちょっと違います。 ユーザーコントロールは.aspxページを.ascxという形にしたものであり、.aspxページから登録とインスタンス化を行うことでアクセスできます。これは、さまざまな付加機能を備えたサーバーサイドインクルードであり、完全にオブジェクト指向準拠のプログラミング言語を使用しているASP/ASP.NET開発者にとっては非常に有意義な手法です。ここで、私自身の言葉を交えながら、ASP.NETユーザーコントロールとは何かを定義しておきたいと思います。 ASP.NETユーザーコントロールとは、一連の機能をカプセル化している複数のサーバーコントロールまたは静的HTML要素の集まりです(場合によってはコードも含まれています)。既存のサーバーコントロールの機能を拡張しただけのものもあれば(回転できるイメージコントロールや、日付をテキストボックスに格納するカレンダコントロールなど)、いくつかの要素を連携させて一連の決まった機能を実行させるものもあります。ユーザーコントロールの大きな特徴は、分離コードを通じて(またはHTML内で)自身のプロパティを公開でき、そのプロパティを他の要素から簡単に利用できることです。 前述の4種類のコントロールは、拡張子.aspxおよび.ascxを持つWebフォームページの機能セットの一部としてJITコンパイルされるという点に注意してください。これらのクラスはページクラス階層から派生したものであり、所定のプロパティ、メソッド、イベントを継承しています。しかし、本稿で作成するカスタムコントロールは、ページクラスを継承する必要はありません。つまり、プリコンパイルコンポーネントとしてASP.NETのページフレームワークから切り離すことが可能です。ただし、それによってページフレームワークのサポートを受けられなくなるため、一部の機能を自分の手で実装する必要があります。 MicrosoftはWebユーザーコントロールとWebカスタムコントロールを区別していますが、ユーザーコントロールもある程度はカスタマイズできるため、この区別はあまり適切でないように思います。いずれにしても、カスタムコントロールがユーザーコントロールと大きく違うのは、プリコンパイルされることと、ユーザーコントロールよりも作成が難しいことです。もう1つの大きな違いは、カスタムコントロールは、グローバルアセンブリキャッシュ(GAC)に登録できるため、再利用しやすいと考えられることです(それに対してユーザーコントロールは、IIS上のアプリケーションごとに別々のコピーが必要です)。IDEとデザイン時のサポートにも違いがあります。 ユーザーコントロールとは対照的に、カスタムコントロールはクラスの形にコンパイルされます。さらに、HTML、JavaScriptなどのインターフェイス部分は、標準ページクラスのユーザーインターフェイス部分を使用するのではなく、コードとして記述します。そのため、プログラマは.NETプログラミングモデルの豊富な機能と独自の複雑なインターフェイスを柔軟に融合させることができます。カスタムコントロールはクラス内に含まれるため、.NET Frameworkのすべての機能を利用できます。つまり、プロパティを定義し、関数を実装することができます。実際、ASP.NETおよびVisual Studio .NETに用意されているサーバーコントロールは、実質的には、少々複雑なカスタムコントロールです。これらはコンパイル済みクラスであり、ページ上に配置されると、相互にやり取りし、 カスタムコントロールを作成するにはいくつかの方法があります。
例えば、ボタンとテキストボックスをカプセル化したコントロールが必要な場合は、既存のコントロールを一緒にコンパイルすることで、これを実現します。このようなコントロールのことを「複合コントロール」と呼びます。
要件のほとんどは既存のサーバーコントロールで満たされているが、必要な機能がいくつか不足している場合は、そのコントロールクラスから派生クラスを作成し、プロパティ、メソッド、イベントをオーバーライドします。
要件を満たす既存のWebサーバーコントロール(またはその組み合わせ)がない場合は、基本のコントロールクラスの1つから派生クラスを作成します。基本のコントロールクラスはWebサーバーコントロールの基本的な機能をすべて備えているため、必要な機能だけをプログラミングすれば済みます。
既にお気付きかもしれませんが、ここでは1番目の方法に焦点を当てます。つまり、必要な機能を提供する単純な複合コントロールを作成します。通常、このように複雑な問題を扱う場合には、前述のユーザーコントロールとカスタムコントロールのメリット/デメリットを考えて、必要な機能をユーザーコントロールとして実装します。しかし本稿では、複合コントロールの作成に関するいくつかの問題を紹介するために、あえてカスタムコントロールに取り組むことにします。本稿のコードを出発点として、あれこれ研究してみてください。同じアプリケーションのユーザーコントロール版の実装に興味がある人は、自分自身で試してみるか、aspalliance.comに掲載されている私のコラムを参照してください。 複合コントロールの作成今回のアプリケーションでは、ユーザーがテキストを入力するテキストボックスと、それに対応する送信ボタン、およびユーザーにフィードバックを表示するラベルを用意します。今回作成するカスタムコントロールは、次のような構造のXMLファイルを参照することで、ユーザーから送信されたテキストに不適切な言葉(不適ワード)が含まれているかどうかをチェックします。 <?xml version="1.0"?> <words> <word>word root 1</word> <word>word root 2</word> </words> サンプルを期待どおりに動作させるには、このファイルの内容をあらかじめ変更しておく必要があります。本稿で実装するバージョンでは、送信されたテキストに不適ワードが含まれていないかどうかだけを報告します。必要に応じて、この機能を拡張してください。 以降では、Textboxコントロール、Labelコントロール、Buttonコントロールという3つのASP.NETサーバーコントロールを組み合わせた複合コントロール(Composite)について説明します。ユーザーが子Buttonコントロールをクリックすると、Compositeは送信されたテキストを分析し、XMLファイル(カスタムプロパティで指定。デフォルトは「bad_words.xml」)内の不適ワードがテキスト中に含まれていないかをチェックし、カスタムイベントを発生させます。Compositeは子Labelコントロールの 複合コントロールの子コントロールはカプセル化されていることに注意してください。既定では、子コントロールを親の外部から見ることはできません。ページ開発者が親の 複合コントロールでは、子コントロールをプロパティとして公開するかどうかを選択できるほか、子コントロールのどのプロパティ/イベントをトップレベルのプロパティ/イベントとして公開するかを選択できます。複合コントロールが子コントロールのプロパティを公開する場合は、次の例に示すように、単純に処理を子コントロールに委譲します。 ’ Delegate to label, which is an instance of ’ System.Web.UI.WebControls.Label Public Property Text() As String Get EnsureChildControls() Return label.Text End Get Set EnsureChildControls() label.Text = value End Set End Property それでは、複合コントロールをインスタンス化するWebフォームのコードから見ていきましょう。 リスト1 composite.aspx
<%@ Page Language="vb" debug="true" trace="true" %> <%@ Register TagPrefix="Custom" Namespace="CustomControls" Assembly = "CustomControls" %> <html> <script language="VB" runat=server> Private Sub CheckText(sender As Object, e As CheckEventArgs) If e.Match = false Then Composite.Text = "<h2>Clean your dirty mind out!</h2>" Else Composite.Text = "Text validated OK." End If End Sub </script> <body> <h1>Anti-Swear Composite Control Example</h1><br> <form runat=server> <Custom:Composite id = "Composite" OnCheck = "CheckText" filename = "bad_words.xml" runat = server/> </form> </body> </html> このコードでは、名前空間とアセンブリ名を指定して複合コントロールを登録しています。後で、複合コントロールを.dllにコンパイルし、アプリケーションの「bin」ディレクトリに保存します。このディレクトリは、ASP.NETが最初に検索する場所です。前述の通り、再利用性を高めるために複合コントロールのdllをGACに配置することもできます。これについては、また別の記事で説明します。 Webフォームのユーザーインターフェイス内では、次の情報を指定してカスタムコントロールをインスタンス化しています。
複合コントロールが呼び出す 次は、複合コントロールの実装を見ていきましょう。今回のサンプルでは、2つのクラスをそれぞれ「composite.vb」と「checkevent.vb」という別々のVBソースファイルとして実装します。 リスト2 composite.vb
Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Xml Imports System.Collections Namespace CustomControls Public Class Composite Inherits Control Implements INamingContainer Private _filename As String = "bad_words.xml" Private label As Label Public box1 As TextBox Public Property filename() As String Get Return _filename End Get Set _filename = value End Set End Property ’takes as input the submitted text and returns ’the sanitisied version if naughty words found, ’or a copy of the original string if not Public Function CheckString(InputString as String) as string Dim alWordList As new ArrayList dim xmlDocPath as string = mappathsecure("bad_words.xml") dim xmlReader as XmlTextreader = _ new xmlTextReader(xmlDocPath) dim element as string dim output as string dim asterisks as string = "*************************" ’load the naughty word roots into an arraylist while (xmlReader.Read()) if xmlReader.NodeType=xmlNodeType.Text then alWordList.Add(xmlReader.Value) end if end while xmlReader.Close() ’check the string, replacing naughty roots with the ’appropriate number of asterisks. For Each element in alWordList InputString=InputString.Replace(element, _ asterisks.substring(1, (element.length))) Next Return InputString End Function Public Property Text() As String Get ’This method first checks the current value of the ’ChildControlsCreated property. ’If this value is false, ’the CreateChildControls method is called. EnsureChildControls() Return label.Text End Get Set EnsureChildControls() label.Text = value End Set End Property Public Event Check As CheckEventHandler Protected Overridable Sub OnCheck(ce As CheckEventArgs) RaiseEvent Check(Me, ce) End Sub ’create the child controls of the composite control Protected Overrides Sub CreateChildControls() Controls.Add(New LiteralControl("<h3>Enter some text: ")) ’the text box Dim box1 As New Textbox() box1.Text = "" Controls.Add(box1) Controls.Add(New LiteralControl("</h3>")) ’the button Dim button1 As New Button() button1.Text = "Submit" Controls.Add(New LiteralControl("<br>")) Controls.Add(button1) ’dynamically add an event handler ’to the newly created button object AddHandler button1.Click, AddressOf Me.ButtonClicked Controls.Add(New LiteralControl("<br><br>")) label = New Label() label.Height = Unit.Pixel(50) label.Width = Unit.Pixel(500) label.Text = "" Controls.Add(label) End Sub Protected Overrides Sub OnPreRender(e As EventArgs) CType(Controls(1), TextBox).Text = "" End Sub Private Sub ButtonClicked(sender As [Object], e As EventArgs) OnCheck(New CheckEventArgs(CType(Controls(1), _ TextBox).Text,CheckString(CType(Controls(1), _ TextBox).Text))) End Sub End Class End Namespace このコードの概要を順番に説明していきます。
リスト3 checkevent.vb
’ CheckEvent.vb ’ Contains the code for the custom event data class CheckEventArgs. ’ Also defines the event handler for the Check event. Imports System Namespace CustomControls Public Class CheckEventArgs Inherits EventArgs Private _match As Boolean = False Public Sub New(string1 As String, string2 as String) If string1=string2 Then _match = True End If End Sub Public ReadOnly Property Match() As Boolean Get Return _match End Get End Property End Class Public Delegate Sub CheckEventHandler(sender As Object, _ ce As CheckEventArgs) End Namespace 基本は「composite.vb」と同じです。 最後に、今回のようにIDEの助けを借りずに作業を進めてきた場合は、次のようにしてコマンドラインからコードをコンパイルできます(Webアプリケーションのルート内に「bin」サブディレクトリを作成しておき、ルートから下記のコマンドを実行します)。 vbc /t:library /out:./bin/CustomControls.dll /r:System.dll ここで説明した内容が、実用的な小規模アプリケーションで複合コントロールを使用する際の参考になれば幸いです。ここで紹介した例は、自分の環境に合わせて変更していただいてかまいません。今後記事で取り上げてほしい内容や、私自身または私の会社が興味を持ちそうなプロジェクトの可能性の問い合わせなど、ご意見ご要望がありましたらぜひお寄せください。 参考資料
著者紹介Chris Sully(Chris Sully)
英国在住のMicrosoft Web開発者。最近は.NETとASP.NETを使った開発に特に力を注ぐ。ウェールズ出身。Web設計/開発会社Cymru-Web.net(http://www.cymru-web.net/)のテクニカルディレクタを務める。同社は.NETおよび関連するMicrosoftテクノロジを用いたWebアプリケーションの受注開発を中心業務とする一方で、カスタマイズ可能なパッケージ製品も数多く提供している。また、ホスティング、設計、トレーニングなどのサービスも提供している(詳細についてはサイトを参照。メール(chris.sully@cymru-web.net)またはWebサイトからの問い合わせも可能)。
著者はCymru-Webの業務のかたわら、技術記事の執筆やフリーランスの仕事にも従事。コンピューティング工学の学士号、認知科学の修士号、コンピュータ科学(ニューラルネットワーク)の博士号を取得。カーディフの通信会社に勤務した4年間では、Webデザイナとしてスタートし、のちにMicrosoftテクノロジ中心のWeb開発者となる。さらに、ロンドンの国際コンサルタント会社で2年間にわたってイントラネットアプリケーションに携わり、その後、Cymru-Web.netに入社して現在にいたる。高い技術を持ち、VBよりもASP、特にSQLServer/IIS/XML/XSLTとの連携を得意とする。エンタープライズ環境におけるライフサイクルの設計/開発にも携わる。 |