私の場合はなかなか実戦で使う機会のない両者ですが、どんなものなのか触ってみました。ここでやっていることは基本中の基本かと思われますが、やってないとすぐ忘れてしまうので備忘録として残しておきます。
実は随分前に似たようなことをSilverlight2で試したのですが、3で焼き直して再チャレンジです。
やたらと規格だらけという印象が否めないWebサービスですが、そんな規格に煩わされずにWebサービスが構築できるというWCFを使ってみました。折角なのでSilverlight3(以降SL3と略記)からWebサービスを呼び出す、ただそれだけの単純なサンプルを作ります。
XPにインストールしたVisualStudio2008とSilverlight3 SDKで作業しています。現時点で最新のSilverlight4とそのToolsはVS2010用らしいので既に環境のある2008を選択しました。
まずは空のソリューション"Silver3Demo"を作成し、そこにSilverlight3アプリケーションプロジェクト"svlApp1"とそれを表示させるWebアプリケーションのプロジェクト"webApp"を追加しました(名前空間はcom.sample.web)。WCFサービスは適宜Webアプリケーションに追加することにします。更にロジック部分を担当するクラスライブラリのプロジェクト"bizApp"をソリューションに追加します(名前空間はcom.sample.biz)。そして"webApp"より"bizApp"を参照するようにします。いつものように実験的意味合いの強いものですから冗長的なことをやってますw。
※ここで"svlApp1"の名前空間も最初は"com.sample.svl"としていたのですが、何かとエラーが出てしまい作成時に自動的に命名された"svlApp1"に戻しました。ちゃんとやれば変更できそうですが、面倒なので先送りw
"webApp"プロジェクトに[追加]-[新しい項目]でWCFサービス"wcfService"を追加するとWebサービスのインターフェース定義と実装用のsvcファイルが加わります。以前のWebサービスは*.asmxだったと思いますがWCFでは*.svcらしいです。追加の際にweb.configにもWCFサービスの設定が自動的に追加されますが、SL3から呼び出せるようにweb.configを編集します(これをやっておかないと後述の[サービス参照の追加]でエラーが出てしまいました。SL3はWS-*には対応してないんですかね?):
<system.serviceModel>
<bindings>
<customBinding>
<binding name="customBindingSL">
<binaryMessageEncoding/>
<httpTransport/>
</binding>
</customBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="com.sample.web.wcfServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="com.sample.web.wcfServiceBehavior" name="com.sample.web.wcfService">
<!--<endpoint address="" binding="wsHttpBinding" contract="com.sample.web.IwcfService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>-->
<endpoint address="" binding="customBinding" bindingConfiguration="customBindingSL" contract="com.sample.web.IwcfService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
元々あった緑の部分をコメントアウトして赤い部分を追記しました。web.configを編集した後にsvcファイルを[ブラウザで表示]させると設定に間違いがあるかどうか確認できます。
尚、サービス側の変更を行った場合は、それを参照しているクライアント側も[サービス参照の更新]をしてそれを反映させる必要があります。
取りあえずは簡単なサービスメソッドを用意しておきます。
インターフェース定義:
<ServiceContract()> _ Public Interface IwcfService <OperationContract()> _ Sub Login(ByVal User As String, ByVal Pass As String) End Interface
実装はほとんどやる気のないコードw:
Public Class wcfService Implements IwcfService Public Sub Login(ByVal User As String, ByVal Pass As String) Implements IwcfService.Login Return End Sub End Class
SL3からサービスを呼び出せるようにする為に、"svlApp1"プロジェクトにて[サービス参照の追加]を行います。ダイアログの探索でソリューションを選ぶと"wcfService"が一覧に表示されているかと思います。名前空間を"refService"にして参照を追加しました。これでサービスのWSDLからWebサービスのクライアントコードが自動生成され、それを使ってSL3よりサービスを呼び出せるようになります:
Private Sub btnLogin_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnLogin.Click Dim client As New refService.IwcfServiceClient() AddHandler client.LoginCompleted, AddressOf OnLoginCompleted client.LoginAsync(Me.txtUser.Text.TrimEnd, Me.txtPass.Text.TrimEnd) End Sub Private Sub OnLoginCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) End Sub
SL3のページに配置されたテキストボックス×2とボタンを使ってLoginっぽいことを行います(実際は何もしてませんが)。
クライアント側もまったくやる気のないコードですが、先の自動生成によって作成されたコード(クラス)を使ってSL3からWebサービスを非同期で呼び出しています。Webサービスのクライアントオブジェクトを生成し、その完了イベントにハンドラを登録し、Webサービスを呼び出すといった手順です。サービス側の処理が済むと先のハンドラが呼び出されます。何だかAjaxなjavascriptのコードでよく見かけるスタイルですね。実際、ブレークポインタを置いてデバッグ実行するとちゃんと呼び出せていました。
Webサービスのメソッド中で例外が発生しても、クライアント側にはエラーが起こった(e.Errorがnullではない)ことはわかってもその詳しい情報までは取得できません。e.Errorに入っているのもCommunicationExceptionとかTimeoutExceptionとかです。そこで例外の情報をある程度クライアント側でも受け取れるようにします。で、具体的にはどうすればいいのかはMSDNに見事なまでに直球な解説があるので、そのまんま丸っと真似しときました。
WCFに拡張Behaviorを追加し、エラー時のHTTPコード500を200に変えてSL3側でも受信できるようにします。実装は"bizApp"にMSDNのコード"SilverlightFaultBehavior"そのまんまコピーして追加しましたw 次に、このクラスが機能するようにweb.configを編集:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="silverlighFaults" type="com.sample.biz.SilverlightFaultBeavior, bizApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="SilverlightFaultBehavior">
<silverlighFaults />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="com.sample.web.wcfServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="com.sample.web.wcfServiceBehavior" name="com.sample.web.wcfService">
<endpoint address="" binding="customBinding" bindingConfiguration="customBindingSL" behaviorConfiguration="SilverlightFaultBehavior" contract="com.sample.web.IwcfService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
赤い部分が追加または変更した箇所です。拡張Behaviorの登録部分にあるtypeには厳密なクラス名とアセンブリ名が書かれています。プロジェクトのプロパティでアセンブリ名と名前空間が確認できますから"bizApp"のプロパティを参照して書き足しています。
例外処理を使うにはサービスの実装にも修正が必要です。例外をクライアントに伝えるためにFaultException(Of T)を使うことにします。ジェネリクスのTに自分で作成したエラー情報クラス"ErrorInfo"を実装し、それをFaultExceptionと共に渡すようにします。ErrorInfoは単にメッセージ文字列を入れるだけの簡単なもので、"bizApp"に実装しました:
Imports System.ServiceModel Imports System.Runtime.Serialization <DataContractFormat(Style:=OperationFormatStyle.Document)> _ <DataContract(Name:="ErrorInfo")> _ Public Class ErrorInfo <DataMember()> _ Private mMessage As String = String.Empty <DataMember()> _ Public Property Message() As String Get Return Me.mMessage End Get Set(ByVal value As String) Me.mMessage = value End Set End Property End Class
次にサービスのインターフェース定義に追記:
<OperationContract()> _ <FaultContract(GetType(biz.ErrorInfo))> _ Sub Login(ByVal User As String, ByVal Pass As String)
最後にサービスの実装ですが、ログインに失敗したら例外を発生させるというシナリオでいきましょうw
Public Sub Login(ByVal User As String, ByVal Pass As String) Implements IwcfService.Login If ログイン成功なら Then '何もしない Else Throw New FaultException(Of biz.ErrorInfo)(New biz.ErrorInfo() With {.Message = "ログイン失敗"}, New FaultReason("失敗")) End If End Sub
もはやコードになってませんがw これでサービス側のメソッド内で発生した例外をクライアントに伝えることができるはずです。
では実際にクライアント側で例外が伝わっているかを確認できるようにします。先程のやる気のないコードにちょっとだけやる気を出させてみました:
Public Sub OnLoginCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) If e.Cancelled Then 'キャンセルされた ElseIf e.Error IsNot Nothing Then 'エラー(例外)が発生 If TypeOf e.Error Is System.ServiceModel.FaultException(Of refService.ErrorInfo) Then 'ログイン失敗 Dim ex As System.ServiceModel.FaultException(Of refService.ErrorInfo) = DirectCast(e.Error, System.ServiceModel.FaultException(Of refService.ErrorInfo)) Dim Message As String = ex.Detail.Message Else 'その他のエラー End If Else '正常終了 End If End Sub
サービス側で発生した例外はクライアント側のtry-catchで捕捉するわけではなく、e.Errorで判断します。これで例外のDetailプロパティからErrorInfoが取れるのでエラーの詳細を取得することができます。
かなり大雑把ではありますが、ザクっと紹介してみました。Silverlightの利用方法ですが、アプリケーションをガッツリ全部Silverlightで組んだり、ASP.NETで便利なWebControlとして活用したりと様々かと思います。
尚、本記事に関して間違いなどあればご指摘頂ければ幸いです。