Uncategorized

【SOA における WCF の役割 (3)】 WCF LOB Adapter SDK によるカスタムアダプター実装

赤字 -> 2008/03/28 追記

環境:
Visual Studio 2005
.NET Framework 3.0
WCF LOB Adapter SDK

  1. WCF と BizTalk、そして LOB Adapter
  2. WCF における Binding と Channel の基礎
  3. WCF LOB Adapter SDK によるカスタムアダプター実装
  4. WCF からの LOB Adapter の使用
  5. BizTalk Server からの LOB Adapter の使用 – schema 編
  6. BizTalk Server からの LOB Adapter の使用 – orchestration 編
  7. BizTalk Server からの LOB Adapter の使用 – deploy 編

こんにちは。

今回は、前回おさらいした Binding, Channel の概念を踏まえ、実際に WCF LOB Adapter SDK を使って WCF のカスタムチャネルに相当するカスタムのアダプターを実装してみます。

まず開発環境についてですが、BizTalk Server 2006 R2 は .NET Framework 3.0 に対応していますが現状開発ツールは Visual Studio 2005 がベースとなっています。無論、Visual Studio 2008 への対応なども現在検討されていますので、今後のアナウンスなどお待ちください。(BizTalk を使う場合、当面は、Visual Studio 2005 が混在した開発環境となります。)

【Adapter Framework の概要】

まずは、いきなりアダプター作成する前に、第 1 回 でもご説明した WCF の上にかぶさっている .NET 3.0 Adapter Framework というものについて 実際の UI を見ながら簡単にご説明します。
WCF LOB Adapter SDK をインストールすると、.NET 3.0 Adapter Framework のインストールと Visual Studio への Add-in が追加されます。第1回で記載したように、WCF LOB Adapter に BizTalk は必要ありませが、.NET 3.0 Adapter Framework は BizTalk (というか、SOA のモデルです) を強く意識したフレームワークになっています。

まず、このフレームワークを組み込むと、「アダプターを使用する開発者、もしくは BizTalk の管理者」 (注 : アダプターを『作る』 開発者ではありません) は、以下のように Visual Studio 上から通常の WCF のサービスリファレンスとは別に、Adapter Service Reference というものを追加できるようになります。(これは、BizTalk Server 2006 R2 上からも起動可能です。)
カスタムアダプターを作成して組み込むと、『アダプターを使用する開発者』 は、このメニューを使って、簡単にアダプターを組み込んだ処理を実装することができるようになっています。

これを選択すると、以下のような画面が表示され、ここで選択・設定したアダプタの情報は、通常の WCF サービスの構成情報として .config に記述されます。


(アダプター参照の設定画面)

つまり、WCF の上に 1 枚「Adapter」という概念がかぶさった構成となっています。
上記の設定画面を図入りで掲載しましたが、この画面を理解しておくことはカスタムアダプターを構築する上で重要です。というのは、後述する WCF LOB Adapter の開発では、この概念を意識して必要な機能を実装していくためです。
上記の画面でおこなうことは以下です。

  1. まず、Adapter Framework に登録された Binding の中から、使用する Binding (Adapter) を選択します (画面左上)。今回開発するものは、ここに組み込んでいくことになります。
  2. つぎに、画面の右上で URI 文字列 (例: demo://virtualhost/… など) を設定するのですが、上図の接続情報の構成ボタンを押すことで、接続に必要な情報を UI 上で設定して自動的に URI 文字列を構成することができるようになっています。
    上図の [接続情報の構成ボタン] を押すと以下の画面が表示されます。
    この画面で [URI Properties] タブをクリックすると下図の画面が表示され、ここで設定した内容を元に URI 文字列 (URI テキストボックスの文字列) が構成されるといった動きです。

  3. また、この画面の [Binding Properties] タブをクリックすると、下図の画面が表示されます。
    ここに必要なプロパティの値を設定すると、WCF で言うところの binding 属性として .config に内容が反映されます。(下図)
  4. 最後に、アダプター参照の設定画面の下部ですが、ここで使用できるオペレーションの一覧を表示して、どのオペレーションを使用するかを選択することができます。

 

【カスタムアダプターの構築】

では、いよいよ上記のフレームワークにカスタムアダプターを構築して組み込んでいきましょう。

カスタムアダプター作成については以下にチュートリアルがありますが、そこそこちゃんとしたものを構築している関係もあり、はじめて構築する人にとっては半日 (は言いすぎ?でも 3 – 4 時間程度) はかかってしまうことでしょう。また、「そもそも何をやっているのか」を理解することも時間がかかるかもしれません。

チュートリアル
http://msdn2.microsoft.com/en-us/library/bb798080.aspx

そこで、以降では、より簡単なサンプルで主要なエッセンスのみをご紹介して感じをつかんで頂きたいと思います。
今回作成するサンプルでは、文字列をプロパティで指定した回数だけ繰り返して答えを返すという簡単なオペレーションの実装と、inbound シナリオの例として定期的な間隔で決められた文字列を出力するという 2 つオペレーションを持ったカスタムアダプターを作成してみましょう。

まず、Visual Studio 2005 で、[WCF LOB Adapter] のプロジェクトを新規作成します。

するとウィザードが起動してきますので、以下の通り設定していきます。

  1. まず下記の画面が表示されますので、ここで、スキームとして「myscheme」、プロジェクト名として「myadapter」を指定してみましょう。
    このように指定すると、このサービスの名前空間 (ネットワーク上で共有されるユニークな名前空間で、サービスそのものの URI とは異なります) は「myscheme://myadapter」 になります。
  2. つぎに、実装するオペレーションの種類を選択します。
    outbound というのは通常のオペレーションのように外部から呼び出されて答えを返す形態ですが、inbound というシナリオがあります。このシナリオは、第1回でも記載しましたが、例えば、「LOB のデータが更新されたら通知する」といったように、内部の処理をトリガとして応答するようなオペレーションです。
    今回は上述の通り、outbound の一般的なオペレーションを 1 つと、inbound のオペレーションを 1 つ実装しますので、以下のように双方にチェックを付けます。
  3. また、上図の「Retrieve」、「Browse」、「Search」は、前述したアダプター参照の設定画面において、設定画面の下部にあったオペレーションの表示欄の動きを定義するものです。
    今回はサンプルですので、検索機能 (Search) については割愛し、この欄への表示 (Browse) と、選択をおこなった際の構成の組み立て (Retrieve) を実施できるようにしましょう。
  4. つぎに、「アダプタープロパティ」と「コネクションプロパティ」と呼ばれるものを設定します。まずは「アダプタープロパティ」からです。
    アダプタープロパティとは、前述のアダプター参照の設定画面の [Binding Properties] タブで設定するプロパティのことです。前述したように、ここで指定するプロパティはすべて .config ファイルの binding 属性として設定されます。
    下図でプロパティ名と型、初期値を設定して [Add] ボタンを押すことで、プロパティの定義を追加することができます。今回は、文字列を繰り返す回数を指定できるよう、「Count」という int 型のプロパティだけを作成することにします。
  5. 同様に、つぎのウィザードで、「コネクションプロパティ」の設定画面があがってきます。
    コネクションプロパティとは、前述したアダプター参照の設定画面における [URI Properties] タブで指定できる属性のことです。今回は hostname を指定すると、myscheme://hostname という形式で URI が組み立てられるように、「hostname」という文字列型のプロパティを追加しておきましょう。

ウィザードを完了すると、コードファイルが一式の入ったプロジェクトが自動作成されます。(下図右)

ここで、作成されたコードファイルについて簡単にご説明しておきましょう (そろそろ、前回記載したブログの内容に関する知識が必要ですのでご注意ください)。

まず、Binding の本体を定義しているのは、<Project Name>Binding.cs というファイル (上図の CustomAdapter1Binding.cs) です。
この中では、第 2 回 (前回) に述べた BindingElement の Collection (<Project Name>BindingCollectionElement.cs) を作成していますが、使用されている BindingElement は実は 1 つだけで、<Project Name>.cs (上図の CustomAdapter1.cs) がその BindingElement になります。
この CustomAdapter1.cs クラスはコード上では Adapter というクラスを継承していますが、この Adapter クラスが前回「必須である」と記載した TransportBindingElement 抽象クラスを継承したクラスになっています。つまりこの Binding は、TransportBindingElement の BindingElement ただ 1 つだけが使用された Binding ということになります。もし Binding の Collection 自体をカスタマイズしたいなら、この <Project Name>Bindings.cs (CustomAdapter1Binding.cs) を編集すればOK ですが、コンフィグレーションによってカスタムの BindingElement が挿入できるよう <Project Name>BindingElement.cs (CustomAdapter1BindingElement.cs) というカスタム開発用の予備の BindingElement も用意されていますので、この実装をおこなって所定の箇所のコメントアウトを外すと独自の BindingElement を組み込むことができるようになっています。(つまり、この <Project Name>BindingElement.cs は予備であり、デフォルトでは使用されていません。)

outbound や inbound の処理本体 (WCF における Channel で処理されるべき部分) は、上図の <Project Name>OutboundHandler.cs、<Project Name>InboundHandler.cs の中で分離して記述できるようになっていて、
それぞれ Adapter Framework がシナリオにあった方法で呼び出してくれます。

では、まず、この Adapter (以降では、CustomAdapter1 とします) の Dispose 処理を修正しましょう。
CustomAdapter1HandlerBase.cs の Dispose メソッドの中の処理 (生成されたコードでは NotImplementedException を throw しています) を削除してください。

[CustomAdapter1HandlerBase.cs]

protected virtual void Dispose(bool disposing){// Exception は throw しません !}

つぎに、アダプター参照の設定画面でユーザ (このアダプターを利用する開発者、BizTalk 管理者) が入力する「スキーム」文字列や URI Properties (今回は、上述の通り hostname というものを設定しました) に応じて URI 文字列 (myscheme://hostname という文字列) を構成する処理を作成していきましょう。
この処理では、アダプターの Connection オブジェクト (CustomAdapter1Connection.cs) を使用しますが、その前に、Connection の組み立ての際の Open や Close 処理にも暫定のコードが入ってしまっていますので、
CustomAdapter1Connection.cs を開いて、以下の各メソッドを下記の通り実装しなおします。

[CustomAdapter1Connection.cs]

public void Open(TimeSpan timeout){// Exception は throw しません !}public void Close(TimeSpan timeout){// Exception は throw しません !}public void ClearContext(){// Exception は throw しません !}public bool IsValid(TimeSpan timeout){return true;}

では、URI 文字列を組み立てましょう。
CustomAdapter1ConnectionUri.cs を開き、以下の通り CustomAdapter1ConnectionUri クラスのコンストラクタと Uri の getter/setter を実装します。

[CustomAdapter1ConnectionUri.cs]

public CustomAdapter1ConnectionUri(){Uri = new Uri("myschema://hostname");}public CustomAdapter1ConnectionUri(Uri uri): base(){Uri = uri;}public override Uri Uri{get{if (String.IsNullOrEmpty(this.hostname)) throw new InvalidUriException("Invalid target system host name.");return new Uri(CustomAdapter1.SCHEME + "://" + Hostname);}set{this.hostname = value.Host;}}

このように、URI 自身も CustomAdapter1ConnectionUri というカスタムのクラスになっていて、例えば、URI の中から上述の hostname 部分だけ取り出したいという場合には、このクラスのプロパティを参照するだけで取り出せるような構造になっています。(そのような処理が必要になった場合には、便利です。)

さて、つぎに、上述した Adapter 参照の設定画面のブラウズ (Browse) 機能 (画面下部) を実装しましょう。
CustomAdapter1MetadataBrowseHandler.cs を開き、Browse メソッドを以下の通り実装します。今回は、繰り返し文字列を返す outbound オペレーションを「EchoTimes」、inbound オペレーションを「DoSomeWork」としておきましょう。

[CustomAdapter1MetadataBrowseHandler.cs]

public MetadataRetrievalNode[] Browse(string nodeId, int childStartIndex, int maxChildNodes, TimeSpan timeout){// 左のツリー部分に、ルートのカテゴリを追加if (MetadataRetrievalNode.Root.NodeId.Equals(nodeId)){MetadataRetrievalNode node = new MetadataRetrievalNode("CustomMainCategory");node.DisplayName = "Main Category";node.IsOperation = false;node.Description = "This category contains inbound and outbound categories.";node.Direction = MetadataRetrievalNodeDirections.Inbound | MetadataRetrievalNodeDirections.Outbound;return new MetadataRetrievalNode[] { node };}if (nodeId == "CustomMainCategory"){// Outbound オペレーションを設定MetadataRetrievalNode outNode = new MetadataRetrievalNode("Custom1/EchoTimes");outNode.DisplayName = "EchoTimes";outNode.Description = "This operation echoes the incoming string COUNT number of times in a string array.";outNode.Direction = MetadataRetrievalNodeDirections.Outbound;outNode.IsOperation = true;// Inbound オペレーションを設定MetadataRetrievalNode inNode = new MetadataRetrievalNode("Custom1/DoSomeWork");inNode.DisplayName = "DoSomeWork";inNode.Description = "This operation is used for inbound operation test.";inNode.Direction = MetadataRetrievalNodeDirections.Inbound;inNode.IsOperation = true;return new MetadataRetrievalNode[] { inNode, outNode };}return null;}

なお、Node ID は、BizTalk から使用する際 (XML 変換の際) にも重要な文字列となりますので、考えてネーミングをしておいてください。

つぎに、上述した Adapter 参照の Retrieve の処理を実装しますが、その前に、サービスやオペレーションのメタデータというものについて簡単に概念を記述しておきます。
WCF を使われたことがある方は、「メタデータ交換」(Metadata Exchange) という言葉を聞いたことがあるかと思いますが、メタデータとは、そのサービスやオペレーションが、どのような名前で、どのような引数・返値の型で、などの基礎情報のことを言います。当然のことですが、このメタデータの情報がなければクライアントはサーバのメソッドを呼ぶことはできません (どこの何に、どのような形式で呼び出せば良いのかわかりません)。

では、この Retrieve の処理を実装していきましょう。
上記の EchoTimes オペレーションは、文字列の引数 param を受け取って文字列を返す必要があります。
また、DoSomeWork は、処理する文字列を引数 param として受け取り、特に戻り値はありません。
また、今回のサンプルではユーザ定義型の交換はしないので、TypeResolver は必要ありません。
よって、CustomAdapter1MetadataResolverHandler.cs を開き、以下の通り実装します。

[CustomAdapter1MetadataResolverHandler.cs]

public bool IsOperationMetadataValid(string operationId, DateTime lastUpdatedTimestamp, TimeSpan timeout){return true;}public bool IsTypeMetadataValid(string typeId, DateTime lastUpdatedTimestamp, TimeSpan timeout){return true;}public OperationMetadata ResolveOperationMetadata(string operationId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved){extraTypeMetadataResolved = null;// ここでしか判断しないParameterizedOperationMetadata om;OperationParameter parmData;switch (operationId){case "Custom1/EchoTimes":om = new ParameterizedOperationMetadata(operationId, "EchoTimes");om.OriginalName = "lobEchoTimes";om.Description = "This operation echoes the incoming string COUNT number of times in a string array.";om.OperationGroup = "Custom1OutboundContract";om.OperationNamespace = CustomAdapter1.SERVICENAMESPACE;parmData = new OperationParameter("param", OperationParameterDirection.In, QualifiedType.StringType, false);parmData.Description = "Input string";om.Parameters.Add(parmData);om.OperationResult = new OperationResult(QualifiedType.StringType, false);return om;case "Custom1/DoSomeWork":om = new ParameterizedOperationMetadata(operationId, "DoSomeWork");om.OriginalName = "lobDoSomeWork";om.Description = "This operation is used for inbound operation test.";om.OperationGroup = "Custom1InboundContract";om.OperationNamespace = CustomAdapter1.SERVICENAMESPACE;parmData = new OperationParameter("param", OperationParameterDirection.In, QualifiedType.StringType, false);parmData.Description = "Input string";om.Parameters.Add(parmData);om.OperationResult = OperationResult.Empty;return om;default:throw new AdapterException("Cannot resolve metadata for operation.");}}public TypeMetadata ResolveTypeMetadata(string typeId, TimeSpan timeout, out TypeMetadataCollection extraTypeMetadataResolved){extraTypeMetadataResolved = null;// ここでしか判断しないthrow new AdapterException("No custom type is used.");}

一般の LOB システムでは、答えとして配列 (array) を返すことが多いと思いますが、その場合には上記の OperationResult の第 2 引数を true に設定します。

では、以下では、本体の処理部である Outbound と Inbound のオペレーションの処理を記述していきましょう。

まずは Outbound からです。簡単です!
CustomAdapter1OutboundHandler.cs を開き、Execute メソッドを実装するのみです。以下の通り記述してみましょう。

[CustomAdapter1OutboundHandler.cs]

public Message Execute(Message message, TimeSpan timeout){if (timeout.Equals(TimeSpan.Zero)) throw new AdapterException("operation is not executed, because timeout is zero");OperationMetadata om = this.MetadataLookup.GetOperationDefinitionFromInputMessageAction(message.Headers.Action, timeout);switch (message.Headers.Action){case "Custom1/EchoTimes":// 引数書式 <EchoTimes><param>...</param></EchoTimes>XmlDictionaryReader inputReader = message.GetReaderAtBodyContents();while (inputReader.Read()){if ((String.IsNullOrEmpty(inputReader.Prefix) && inputReader.Name.Equals("param"))|| inputReader.Name.Equals(inputReader.Prefix + ":" + "param")) break;}inputReader.Read();string inputValue = inputReader.Value;string resultValue = "";for (int i = 0; i < this.Connection.ConnectionFactory.Adapter.Count; i++)resultValue += inputValue;// 戻値書式 <EchoTimesResponse><EchoTimesResult>...</EchoTimesResult></EchoTimesResponse >XmlWriterSettings settings = new XmlWriterSettings();settings.OmitXmlDeclaration = true;StringBuilder outputString = new StringBuilder();XmlWriter resultWriter = XmlWriter.Create(outputString, settings);resultWriter.WriteStartElement(om.DisplayName + "Response", CustomAdapter1.SERVICENAMESPACE);resultWriter.WriteElementString(om.DisplayName + "Result", resultValue);resultWriter.WriteEndElement();resultWriter.Close();XmlReader replyReader = XmlReader.Create(new StringReader(outputString.ToString()));return Message.CreateMessage(message.Version, om.OutputMessageAction, replyReader);default :return null;}}

このコーディングスタイルですが、かなり面倒な文字列の構築をおこなっていますが、前回記述したようにチャネルの実装はプロトコルそのものの本体を記述することに等しいので、どうしてもこうした実装スタイルになります (ここで .NET 固有のオブジェクトをガンガン使ってしまっては、.NET 以外の LOB システムとの接続に配慮されなくなってしまいます)。

続いて、Inbound の処理を実装します。Inbound シナリオでは、StartListner, StopListner, TryReceive の 3 つを実装します。
まず、Listen の開始時に StartListner が呼ばれます。Listen の際の初期化処理がある場合にはここに処理を記載します。
また inbound シナリオでは、定期的に TryReceive が呼ばれるので、ここで監視対象のオブジェクトなどをチェックし、メッセージ (Message) オブジェクトを作成して処理を呼び出します (ここが処理本体になります)。以降は、この返されたメッセージは、このアダプターを使ったサービス側で処理されます (ここの処理のコードは、このサービスを使用する際に記載しましょう)。
クラス内には StartListner, StopListner, TryReceive の他に WaitForMessage というメソッドがありますが、この処理は外部から明示的に呼び出されない限り (Channel クラスの WaitForMessage メソッドを使用) 呼び出されません。WaitForMessage メソッドは、Message を返さずにメッセージが到着したかどうかだけを bool で返すもので、例えば「監視対象のオブジェクトが到着するまで処理を開始しない」といった場合に、今回のように TryReceive の中で待機するのではなく、明示的に「待機!」を指定する目的で (待機時間を指定して) WaitForMessage メソッドを呼び出し、その後で Receive メソッドを呼ぶといったことが外からできるようになっています。(つまり WaitForMessage メソッドでは処理がブロックされます。もしブロックせずに同じことをしたい場合には、BeginWaitForMessage メソッドを呼び出します。)
今回はこの WaitForMessage メソッドは使いませんので、そのまま (未実装) で結構です。

では、StartListner, StopListner, TryReceive の各メソッドを以下の通り実装してみましょう。

[CustomAdapter1InboundHandler.cs]

public void StartListener(string[] actions, TimeSpan timeout){foreach (string action in actions){if ("Custom1/DoSomeWork".Equals(action)){// 何か初期化処理 (今回はなし !)}}}public void StopListener(TimeSpan timeout){// 何か終了処理 (ここもなし !)}public bool TryReceive(TimeSpan timeout, out System.ServiceModel.Channels.Message message, out IInboundReply reply){// timeout の処理は省略します ...reply = new CustomAdapter1InboundReply();String xmlData = String.Format(@"<DoSomeWork xmlns=""{0}""><param>{1}</param></DoSomeWork>", CustomAdapter1.SERVICENAMESPACE, "testdata");XmlReader reader = XmlReader.Create(new StringReader(xmlData));message = Message.CreateMessage(MessageVersion.Default, "Custom1/DoSomeWork", reader);System.Threading.Thread.Sleep(5000); // ここで 5 秒間待機return true;}

また、この inbound ハンドラクラスの Abort と Reply メソッドの暫定コード(未実装のコードが入っています)を削除しておきましょう。

[CustomAdapter1InboundHandler.cs]

public override void Abort(){// Exception は throw しません !}public override void Reply(System.ServiceModel.Channels.Message message, TimeSpan timeout){// Exception は throw しません !}

以上で、コードの記述は終了です。第 2 回で述べたカスタムチャネルの実装というのは製品ベンダの方などが扱う非常にプロ級!なコーディングなのですが、このカスタムアダプターでは必要な処理を自動で生成してくれますので、こんなに簡単に処理を作成することができます!

では、ビルドをおこない、WCF のフレームワークに登録をおこなっていきます。
まず、このプロジェクトを署名 (プロジェクトのプロパティの [署名] タブで key ファイルを設定) してビルドします。そして、ビルドされた dll を GAC に登録し、

<Windows install path>\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config

に、以下の要素を追加します。(以下で Public Key Token は署名の内容にあわせて変更してください。また、BizTalk Adapter Pack などを入れている場合には既に SAP 用、Siebel 用などの設定が入っているはずですので、必要な要素のみを追加するようにしてください。)

<system.serviceModel>  <client><endpoint binding="myCustomBinding1" contract="IMetadataExchange" name="myscheme" />  </client>  <extensions><bindingElementExtensions>  <add name="myCustomAdapter1" type="myadapter.CustomAdapter1BindingElementExtensionElement,CustomAdapter1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2f3f25ee448858df" /></bindingElementExtensions><bindingExtensions>  <add name="myCustomBinding1" type="myadapter.CustomAdapter1BindingCollectionElement,CustomAdapter1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2f3f25ee448858df" /></bindingExtensions>  </extensions></system.serviceModel>

以上で、登録もすべて完了しました!

次回では、まず基本的な動きを理解してもらうため、作成したこのアダプターを WCF から呼び出してその動きを確認してみましょう。(さらに次の回では、アダプターを BizTalk から使用してみましょう!といっても、この inbound 処理ではちょっと無意味なので、BizTalk Adapter Pack に入っている Oracle 用のアダプターなどを使用してみたいと思っています、、、)

尚、先ほど少しむずかしいと書いた MSDN のチュートリアルの URL を掲載しましたが、そちらのチュートリアルを見て頂くと、ここで記載したヘボいサンプルと違って、以下のような処理も学習することができるようになっています。

  • FileSystemWatcher クラスを使用した inbound 処理におけるファイルのウォッチ (より実用的な inbound 処理の実装例が見れます)
  • アダプター参照の設定画面での検索機能 (Search) の実装
  • 認証処理 (Security) の組み込み (データベース接続文字列の指定、など)
  • ユーザ定義型や配列データの送受信
  • 処理のトレース出力

そして、この MSDN のサンプルの完成プロジェクトは、WCF LOB Adapter SDK のインストールディレクトリの以下に置かれています。

%Program Files%\WCF LOB Adapter\DocumentsSamples

ここで作成したサンプルプロジェクト -> Download

 

Categories: Uncategorized

Tagged as:

8 replies»

Leave a Reply