japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年9月5日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー コラム2006年4月18日 10:00
15 seconds
15 seconds japan.internet.com 編集部メールホームrss
米国 Jupitermedia が運営する、
Microsoft のインターネットソリューションで作業する開発者向けの、
フリーのリソースを提供するサイト。

不快感を与えるテキストデータの入力を排除する

国内国内internet.com発の記事

はじめに

 例えば、ユーザーからテキスト入力を受け取り、データベースへの保存とWebサイトへの表示を行うWebアプリケーションを作成しているとします。このようなアプリケーションの代表例はディスカッションフォーラムです。

 このアプリケーションを多様なユーザーに公開する場合には、不快感を与えるテキストを受け付けない(あるいはWebサイトに表示しない)ようにする必要があるかもしれません。

 そのためには、さまざまな対策が考えられます。

  • アプリケーションのユーザーグループを限定する
  • つまり、アプリケーションの使用前にユーザーにログイン/登録を求めます。このようなシステムにすると、入力されたテキストを特定の登録ユーザーに結び付けることができるため、無責任なルール違反をするユーザーが減少します。仮に問題行動があったとしても、正しい情報でユーザー登録されていれば、そのユーザーを追跡することができます。未登録ユーザーを追跡することも不可能ではありませんが、確実性は劣ります。
  • 管理者が調整を行う
  • 管理者がテキストを承認してからサイトに発行するという方法です。この方法は管理者の負担が大きくなるため、あまり現実的ではありません。
  • 不快感を与えるテキスト入力を受け付けない
  • これは、問題を根本から絶つ効果的な方法です。本稿ではこの方法を取り上げます。

 これから紹介するソリューションでは、不快感を与える言葉のリストをXMLファイルとして定義し、このリストに基づいてテキスト入力を制限する独自の複合コントロールを作成します。実装言語としてはVB .NETを使用し、テキストエディタとコマンドラインコンパイラ(vbc)を使ってコードの記述とコンパイルを行います。

コントロールについての復習

 まず、ASP.NETで使用できるコントロールの種類について簡単に復習しておきましょう。

 私たちが目にするコントロールはすべてサーバーコントロールです。これらはサーバー上で動作し、クライアントに対してHTMLをレンダリングします。サーバーコントロールは、Webフォームページに組み込まれているか(そのためオンデマンドでコンパイルされる)、プリコンパイルの状態で存在するかで大きく2つに分類されます。

 Microsoftは、次の種類のコントロールをASP.NETサーバーコントロールとして分類および区別しており、これらは前者のカテゴリに属します。

  • HTMLサーバーコントロール
  • Webサーバーコントロール
  • 検証コントロール
  • ユーザーコントロール

 おそらく最初の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に用意されているサーバーコントロールは、実質的には、少々複雑なカスタムコントロールです。これらはコンパイル済みクラスであり、ページ上に配置されると、相互にやり取りし、OnLoadなどのイベントを利用してページとやり取りし、HTMLおよびスクリプトを発行して、ASP.NETテクノロジの機能を提供します。

 カスタムコントロールを作成するにはいくつかの方法があります。

  • 2つ以上の既存のコントロールの機能を結合したコントロールをコンパイルする
  • 例えば、ボタンとテキストボックスをカプセル化したコントロールが必要な場合は、既存のコントロールを一緒にコンパイルすることで、これを実現します。このようなコントロールのことを「複合コントロール」と呼びます。
  • 既存のコントロールをカスタマイズする
  • 要件のほとんどは既存のサーバーコントロールで満たされているが、必要な機能がいくつか不足している場合は、そのコントロールクラスから派生クラスを作成し、プロパティ、メソッド、イベントをオーバーライドします。
  • 基本のコントロールクラスからカスタムコントロールを作成する
  • 要件を満たす既存の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コントロールのTextプロパティもトップレベルプロパティとして公開しています。

 複合コントロールの子コントロールはカプセル化されていることに注意してください。既定では、子コントロールを親の外部から見ることはできません。ページ開発者が親のControlsコレクションを使って子コントロールにアクセスしようとしても、リテラルコントロール(サーバー側の処理を必要としない基本的なHTML要素を出力するのに使用)が介在するため、特定の子コントロールのインデックスを取得するのは困難です。

 複合コントロールでは、子コントロールをプロパティとして公開するかどうかを選択できるほか、子コントロールのどのプロパティ/イベントをトップレベルのプロパティ/イベントとして公開するかを選択できます。複合コントロールが子コントロールのプロパティを公開する場合は、次の例に示すように、単純に処理を子コントロールに委譲します。

’ 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フォームのユーザーインターフェイス内では、次の情報を指定してカスタムコントロールをインスタンス化しています。

  • 複合コントロールのOnCheckイベント発生時に実行されるローカルサブルーチン。このサブルーチンでは、パブリックプロパティを通じて複合コントロールのラベルのテキスト値を書き換えます。このテキストの内容は、OnCheckイベントで設定される別のパブリックプロパティの値によって異なります。
  • 不適ワードが定義されているXMLファイルの名前。

 複合コントロールが呼び出すCheckTextサブルーチンの内容については、前述のコードを参照してください。

 次は、複合コントロールの実装を見ていきましょう。今回のサンプルでは、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

 このコードの概要を順番に説明していきます。

  • 使用するクラスに必要な名前空間をインポートし、このクラスが存在する名前空間を定義します。
  • 複合クラスCompositeを定義します。基本コントロールクラスを継承し、INamingContainerインターフェイスを実装することを指定します。これによって、ポストバックイベントを子Buttonコントロールにルーティングできます。
  • Compositeは、OnInitや自身のコンストラクタではなく、CreateChildControls()メソッド内で子コントロールを作成します。CreateChildControls()は、コード内で子コントロールを使用する場面でChildControlsCreatedを通じて呼び出されます。
  • Compositeは、子ButtonコントロールのClickイベントを公開しません。代わりにClickイベントを処理し、カスタムイベントCheckを発生させます。複合コントロールに子コントロール上のイベントを処理させる場合は、CreateChildControlsの中でイベントハンドラを関連付ける必要があります。
  • Compositeは、次のパブリックプロパティを公開します。
    • Text――基本の値は、子LabelコントロールのTextプロパティです。
    • Filename――不適ワードリストの読み込み元となるXMLファイル名を取得または設定できます。
  • メインのパブリック関数はCheckStringです。この関数は、指定のXMLファイルから不適ワードを配列リストに読み込み、配列リスト内の1つ1つのワードを、受け取ったテキスト文字列の中で検索します。不適ワードが見つかった場合は、適当な数のアスタリスクに置き換えます。
  • OnPreRenderでは、子TextBoxコントロールのテキストをクリアします。
  • ButtonClickedが実行されるタイミングは、子Buttonコントロールがクリックされて、onCheckが適切な引数と共に呼び出されたときです(新しいCheckEventArgsオブジェクトには、チェック済みテキストと未チェックのテキストがコンストラクタパラメータとして渡されます)。ButtonClickedが呼び出されると、OnCheckが親.aspxページ内のコードで処理されるイベントを発生させます。このイベントが子コントロールからコンテナへと「バブルアップ」され、コンテナオブジェクトのトップレベルイベントとして公開されます。
リスト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」と同じです。CheckEventArgsのコンストラクタは2つの文字列をパラメータとして受け取り、ブール値matchの値を適切に設定します。ここではイベントハンドラCheckEventHandlerも定義します。

 最後に、今回のようにIDEの助けを借りずに作業を進めてきた場合は、次のようにしてコマンドラインからコードをコンパイルできます(Webアプリケーションのルート内に「bin」サブディレクトリを作成しておき、ルートから下記のコマンドを実行します)。

vbc /t:library /out:./bin/CustomControls.dll /r:System.dll
 /r:System.web.dll /r:System.drawing.dll /r:System.Data.dll
 /r:System.xml.dll *.vb

 ここで説明した内容が、実用的な小規模アプリケーションで複合コントロールを使用する際の参考になれば幸いです。ここで紹介した例は、自分の環境に合わせて変更していただいてかまいません。今後記事で取り上げてほしい内容や、私自身または私の会社が興味を持ちそうなプロジェクトの可能性の問い合わせなど、ご意見ご要望がありましたらぜひお寄せください。

参考資料

  • .NET Framework SDKドキュメンテーション
  • ASP.NET: Tips, Tutorials, and Code』 Stephen Walther・Doug Seven・Donny Mack・Chris Payne・Billy Anders・Adam Nathan・Dan Wahlin 著、Scott Mitchell 編集、Sams、2001年8月
  • Professional ASP.NET 1.0』 Richard Anderson・Brian Francis・Alex Homer・Ron Howard・David Sussman・Karli Watson 著、Wrox Pr Inc、2002年2月
  • 各種オンラインリソース

著者紹介

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との連携を得意とする。エンタープライズ環境におけるライフサイクルの設計/開発にも携わる。
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/