特集 VIVA! Servers


Visual Basic
クライアントアプリケーションから、
Transactional ASPを利用したWebアプリケーション開発

秋月 巌 AKIZUKI, Iwao
秋月 巌ソリューション事務所


メリッサの衝撃

 この号が店頭に並ぶ頃にはすでに古い話題になってしまっていると思うが、メリッサの衝撃がコンピュータユーザーを恐怖に陥れた。メリッサ自身は悪意も少ないし、予防法も難しくない。ウィルスチェックなしに送信されてきた添付ファイルを開かないような習慣をつけさえすればよい。また、類似品を含めてワクチンソフトを開発することも簡単だ。該当のOffice文書にWindows APIが使われていないことを確認すればよい。もちろん、私はメリッサを受け取らなかったし、そのコードを見たことがあるわけではないが、Windows APIを使わずにあのような処理を行なうことはできないはずである。
 メリッサが問題だったのは、その感染力である。もし、あの感染力で、もっと害のあるウィルスがデビューしていたら、それこそ惨事にいたっていただろう。新聞などにはメリッサの被害として過負荷によるサーバーのダウンが報じられていたが、サーバーダウンはメリッサが感染するプロセスで発生した問題であり、メリッサに記述されたソースコード自体が与えた被害ではない。サーバーがもっと丈夫だったら、あるいはサーバーあたりのメールの数がもっと少なかったら、サーバーはダウンしなかったかもしれない。

その感染力はクライアントサーバーアーキテクチャによってもたらされた

 その強力な感染力は、メリッサがクライアントサーバー(C/S)型のウィルスであったことに原因している。もちろん、メリッサはクライアントアプリケーションであり、サーバーはSMTPサーバーである。動作している間のメリッサは、完全にSMTPメールクライアントとして動作する。このメールクライアントが一般のメールクライアントと異なるのは、一種類のメッセージしか送出できない点である。もちろん、そのメッセージには添付ファイルとして自分自身が含まれている。
 コミニュケーションスタイルとしてのC/S型システムの特徴は、誰かと連絡をとる際に必ず代理人を経由するということである。この代理人がサーバーであり、結果としてサーバーに情報が集中する。このメリットはサーバーが情報を統括することで利用者間の情報あるいはリソースの共有が可能だということであり、デメリットとしては負荷の集中とサーバーの重要性が極度に高まることである。皆が代理人を経由して通信していると、代理人が病気で寝込んだ場合に誰とも連絡がとれなくなってしまうのである。

クライアントサーバーと
データベース

 C/Sデータベースシステムは、C/Sシステムとデータベースシステムを組み合わせたものである。しかし、この二者は必ずしも一体ではない。たとえば、Webデータベースシステムを考えてみれば、それは明らかである。Webデータベースシステムは完全なC/Sシステムとして動作するが、データベースシステムはデータベースアクセスを行ない、C/Sとしての動作はWebサーバーとWebクライアントに委ねられている。
 メールにも同様のことがいえる。一般にメールの配信はSMTP/POPサーバーとSMTP/POPクライアントによって行なわれている。しかし、Yahoo!やHotMailのようなメール配信サービスはPOPサーバーを使わずに、Webサーバー、つまり、HTTPサーバーによって行なわれている。

WebサーバーはWebブラウザ以外からもアクセスが可能

 このようにWebサーバーはサーバー側に何かしらのアプリケーションを配置することで、汎用的なC/Sシステムとして動作する可能性をもつ。つまり、メールサーバーにもデータベースサーバーにもファイルサーバーにもなるのである。
 しかし、それではクライアントにWebブラウザを使わなければいけないと考えるかもしれない。それをVisual Basicアプリケーションクライアントで実現するのが、INETコントロールのHTTPサポートである。今回はこのコントロールをVisual Basicアプリケーションで利用し、Microsoft SQL ServerにIenternet Information Server経由でアクセスする手法を紹介する。
 INETコントロールを使わなくても、WinSockコントロールを使ったりWinSock APIを使用して同じことを実現することもできる。今回、解説するサンプルはDDJ日本版の3月号で使ったものを移植したものだが、そこではWinSockコントロールを使っていたのである。ちなみにそのサンプルはVisual Basicアプリケーションではなく、Visual Basicで開発したActiveXコントロールとして実装されており、しかも、そのコントロールはWebブラウザ内で起動していた。

CookieをサポートするINETコントロール

 INETコントロールのWinSockコントロールに対して高水準に実装されているので、ユーザーはHTTPプロトコルについて理解する必要もないし、Cookieなどの実装も自動化される。サンプルではActive Server Pagesのセッション維持機能を利用しているのでWinSockコントロールで同様の処理を実装するには、Cookieを開発者がサポートする必要がある。Active Server Pagesのセッションは内部的にCookieを利用しているからである。

Webサーバーへのリクエスト

 INETコントロールでHTTPリクエストを要求する方法は簡単である。INETコントロールのExecuteメソッドにURLを指定すればよい。サンプルアプリケーションのボタンのClickイベントプロシージャに記述された次のコードが、HTTP要求を実行している。

Inet1.Execute sParam, "GET"
 引数sParamには、取得するページのURLが保持されている。取得される結果は、Webブラウザの「ソースを表示」で確認できるものと同である。Webブラウザのように表示するためには、そのための処理をする必要がある。もちろん、今回はブラウザで表示するためではなく、データを受信するためだけに使用するのでその必要はない。
 返信されたデータを取得するのは、少し複雑である。INETコントロールによるイベント処理はすべてStateChangedイベントを使用する。このコントロールは、このイベントしかないのである。StateChangedイベントプロシージャにはこのイベントが呼び出されたステータスが渡される。値の取得を行なうのは、ステータスとして定数icResponseCompletedが渡されたときである。

サンプルプログラムの構成

 図1はサンプルプログラムの実行画面と構成を示している。サンプルプログラムはクライアント用のVisual Basicフォームと、サーバー用の2つのActive Server Pagesファィルによって構成されている。Active Server Pagesファイルはデータの取得用と更新用の2つが用意されており、処理に応じてクライアントプログラムが使い分ける。

サーバー側の処理にActive Server Pagesを使用

 サーバー側での処理にはActive Server Pagesを使用している。これはもちろん、他のCGI開発方法を使用することも可能である。また、Visual Basic 6.0のWebClassを使用することもできる。ただ、今後もWebアプリケーション開発を続けるならば、WebClassよりもActive Server Pagesの方が多くの点で優れていることを実感することになるだろう。以前にも本誌特集で書いたことがあるが、WebClassのActive Server Pagesに対するメリットは、コードを隠蔽するためにActiveXコンポーネントを使用した場合のデバッグ性にとどまる。
 それ以上にActive Server Pagesの何よりのメリットは、Microsoftのプロダクト構成の中で、WebClasがVisual Basicユーザーのためのマイナーな開発手法であるのに対し、Active Server PagesはWindows DNAの中心的な構成要素であるということだ。将来性の差は最終的に大きな差となる。Active Server Pagesがなくなり、WebClassだけが残るということは、構造的にいってありえないからである。
 性能を追求するならば、ISAPIエクステンションを利用するのがもっとも有利である。実はここで紹介するのと同じものをISAPIですでに実装したものが存在する。Remote Data Service(RDS)と呼ばれるこのテクノロジーは以前、Advansed Data Connectorと呼ばれていた。専門誌等で大きく扱われることはないが、現在、Microsoftが提供するテクノロジーのもっとも優れたものである。アーキテクチャは完璧であり、実装も以前よりもはるかに進歩し、実用の領域に達している。
 ただ、Webアプリケーション用のActiveXコンポーネントとして位置付けられているため、Internet Explorerでしか使用することができない。そのため、メジャープレーヤーとしての地位が与えられていないのだが、このテクノロジーは、本稿のサンプルと同様にVisual Basicアプリケーションから利用することができる。

図1:サンプルプログラムの構成

サンプルのインストールと
実行

 付録CD-ROMの\Serverフォルダを、IISによってWebで公開されているフォルダにコピーする。これでサーバー側のActive Server Pagesが使用できる。IIS4.0の場合には、これだけでよいのだが、それ以前のバージョンのIISを使用している場合、Active Server Pagesモジュールのインストールと実行権限の設定を行なう必要がある。
 次に\ClientフォルダにあるClentVB.vbpをVisual Basicでロードする。サンプルはVisual Basic 6.0で作成されているが、プロジェクトファイルの“Retained=0”の一行を削除すれば、Visual Basic 5.0でも実行できる。
 プログラムを実行したら、URLフォルダ名項目にServerフォルダのURLパスを指定する。たとえば、デフォルトのホームURLがC:\inetpub\wwwrootで、その直下に\Serverフォルダをコピーし、ホスト名がhostnameの場合、この項目に指定する内容は次のようになる。

hostname/ ServerASP
 また、IISが稼動しているサーバーマシンにMicrosoft SQL Serverがpubsサンプルデータベースを含めてインストールされサービスが稼動している必要がある。IISとMicrosoft SQL Serverが別の場合や、ログイン権限をデフォルト以外に設定している場合はプログラムの内容を変更する必要があり、その方法については後述する。Microsoft SQL Serverは最新のMicrosoft SQL Server 7.0で作成しているが、6.5でも動作するはずである。
 データベースのアクセスにはODBCを経由しないで、OLE DBプロパイダを使用しているため、ODBCの設定を行なう必要はない。
 これで[データの取得]ボタンをクリックすれば、pubsデータベースのauthorsテーブルのデータを取得し、表示する。初回の実行時には、若干の時間がかかる。ここでボタンを何度もクリックしてしまうと、エラーハンドラーが記述されていないため、エラーが発生する。
 グリッドから任意のデータをマウスクリックすると、下部のテキストボックスにデータが表示される。データの編集が必要な場合、内容を変更し[更新]ボタンをクリックする。
 更新に成功した場合「Update Complate」と表示されたダイアログメッセージを表示する。更新に失敗した場合には「Update Abort」というメッセージが表示され、ロールバックが実行される。

トランザクションの管理

 このサンプルプログラムでは、Active Server PagesのTransactional ASPを使用することで、トランザクションを制御している。そのため、サーバー側ではDTCサービスが実行されている必要がある。データの編集後に表示されるメッセージは、トランザクションの成功と失敗を検知して表示を変更している。ここでは単純なアップデート処理だけなので、失敗することはまずないが、もっと複雑なトランザクションが発生するときに応用すれば効果は大きい。

HTTPリクエストでASPファイルを指定する(クライアントVBアプリケーション)

[データを取得]ボタンのクリック時に実行されるイベントプロシージャで実行されている処理がリスト1である。
 複雑に見えるかもしれないが、単にひとつの引数を組み立て、INETコントロールのExecuteメソッドの引数に渡しているだけである。
「URLフォルダ名」項目に“hostname/serverASP”と指定した場合、引数sParamは最終的にリスト2-(1)のようになる。
 Executeメソッドの2番目の引数に“GET”を指定し、最初の引数にこの内容を指定するということは、Webサーバーにとっては、リスト2-(2)のようなHTMLリンクがクリックされたのと等しい。
 ちなみに、このリンクがクリックされたのが図2である。データベースにアクセスしたActive Server Pagesが結果をテキストとして送り返しているのがわかる。レコードの区切りには改行コードを使用しているので、ソースでは改行されているがHTMLでは反映されていない。
 引数の内容を見てみよう。まず最初にaspselect.aspのURLが、[URLフォルダ名]項目に入力された内容を元に結合される。続いて記述されているのが、aspselect.aspに渡される引数である(リスト2-(3))。この引数は2つある。最初の引数Providerは、OLE DBのプロパイダを指定している。
 ここではOLE DBプロパイダのタイプにMicrosoft SQL Server、ユーザーIDとして“sa”、使用するデータベースに“pubsデータベース”、データベースサーバーマシン名に“hostname”を指定している。だから別のユーザー名でMicrosoft SQL Serverにアクセスするためには、このコードを変更する必要がある。また、データベースサーバーマシン名にhostnameが指定しされているのは、URLに指定したホスト名を切り出して流用しているからである。そのため、IISのホスト名とMicrosoft SQL Serverのマシンが異なる場合は、この部分を書きかえる必要がある。
 スペースの代わりにプラス(+)記号が使われていることに注意してほしい。これはURLにスペースを使うときのHTTPの規則である。もっとも、INETコントロールを使う限りは、ここがスペースのままでも、正しく実行される。
 もうひとつの引数SQLでは、実行するSQL文を指定している。

SQL=SELECT+au_id,au_lname,address,city+FROM+authors
 ここではauthorsテーブルから4つの項目と全レコードを指定して値を取得する。

リスト1:[データを取得]ボタンによって実行される処理
sServer = Mid(txtServerName, 1, _
  InStr(1, txtServerName, "/") - 1)
sParam = "http://" & txtServerName & "/aspselect.asp"
sParam = sParam _
  & "?CONPARAM=Provider=SQLOLEDB.1;" _
   "User+ID=sa;Initial+Catalog=pubs;" _
   "Data+Source=" & sServer
sParam = sParam _
  & "&SQL=SELECT+au_id,au_lname,address,city+FROM+authors"
Inet1.Execute sParam, "GET"

リスト2
@
http://hostname/serverASP/aspselect.asp?CONPARAM=Provider=SQLOLEDB.1;User+ID=sa;Initial+Catalog=pubs;Data+Source=santiago&SQL=SELECT+au_id,au_lname,address,city+FROM+authors

A
<A HREF=http://hostname/serverAsp/aspselect.asp?CONPARAM=Provider=SQLOLEDB.1;User+ID=sa;Initial+Catalog=pubs;Data+Source= hostname&SQL=SELECT+au_id,au_lname,address,city+FROM+authors>
リンク
</A>

B
Provider=SQLOLEDB.1;User+ID=sa;Initial+Catalog=pubs;Data+Source=hostname

図2:aspselect.aspの実行結果をブラウザで取得

Active Server Pagesによるデータアクセスと返信(aspselect.asp)

 記事リスト4はデータベースにアクセスし、クライアントの結果を送り返すActive Server Pagesファイル(aspselect.asp)の全ソースリストである。
 冒頭で、先ほど送信した引数を変数に代入している。

conparam = Request("CONPARAM")
sqlparam = Request("SQL")
 Requestオブジェクトは、HTTPリクエストに含まれる引数を取得するのに使用するActive Server Pagesのオブジェクトである。このオブジェクトはWebClassでも使用することができる。
 以下のコードはADOのConnectionオブジェクトがデータベースに接続するためのプロセスである。
Set dbConnection = _
 Server.CreateObject("ADODB.Connection")
dbConnection.Open conparam
Set rs = Server.CreateObject("ADODB.Recordset")
rs.Open sqlparam, dbConnection, 1, 3
 ConnectionオブジェクトのOpenメソッドの引数に、クライアント側で作成した接続文字列を利用している。ポイントは最後の行、RecordsetオブジェクトのOpenメソッドの実行時に、結果セットを更新可能なレコードセットとして作成していることである。ここで作成したRecordsetオブジェクトは、更新用のActive Server Pagesファイルでも再利用され、そこでRecordsetを使用した更新処理が実行される。
 次の2行は返信データの開始位置を指示するためのヘッダーである。図2の冒頭に“DataStart”という文字列が表示されていることがわかる。
Response.Write("DataStart")
Response.Write(Chr(13) & Chr(10))
 以下の処理はレコードセットの内容を抽出し、項目と項目の間にタブ記号“Chr(9)”、レコードとレコードの間に改行コード“Chr(13) & Chr(10)”を挿入し、クライアントに返信している。
Do While Not rs.EOF
  FieldCount = rs.Fields.Count
    i =  0
    Do While i < FieldCount
      Response.Write(rs.Fields(i) & Chr(9))
      i = i + 1
    Loop
  Response.Write(Chr(13) & Chr(10))
  rs.MoveNext
Loop
 クライアントに結果を返すためには、Active Server PagesモジュールのResponseオブジェクトを使用する。ネストしたループ内でレコードセット内の全項目のデータとすべてのレコードを出力している。最終レコードの処理が終了するとループを脱出し、処理は次に移る。
 RecordsetオブジェクトをSessionオブジェクトに格納する次の処理は重要である。
Set Session("rs") = rs
 ここで格納したオブジェクトやデータを、別のページから呼び出すことで擬似的にページ間でセッションが継続しているように扱うことができる。もちろん、別のページからこのオブジェクトを参照できるのは、このオブジェクトを格納したユーザーに限られる。この場合の「同じユーザー」とは、同じマシンのブラウザから一度もブラウザを閉じることなく再度アクセスしたユーザー、という定義である。このサンプルの場合でいえば、同一のINETコントロールから再ロードすることなくアクセスしたユーザー、ということになる。
 この同一性の確保のためにCookieが用いられている。INETコントロールがCookieをサポートしているため、Active Server PagesのSessionオブジェクトが使えるのである。
 最後の2行はデータ送信の終了を指定するための文字列とコードである。
Response.Write("DataEnd")
Response.Write(Chr(13) & Chr(10))
返信結果の取得
(クライアントVBアプリケーション)

 Active Server Pagesファイルが返信した結果を処理するのは、クライアントに配置されたINETコントロールのStateChangedイベントプロシージャである。Webサーバーが返信する結果を受信終了したとき、INETコントロールはStateChangedイベントプロシージャの引数Stateに定数icResponseCompletedの値を設定する。
 以下の条件指定により続くコードはStateの内容が12の場合にのみ実行される。

Select Case State
       Case icResponseCompleted
 受信したすべてのデータを変数strDataに格納しているのが、次のコードである。
Do While Not bDone
  vtData = Inet1.GetChunk(1024, icString)
  strData = strData & vtData
  If Len(vtData) = 0 Then
    bDone = True
  End If
Loop
 INETコントロールのGetChunkメソッドを使ってバッファの内容を取得し、それを文字列型の変数strDataに追加している。バッファの内容が空になった時点でループを終了する。
 取得した内容に文字列"DataStart"が含まれている場合、すなわち、aspselect.aspの内容を受信した場合には、次のコードによってユーザー定義関数MakeGridDataを実行している。
If InStr(strData, "DataStart") > 0 Then
  MakeGridData strData
 ここで条件を指定しているのは、このイベントプロシージャが、更新用のActive Server Pagesファイルの結果を取得したときにも使用されるからである。
 関数MakeGridDataは取得したデータをフレキシブルグリッドコントロールに表示する。この関数の説明は割愛する。

テキストボックスにカレントレコードの内容を表示する(クライアントVBアプリケーション)

 フレキシブルグリッドコントロールに最初がデータに表示されたときや、同コントロールがクリックされたときに、テキストボックスにデータを表示しているのが、次のプロシージャである。

Private Sub flgList_Click()
  Dim i As Integer
  For i = 0 To 3
    flgList.Col = i
    txtField(i).Text = flgList.Text
  Next
End Sub
 このフレキシブルグリッドコントロールのClickイベントプロシージャでは、クリックされた行のデータを先頭から順次テキストボックスに代入する。テキストボックスに表示された内容はデータの編集が可能になる。

更新内容の送信
(クライアントVBアプリケーション)

 編集されたテキストボックスの内容をデータベースに反映させるためには[更新]ボタンをクリックする必要がある。  次の条件文では、ユーザー定義関数DataCheckを使用してデータがユーザーによって変更されたか調べている。変更されていない場合、サーバーへのデータ送信は行なわない。

If Not DataCheck Then Exit Sub
 INETコントロールのExecuteメソッドの引数を合成する次の部分では、更新用のActive Server Pagesファイルaspupdate.aspのURLを指定している。
sParam = "http://" & txtServerName & "/aspupdate.asp"
 以下のコードはテキストボックスの内容をaspupdate.aspの引数として指定している。
sParam = sParam & "?FIELD1=" & txtField(0).Text
sParam = sParam & "&FIELD2=" & txtField(1).Text
sParam = sParam & "&FIELD3=" & txtField(2).Text
sParam = sParam & "&FIELD4=" & txtField(3).Text
 Webサーバーにリクエストを実行しているのが、次の1行である。
Inet1.Execute sParam, "GET"
 合成した変数sParamとHTTPオペレーションである“GET”をExecuteメソッドの引数として指定している。

Active Server Pagesによるデータ更新(aspupdate.asp)

[更新]ボタンがクリックされたときに呼び出されるaspupdate.asp(リスト3)について解説する。このファイルの先頭に記述されている次の1行によって、このActive Server PagesファイルがTransactional ASPとして有効になる。

<%@TRANSACTION=Required%>
 Transactional ASPはMicrosoft Transaction Serverを利用して、Active Server Pagesのトランザクション処理を可能にする。
 次のコードはaspselect.aspでSessionオブジェクトに格納したRecordsetオブジェクトを取り出している。
<% Set rs = Session("rs")
 ここではオブジェクトのポインタを取得しているので、前のActive Server Pagesファイルで、このRecordsetオブジェクトを扱ったときの状態がそのまま引き継がれる。
 検索を実行する次のコードの先頭で、レコードポインタを先頭に移動しているのはaspselect.aspでポインタを最後に移動しているからである。
rs.MoveFirst
  Do While Not rs.EOF
    If rs.Fields(0) = Request("FIELD1") Then
 更新するレコードが見つかったら、各項目にRequestオブジェクトが切り出した値を代入する。この値はクライアントが送信したユーザーの入力内容である。
rs.Fields(1) = Request("FIELD2")
rs.Fields(2) = Request("FIELD3")
rs.Fields(3) = Request("FIELD4")
rs.Update
 Recordsetオブジェクトに受信した値を入力した後、Updateメソッドを実行することで更新を実行する。
 この後で、明示的にObjectContextオブジェクトのSetCompleteメソッドを呼び出し、トランザクションの終了を宣言する。
ObjectContext.SetComplete
 トランザクションの終了後、その結果に応じてOnTransactionCommitイベントかOnTransactionAbortイベントが発生する。それぞれのイベントに対応するプロシージャが次の2つのプロシージャである。
Sub OnTransactionCommit
  Response.Write "Update Complete"
End Sub

Sub OnTransactionAbort
  Response.Write "Update Abort"
End Sub
 ここでは発生するイベントに応じて返信する内容を変更している。この内容はクライアント側での処理に関連するので重要である。

トランザクションの結果を受信する
(クライアントVBアプリケーション)

 aspselect.aspの返信を受信するとき同様、aspupdate.aspの結果を受信するのもINETコントロールのStateChangedイベントプロシージャである。
 バッファから受信内容を取り出した後、次の条件がFalseになるため、Else以降の処理が実行される。

If InStr(strData, "DataStart") > 0 Then
           | (省略)
Else
 Else以降はネストしたIf文が用意されている。この条件文でトランザクションの成功時と失敗時の処理を分岐している。
 成功時には次のコードにより、テキストボックスの内容がフレキシブルグリッドに転記される。
If strData = "Update Complete" Then
  For i = 0 To 3
  flgList.Col = i
  flgList.Text = txtField(i).Text
  Next
End If
 最後に受信したメッセージをメッセージボックスで表示し、処理を終了する。
MsgBox strData
 ここでは単一のトランザクションしか実行していないが、複数の更新を同時におこなうようなケースでは、それらがセットでロールバックされ、データの整合性が確保される。

まとめ

 今回のサンプルプログラムでは、私が実際の開発に用いない方法をいくつか使用している。
 まず、Active Server PagesのSessionオブジェクトである。このオブジェクトを使用するにはいくつかの問題がある。まず、サーバー側のリソースが無駄使いになりがちだというのもそのひとつだが、Sessionオブジェクトが次回のアクセス時に有効か無効かはタイムアウトに依存することになる。このサンプルでは、タイムアウトが発生した場合のエラーハンドラーは記述していないので、その場合、当然、エラーが発生する。
 Active Server Pagesを使ってステートを維持する場合、私はCookieかURLのクエリーストリング、あるいはフォーム内でHIDDEN属性をもつ<INPUT>タグを使用する。
 次にRecordsetオブジェクトを使った更新である。私は普段、Recordsetオブジェクトを読取専用で作成し、更新や追加する場合には、それに対応するSQL文を作成して実行する。それは単にRecordsetオブジェクトの更新処理の最適性を信じていないからにほかならない。それは今回のようにRecordsetオブジェクトをページをまたいで使用する場合にはなおさらである。
 最後にTransactional ASPを使用したトランザクション管理である。私だったら、MTSのような複雑なレイヤーを介したトランザクションコントロールより、データベースが提供するトランザクション管理メソッドを直接利用する。それはDTC、MTSといったMicrosoftが提唱するメカニズムに対する不信感をもっているからである。  今回、紹介した手法は概してMicrosoftのWindows DNAアーキテクチャに準じたものであるということができる。しかし、それが最善の方法だというわけではないことは覚えていた方がいい。
 Microsoftが提供する製品には優れたものも少なくない。しかし、それもMicrosoftが推薦するように使用すると、あまり調子よく動作しない。しかし、Microsoftを責めてはいけない。Microsoftが提唱するのは、コンピュータとはユーザーの責任において使用するものだということなのである。そして、その主張は間違いなく真理である。
 この真理が普及すれば、プログラムを開発提供をしている読者の方々も、エンドユーザーから非難されることがなくなるのである。しかし、もちろんそんなことになるわけはないので、責められるのは開発者だということになる。Microsoftの提案通りにシステムを作ったのがいけないのである。

リスト3:クライアントVBアプリケーションの全ソースコード
Private Sub cmdSelect_Click()
  Dim sParam As String
  Dim sServer As String

  sServer = Mid(txtServerName, 1, InStr(1, txtServerName, "/") - 1)

  sParam = "http://" & txtServerName & "/aspselect.asp"
  sParam = sParam _
         & "?CONPARAM=Provider=SQLOLEDB.1;User+ID=sa;Initial+Catalog=pubs;Data+Source=" _
             & sServer
  sParam = sParam & "&SQL=SELECT+au_id,au_lname,address,city+FROM+authors"
  Inet1.Execute sParam, "GET"
End Sub

Private Sub cmdUpdate_Click()
  Dim sParam As String
  Dim sServer As String

  If Not DataCheck Then Exit Sub

  sParam = "http://" & txtServerName & "/aspupdate.asp"
  sParam = sParam & "?FIELD1=" & txtField(0).Text
  sParam = sParam & "&FIELD2=" & txtField(1).Text
  sParam = sParam & "&FIELD3=" & txtField(2).Text
  sParam = sParam & "&FIELD4=" & txtField(3).Text

  Inet1.Execute sParam, "GET"
End Sub

Private Function DataCheck() As Boolean
  Dim i As Integer
  DataCheck = False
  For i = 0 To 3
    flgList.Col = i
    If txtField(i).Text <> flgList.Text Then
      DataCheck = True
      Exit Function
    End If
  Next
End Function

Private Sub flgList_Click()
  Dim i As Integer
  For i = 0 To 3
    flgList.Col = i
    txtField(i).Text = flgList.Text
  Next
End Sub

Private Sub Form_Load()
  flgList.ColWidth(0) = 1300
  flgList.ColWidth(1) = 1300
  flgList.ColWidth(2) = 1800
  flgList.ColWidth(3) = 1500
  flgList.Row = 0
  flgList.Col = 0
  flgList.Text = "au_id"
  flgList.Col = 1
  flgList.Text = "au_lname"
  flgList.Col = 2
  flgList.Text = "address"
  flgList.Col = 3
  flgList.Text = "city"
End Sub

Private Sub Inet1_StateChanged(ByVal State As Integer)
  Dim vtData As Variant 'データを入れる変数。
  Dim strData As String
  Dim bDone As Boolean
  Dim i As Integer

  strData = ""
  bDone = False
  Select Case State
    Case icResponseCompleted '12
      Do While Not bDone
        ' チャンクを取得します
        vtData = Inet1.GetChunk(1024, icString)
        strData = strData & vtData
        If Len(vtData) = 0 Then
          bDone = True
        End If
      Loop

      If InStr(strData, "DataStart") > 0 Then
        MakeGridData strData
      Else
        If strData = "Update Complete" Then
          For i = 0 To 3
            flgList.Col = i
            flgList.Text = txtField(i).Text
          Next
        End If
        MsgBox strData
      End If
    End Select
End Sub

Private Sub MakeGridData(strParam As String)
  Dim strRow As String
  Dim strField As String
  Dim lngCurPointer As Long
  Dim CurRecordLen As Integer
  Dim intFieldStartPos As Integer
  Dim intFieldNextPos As Integer
  Dim i As Integer
  Dim j As Integer
  Dim CPOS As String
  Dim CPOSlen As Integer
  CPOS = Chr(9)
  CPOSlen = Len(CPOS)

  j = 0
  ' フィールド情報の先頭に移動
  lngCurPointer = InStr(strParam, "DataStart")
  Do While True
    j = j + 1
    ' 処理する行の先頭ポインタを取得
    lngCurPointer = InStr(lngCurPointer, strParam, vbCrLf) + 2
    ' 処理する行の長さを取得
    CurRecordLen = InStr(lngCurPointer, strParam, vbCrLf) - lngCurPointer
    ' 処理する行を取得
    strRow = Mid(strParam, lngCurPointer, CurRecordLen)
    ' フィールド情報の最後ならば、ループを終了
    If Left(strRow, 7) = "DataEnd" Then
      Exit Do
    End If

    intFieldNextPos = 1
    flgList.Rows = j + 1
    flgList.Row = j
    For i = 1 To 4
      intFieldStartPos = intFieldNextPos
      ' 区切り記号が見つかったら、左側がフィールドの値
      intFieldNextPos = InStr(intFieldStartPos, strRow, CPOS) + CPOSlen
      If intFieldNextPos <> 0 + CPOSlen Then
        ' 値を strField 変数に割り当てます。
        strField = Mid(strRow, intFieldStartPos, intFieldNextPos - intFieldStartPos - CPOSlen) 'Left(strRow, intPos - 1)
      Else
        ' 区切り記号が見つからなければ、最後のフィールドです
        strField = Mid(strRow, intFieldStartPos, Len(strField) - CPOSlen + 1)
      End If
      flgList.Col = i - 1
      flgList.Text = strField
    Next
  Loop

  flgList.Row = 1
  flgList.Col = 0
  flgList_Click
  flgList.SetFocus
End Sub

リスト4:データ取得用Active Server Pagesファイルの全ソースコード(aspselect.asp)
<%
  conparam = Request("CONPARAM")
  sqlparam = Request("SQL")

  Set dbConnection = Server.CreateObject("ADODB.Connection")
  dbConnection.Open conparam
  Set rs = Server.CreateObject("ADODB.Recordset")
  rs.Open sqlparam, dbConnection, 1, 3

  Response.Write("DataStart")
  Response.Write(Chr(13) & Chr(10))

  Do While Not rs.EOF
    FieldCount = rs.Fields.Count
    i =  0
    Do While i < FieldCount
      Response.Write(rs.Fields(i) & Chr(9))
      i = i + 1
    Loop
    Response.Write(Chr(13) & Chr(10))
    rs.MoveNext
  Loop

  Set Session("rs") = rs

  Response.Write("DataEnd")
  Response.Write(Chr(13) & Chr(10))
%>

リスト5:データ更新用Active Server Pagesファイルの全ソースコード(aspupdate.asp)
<%@TRANSACTION=Required%>
<% Set rs = Session("rs")

  rs.MoveFirst
  Do While Not rs.EOF
    If rs.Fields(0) = Request("FIELD1") Then
      rs.Fields(1) = Request("FIELD2")
      rs.Fields(2) = Request("FIELD3")
      rs.Fields(3) = Request("FIELD4")
      rs.Update
      ObjectContext.SetComplete

      Exit Do
    End If
    rs.MoveNext
  Loop

Sub OnTransactionCommit
  Response.Write "Update Complete"
End Sub

Sub OnTransactionAbort
  Response.Write "Update Abort"
End Sub
%>


VB Magazine ライブラリ| Visual Basic WorkGroup
int21 ホームページ| PCDN ホームページ

PCDN
Copyright (c) 1998 int21 CorporationAll Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp