Service Tutorial 1 - サービスを作る
マイクロソフトRobotics Studioを用いたアプリケーションを記述することは、一組のサービスの間で入出力を組織化することの単純な問題です。サービスはソフトウェアまたはハードウェアにインターフェースすることを意味していて、特定の機能を実行するプロセスの間で情報交換することができます。
Microsoft Robotics Studioがインストールされたフォルダの下の以下の場所にこの指導用のプロジェクトファイルがあります。
Samples\ServiceTutorials\Tutorial1\CSharp
Step 1: サービスを作る
新しいサービスを作ることから始めましょう。
スタートメニューを開き、Microsoft Robotics StudioのところのCommand Promptを選び実行します。
Samplesディレクトリに移り、初めのサービスを作るために下のサンプルのような引数をもったDssNewServiceツールを実行させます。そして、ServiceTutorial1ディレクトリに移動します。この手順は、あなたが開始するのを助けるために、自動的にテンプレートを作成します。
cd Samples
dssnewservice /namespace:Robotics /service:ServiceTutorial1
cd ServiceTutorial1
この時点で、ServiceTutorial1.slnという名前のVisual Studio SolutionがServiceTutorial1ディレクトリに作られています。これをダブルクリックしてVisual Studioを開始してください。あるいは、今開いているコマンドプロンプトで以下のコマンドを投入します。この時、バージョンによってはプロジェクトの変換が行われます。
msbuild ServiceTutorial1.sln
Step 2: サービスを開始する
Microsoft Robotics Studioのディレクトリに戻ります。
cd ..\..
プロジェクトを作った時に作られたbinディレクトリの中に次のファイルがあることを確認してください。
.
.
.
ServiceTutorial1.Y2007.M07.dll
ServiceTutorial1.Y2007.M07.pdb
ServiceTutorial1.Y2007.M07.Proxy.dll
ServiceTutorial1.Y2007.M07.Proxy.pdb
ServiceTutorial1.Y2007.M07.proxy.xml
ServiceTutorial1.Y2007.M07.transform.dll
ServiceTutorial1.Y2007.M07.transform.pdb
参考
デフォールトでは、DssNewService生成されたアセンブリ名に.Y2007.M07現在の年と月を付け加えます。現在の日付に依存しますから、作られたServiceTutorial1ファイルのビルド名は上記のサンプルとは異なるでしょう。Robotics StudioにインストールされているServiceTutorial1はさらに上記とは異なっているでしょう。
サービスを実行するために、DSSホスティングアプリケーションDssHost.exeを走らせることによって最初にDSSノードを実行させなければなりません。DssHostの何れかを走らせるかを特定するには三つの方法があります。
- マニフェスト、コマンドラインのフラグ/manifestを用いる
- アセンブリ名、コマンドラインのフラグ/dllを用いる
- contract、コマンドラインのフラグ/contractを用いる
manifestファイルはサービスを開始するのに必要な情報を含んでいるXMLファイルです。DssNewServiceはDssHostがサービスによって開始するために必要な情報を含んでいるServiceTutorial1.manifest.xmlと呼ばれるファイルを自動的に作ります。
次のコマンドですでに作られているマニフェストを用いてDssHostを開始してみましょう。
dsshost /port:50000 /manifest:"samples\ServiceTutorial1\ServiceTutorial1.manifest.xml"
参考
インストールされている指導用プロジェクトに対するマニフェストのファイルはsamples\Configフォルダーに格納されています。samples\ServiceTutorials\ServiceTutorial1フォルダーの中にあるServiceTutorial1プロジェクトを用いているのならば、samples\Configから正しいマニフェストでDssHostを実行させなければなりません。
bin\dsshost /port:50000 /manifest:"samples\Config\ServiceTutorial1.manifest.xml"
しかしながら、インストールされた指導用プロジェクトは各々の指導に対する完全なコードを含んでいます。ですから、この時点でそれを実行することは全ての段階終了した時のサービスのふるまいをします。
指定したマニフェストに対するサービスのロード出力は次のようになります。
.
.
.
* Starting manifest load: .../ServiceTutorial1.manifest.xml
* Service uri: ...[http://localhost:50000/servicetutorial1]
* Manifest load complete ...[http://localhost:50000/manifestloaderclient]
ウェブブラウザーを開き、次のアドレスにアクセスしてください。
http://localhost:50000/servicetutorial1
SOAPのエンベロープ(封筒)の中に封入されたServiceTutorial1Stateがブラウザのウィンドウに現れます。ServiceTutorial1Stateは新しく作られたサービスのXML直列化(表現)です。

ブラウザでのhttp://localhost:50000/servicetutorial1はSOAPエンベロープとしてサービスの状態を示します。
参考
SOAP(ソープ) |
XMLとHTTPなどをベースとした、他のコンピュータにあるデータやサービスを呼び出すためのプロトコル(通信規約)。Microsoft社やUserLand Software社、Developmentor社が中心となって開発された。
SOAPによる通信では、XML文書にエンベロープ(封筒)と呼ばれる付帯情報が付いたメッセージを、HTTPなどのプロトコルで交換する。サービスを利用するクライアントと、サービスを提供するサーバの双方がSOAPの生成・解釈エンジンを持つことで、異なる環境間でのオブジェクト呼び出しを可能にしている。
SOAP 1.1では、実際にデータの送受信に使う下位プロトコルは、すでに広く普及しているHTTPやSMTP、FTPなどから選択できるようになっており、企業間で利用する場合でもファイアウォールなどを安全に通過することができる(SOAP 1.0ではHTTPのみ)。 … 続きを読む
現在、WWW関連技術の標準化を行なうW3Cによって標準の策定が行なわれており、IBM社やLotus社など、大手ソフトウェアメーカーも自社製品での対応を表明している。
なお、SOAPメッセージの生成エンジンは「SOAPプロキシ」、解釈エンジンは「SOAPリスナ」と呼ばれることもある。
SOAPによって外部から利用可能な、部品化されたWebベースのアプリケーションソフトは「Webサービス」と呼ばれる。インターネット上で各社が提供しているWebサービスを集め、誰でも検索・照会できるようにするWebサービスを「UDDI」という。
|
Step 3: HTTP GETをサポートする
ウェブブラウザに対して状態を送るとき、そのサービスはSOAPエンぺロープを送るのを任意に避けることができ、次に示すように状態のXML直列化を代わりに送ります。
ServiceTutorial1Types.csファイルはそのサービスのcontractを定義します。それは、このサービスに対するcontract識別子(identifier)、状態、オペレーション・ポート、オペレーション。メッセージ、request/response型を含んでいます。指導書を進めていくとサービスのコンポーネントについてさらに学ぶことになります。
ServiceTutorial1Types.csのファイルにおいて、次の修正を行ってください。
-
最初に、名前空間Microsoft.Dss.Core.DsspHttpを含めるためにファイルの先頭にusing命令を用いて付け加えます。
using Microsoft.Dss.Core.DsspHttp;
- 次に、ServiceTutorial1Stateクラスにプロパティを追加します。これは直列化されたデータをより分かりやすく見ることができるようにします。 Service Tutorial 6 (C#) - Retrieving State and Displaying it Using an XML Transformで、サービス間の情報を運ぶためにServiceTutorial1Stateクラスを使うことになります。
private string _member = "This is my State!";
[DataMember]
public string Member
{
get { return _member; }
set { _member = value; }
}
DataContract属性はServiceTutorial1StateクラスがXML直列化可能であることを示しています。DataContract属性を伴った型の中では、個々のプロパティとフィールドをDataMember属性を用いたXML直列化可能として明示的に記さなければいけません。この属性で宣言されているパブリックのプロパティとフィールドのみが直列化されるでしょう。同様に、直列化するプロパティのメンバーのために、setとgetメソッドが両方実装される必要があります。
参考
属性は型、フィールド、メソッド、プロパティのようなプログラミング要素をキーワードのような注釈を付け加えることのできる.NETの特徴です。.NETの属性についてさらに学ぶためには.NET Framework Developer's GuideのAttributes Overviewを見てください。
- 同じファイルのずっと下のほうに行き、サービスのポートによってサポートされているメッセージのリストにHttpGetメッセージを追加します。ポートはサービス間でメッセージが通信されるメカニズムです。PortSetは正にポートのコレクションです。
[ServicePort]
public class ServiceTutorial1Operations : PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, HttpGet>
{
}
-
ServiceTutorial1.csファイルで、HttpGetメッセージに対するサポートを追加します。
ServiceTutorial1.csはそのサービスの振る舞いを定義します。
再び、DsspHttpに対するusing文を追加します。
using Microsoft.Dss.Core.DsspHttp;
-
ServiceTutorial1クラスの中で、HttpGetメッセージに対するメッセージハンドラーを追加します。
/// <summary>
/// Http Get Handler
/// </summary>
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator<ITask> HttpGetHandler(HttpGet httpGet)
{
httpGet.ResponsePort.Post(new HttpResponseType(_state));
yield break;
}
このハンドラーは、HttpResponseTypeメッセージをHttpGetメッセージのresponseポートに送ります。DSSノード中のHTTP基盤は与えられた状態をXMLに直列化し、HTTPリクエストにresponseの本体として設定します。
上級
HttpResponseTypeコンストラクタはここで用いたのとは違って複数の他のオーバーロードを持っています。これらのオーバーロードはウェブクライアントが直列化されたXMLを変換することに用いられるXSLTファイルのパスを指定することをサービスautherに許します。
-
ビルドしサービスを実行してください、そして実行中にウェブブラウザでhttp://localhost:50000/servicetutorial1にアクセスしてください。次のように表示されます。

ブラウザでサービスの状態はServiceTutorial1Stateとそのメンバーで表示されます。
Step 4: コントロールパネルを用いる
サービスを開始するために、Microsoft Robotics Studioのコントロールパネルを用いることもできます。そしてこれはそれ自身DSSサービスです。これを試すには、最初にコマンドプロンプト内でCTRL+Cを押すことによって現在のDSSノードを終了させます。そして、マニフェスト指定はしないで再びDssHostを実行します。
dsshost /port:50000
ブラウザでhttp://localhost:50000にアクセスします。ページがロードされたときに、左のナビゲーション・ペインの中でControl Panelをクリックしてください。現在のノードによって認識されているサービスの表がブラウザ上に表示されれます。

表の各行はサービスの名前から始まっています。その次がサービスを走らせることのできるマニフェストのリストがあるドロップダウンリストがきます。現在走っているサービスのインスタンスがあれば、サービスのDescriptionの下のところにインスタンスに対するURLがあります。走っているサービスの各々のインスタンスはURLの右側にサービスをDropさせるボタンを同様に含んでいます。このボタンをクリックするとDropメッセージをサービスに送ります。このメッセージはサービスを停止させます。
そのページを下にスクロールしてServiceTutorial1に対する項目を見つけてください。あるいは、Searchボックスの中にサービスの名前(servicetutorial1)を入れてください。

コントロールパネル上のservicetutorial1項目
多分、二つの結果になります。それらの一つはMicrosoft Robotics Studioのインストール由来の完成版のプロジェクトのService Tutorial 1です。ドロップダウンリストにあるマニフェストの位置を見ることによってこれらを区別することができますが、このサービスを走らせるに際してマニフェストを選択する必要はなく、<<Start without manifest>>を選択することによって直接アセンブリ(dllファイル)をロードすることによってできます。マニフェストにリストされているパートナー・サービスのグループと一緒にサービスを走らせる必要がある場合には、このようにはしないで、マニフェストを走らせる必要があります。
Createボタンをクリックすることによってサービスを走らせてください。
左のナビゲーション・ペインからService Directoryを選びます。走っているサービスのリストの中に/servicetutorial1を確認できます。/controlpanelは実際にDSSランタイムの異なったコンポーネントで、DSS環境が初期化されたときにデフォールトに開始されたものを含んで、他のサービスが走っていることが分かります。そのリンクをクリックすること、サービスのURLを直接見ることによって各サービスの状態を調べることができます。
参考
コントロールパネルのサービスはノードが開始されたときにいつも開始されます。そのノードをリスタートすることなしにサービスを開始し停止させることができます。しかしながら、そのノードはサービスアセンブリをロードするため、サービスをリビルドしたときにはそのノードを停止する必要があります。
Step 5: サービスを停止する
サービスが走っている間にブラウザを開きhttp://localhost:50000/controlpanelにアクセスしてください。
参考
そのノードで走っているサービスの最新の表現を得るためにコントロールパネルのサービスをリフレッシュする必要があります。
前の部分で述べたようにservicetutorial1の項目を探してください。

ドロップボタンを持ったコントロールパネルの中のServiceTutorial1サービスのインスタンス
サービスの状態を調べるためにURLをクリックしてください、Createボタンをクリックすることによって新しいインスタンスを作ってください、さもなければDropボタンをクリックすることによって走っているサービスの継続繰り返しを停止します。
Step 6: Replaceをサポートする
置換メッセージはサービスの現在の状態を置換するのに用いられます。置換メッセージが送られたときに、そのサービスの全状態は置換メッセージの本体で指定された状態オブジェクトで置換されます。そのサービスのライフタイムの間中、新しい状態でサービスを初期化させたり、以前に保存した状態をサービスにリストアすることができます。
置換をサポートするサービスのために、ServiceTutorial1Types.csにおいてReplace型を定義します。
public class Replace : Replace<ServiceTutorial1State, PortSet<DefaultReplaceResponseType, Fault>>
{
}
そして、そのPortsetにReplaceを付け加えます。
/// <summary>
/// ServiceTutorial1 Main Operations Port
/// </summary>
[ServicePort]
public class ServiceTutorial1Operations : PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, HttpGet, Replace>
{
}
ServiceTutorial1.csで、置換ハンドラーを追加します。
/// <summary>
/// Replace Handler
/// </summary>
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator<ITask> ReplaceHandler(Replace replace)
{
_state = replace.Body;
replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
yield break;
}
上記のコードで、ServiceTutorial1Stateの型である置換メッセージがこのサービスの
_stateに割り当てられています。そして、DefaultReplaceResponseTypeの型のresponseの成功が置換メッセージとしてResponsePortにポストされます。この信号は状態はうまく置換されたと送り主に戻されます。
サービス間での値の交換をするために後でReplace操作を用いるでしょう。
Appendix A: コード
ServiceTutorial1Types.cs
ServiceTutorial1Types.cs ファイルはサービスのcontractを定義しています。contractはユニークな文字列によって識別され、その文字列は.NET CLR名前空間とそのサービスがサポートしているメッセージのセットに関連付けられています。
これは、このファイルで用いられている名前空間を確立します。
using Microsoft.Ccr.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.Core.DsspHttp;
using Microsoft.Dss.ServiceModel.Dssp;
using System;
using System.Collections.Generic;
using W3C.Soap;
using servicetutorial1 = RoboticsServiceTutorial1;
Contract Class
///
/// ServiceTutorial1 Contract class
///
public sealed class Contract
{
///
/// The Dss Service contract
///
public const String Identifier = "http://schemas.tempuri.org/2006/06/servicetutorial1.html";
}
このContractクラスはIdentifierというユニークな文字列を定義します。すなわちこのcontractと一般的にはサービスを識別するのに用いられます。ユニークな名前を指定するためにURI(Unique Resource Identifier)で用いられているXML文書使用規則に従います。使われているデフォルトのメカニズムは、URIをホスト名(DssNewServiceへのパラメータとして提供されている)、パス(この例では空)、年、月、サービスの名前から作ることです。Namespaceがサービスauthorが数レベルの支配をするホスト名とパスから(例えば、http://spaces.live.comのアカウントのアドレスが用いることができる)作られるならば、日付要素とサービス名の構成は、そのサービスに関する少量の情報を含む利点とともに、ユーザーにユニークさの理にかなった予想を与えます。URIが実際にページを持つ必要がないのと同様に、サービスのauthorは一致するページを作ってはいけない理由もありません。
上級
もし不明瞭な識別子が必要ならば、それは可能ですがお勧めしません。"urn:uuid:4de060f3-f665-11da-95e7-00e08161165f"の形式のGUID(GuidGen.exeツールを用いて発生させる)を用います。
ServiceTutorial1State Class
///
/// The ServiceTutorial1 State
///
[DataContract]
public class ServiceTutorial1State
{
private string _member = "This is my State!";
[DataMember]
public string Member
{
get { return _member; }
set { _member = value; }
}
}
ServiceTutorial1Stateは一つのpublicプロパティMemberを持っていて、メンバーは直列化されるべきである(上で述べたように)と示すDataMember属性とともにそれ自身を宣言しています。デフォールトでは、nullであるプロパティやフィールドは直列化されません。
ServiceTutorial1Operations Class
ServiceTutorial1Operations Class
///
/// ServiceTutorial1 Main Operations Port
///
[ServicePort]
public class ServiceTutorial1Operations : PortSet
{
}
このクラスはそのサービスがサポートするpublicメッセージを定義します。
メッセージ | 解説 |
DsspDefaultLookup |
すべてのサービスはLookup<TBody,TResponse>から派生したメッセージをサポートしなければいけない。基盤はDsspDefaultLookupを定義しています。DsspServiceBaseクラス(実装クラスはどのサービスから派生しているか)Lookupに対するデフォールトのメッセージハンドラーを定義しています。それで実際には、DsspDefaultLookupをoperation PortSetに加えることは、サービスauthorが行う必要があるすべてです。サービスはそれ自身の基本的な情報を伴ったLookupメッセージに応答します。デフォールトの実装では、DsspServiceBaseからのServiceInfoプロパティを返します。
|
DsspDefaultDrop |
Dropメッセージがサービスに送られる時にサービスが停止します。サービスはこれを実装する必要はありません。デフォールトの実装はDsspServiceBaseで用意されています。一般的には、DropをサポートするためにPortSetのoperationにDsspDefaultDropを追加できます。
|
Get |
サービスは、現在の状態でGetメッセージに応答しなければいけません。サービスはGetメッセージに応答する際に、その状態を修正すべきではありません。
|
HttpGet |
上で議論したように、これはGetオペレーションと同等ですが、サービスにウェブブラウザのクライアントで直接通信することのできるようにします。
|
Replace |
サービスは置換メッセージの本体で全ての状態を置換すべきです。置換メッセージは付加的で、全てのサービスに必須でもなく、このメッセージを実装しなくともよい。
|
GetとReplaceは適切なデフォールトの宣言を持たない二つのメッセージで、このサービスによってサポートされています。
- Getの場合、これはGetメッセージへの主要な応答がサービスの状態でなければならないからです。このサービスの状態型、ServiceTutorial1Stateはこのサービスに特有です。
- Replaceに対しては、メッセージの本体(Body)はこのサービスのServiceTutorial1Stateです。
/// <summary>
/// ServiceTutorial1 Get Operation
/// </summary>
public class Get : Get<GetRequestType, PortSet<ServiceTutorial1State, Fault>>
{
/// <summary>
/// ServiceTutorial1 Get Operation
/// </summary>
public Get()
{
}
/// <summary>
/// ServiceTutorial1 Get Operation
/// </summary>
public Get(Microsoft.Dss.ServiceModel.Dssp.GetRequestType body) :
base(body)
{
}
/// <summary>
/// ServiceTutorial1 Get Operation
/// </summary>
public Get(Microsoft.Dss.ServiceModel.Dssp.GetRequestType body,
Microsoft.Ccr.Core.PortSet<ServiceTutorial1State,W3C.Soap.Fault> responsePort) :
base(body, responsePort)
{
}
}
public class Replace : Replace<ServiceTutorial1State, PortSet<DefaultReplaceResponseType, Fault>>
{
}
ServiceTutorial1.cs
ServiceTutorial1.csファイルはそのサービスの実装クラスを含んでいます。
using Microsoft.Ccr.Core;
using Microsoft.Dss.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.Core.DsspHttp;
using Microsoft.Dss.ServiceModel.Dssp;
using Microsoft.Dss.ServiceModel.DsspServiceBase;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
これらはこのクラスで用いられる名前空間です。
サービスを実装するクラス
この部分のコードはServiceTutorial1クラスを宣言しており、DsspServiceBaseから派生しています。すべてのサービスの実装はDsspServiceBaseから派生しています。
/// <summary>
/// Implementation class for ServiceTutorial1
/// </summary>
[DisplayName("Service Tutorial 1")]
[Description("Service Tutorial 1 Service")]
[Contract(Contract.Identifier)]
public class ServiceTutorial1Service : DsspServiceBase
{
Contract属性はこのクラスと以前の部分で説明したContract Identifierとの間の直接の関連性を宣言しています。DisplayNameとDescriptionはサービスを説明する属性です。DisplayNameはタイトルに似た短い説明です。Descriptionはより詳細な説明です。
ServiceTutorial1Stateサービスの状態を保持しています。すべてのサービスは他のサービスからその状態を読んだり、修正したりすることを許す、それのオペレーションポートを通してメッセージが与えられます。
/// <summary>
/// Service State
/// </summary>
private ServiceTutorial1State _state = new ServiceTutorial1State();
このサンプルでは、サービスは次の項目のみをサポートしています。
- 全ての状態を読み込む(GetとHttpGet)
- 全ての状態を修正する(Replaace)
ServicePort属性は_mainPortフィールドがこのサービスの主要なサービスポートであることを宣言しています。
/// <summary>
/// Main Port
/// </summary>
[ServicePort("/servicetutorial1", AllowMultipleInstances=false)]
private ServiceTutorial1Operations _mainPort = new ServiceTutorial1Operations();
これは同様に、このサービスをアドレスするのに用いられるデフォールトのパスを指定します(/servicetutorial1)。同様にこのサービスの一つだけのインスタンスが一時に走ることを規定しています。もしAllowMultipleInstances = trueが指定された場合、そのサービスのインスタンスが作られたときにユニークな接尾辞がパスに追加されます。
Initialization
サービスが作られたときに、パートナーサービスがService Tutorial 4 (C#) - Supporting Subscriptions and Service Tutorial 5 (C#) - Subscribing. で議論されるように作られる間に作成過程には二つの段階があります。
///
/// Default Service Constructor
/// </summary>
public ServiceTutorial1Service(DsspServiceCreationPort creationPort) :
base(creationPort)
{
}
このコンストラクタは作成過程の最初の部分で用いられ、サービスが正しく作られたことを示す形式を持たなければいけません。
Startメソッドは二つの作成過程の最後のアクションで呼ばれます。
/// <summary>
/// Service Start
/// </summary>
protected override void Start()
{
base.Start();
// Add service specific initialization here.
}
base.Start()過程の間、そのサービスはそのサービスによって手動でもできる三つのことを行います。
- ActivateDsspOperationHandlersを呼ぶことは、メインサービスポートでサポートされている各々のメッセージにハンドラーを付加すようにDsspServiceBaseにさせます。
ハンドラーをどのように宣言するかは下を見てください。
- DirectoryInsertを呼ぶことは、このサービスのためのサービス記録(record)がディレクトリに挿入されるようになります。ディレクトリはそれ自身サービスで、このメソッドはサービスに挿入メッセージを送ります。
DssHostが走っているときに、http://localhost:50000/directoryにブラウザで見ることによってディレクトリサービスを検証することができます。
- LogInfoはInsertメッセージを/console/outputサービス(http://localhost:50000/console/output)に送ります。
メッセージの種類はConsoleで、これはメッセージをコマンドコンソールに表示させます。
そのサービスのURIは出力に自動的に付加されます。
次のコードは上で述べたbase.Start()でなされる三つのタスクを要約しています。しかしながら、大部分の場合、サービスを開始を手動で初期化する代わりに、base.Start()を用いることを推奨します。
// Listen on the main port for requests and call the appropriate handler.
ActivateDsspOperationHandlers();
// Publish the service to the local node Directory
DirectoryInsert();
// display HTTP service Uri
LogInfo(LogGroups.Console, "Service uri: ");
Message Handlers
以下は、Getメッセージのためのハンドラーです。このハンドラーは、単にメッセージの反応(response)ポートに、サービスの状態をポストします。
/// <summary>
/// Get Handler
/// </summary>
/// <param name="get"></param>
/// <returns></returns>
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public virtual IEnumerator GetHandler(Get get)
{
get.ResponsePort.Post(_state);
yield break;
}
ServiceHandler属性は、メインサービスポートに送られるメッセージのためのメッセージハンドラーとして、メンバー機能(functions)を識別するためにbase.Start()メソッドの中で呼ばれているActivateDsspOperationHandlersメソッドによって用いられます。それ自身はServicePort属性と識別されます。ServiceHandlerBehavior.Concurrentはメッセージハンドラーがサービスの状態にアクセスするためにRead-Onlyのみが必要であると指定するのに用いられます。同時並行に走るために状態を修正しないようにメッセージハンドラーにさせます。
重要
サービスの状態にwrite accessする必要があるメッセージハンドラーはServiceHandlerBehavior.Exclusiveを用いるべきです。これは、一つのハンドラーだけが一度に状態を修正することができることを明確にします。
大抵、メッセージハンドラーは署名(Microsoft.Ccr.Core.IteratorHandler)を持っています。
public delegate IEnumerator IteratorHandler(T parameter);
イテレータ(.NET 2.0 での新しい特徴)を用いることはスレッドをブロックすることなく非同期アクションの列を含むようにハンドラーにさせます。これはService Tutorial 3 (C#) - Persisting Stateで実例説明をしています。
HttpGetHandlerはこの章で以前に議論しました。(Step 3: HTTP GETをサポートする)
/// <summary>
/// Http Get Handler
/// </summary>
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator<ITask> HttpGetHandler(HttpGet httpGet)
{
httpGet.ResponsePort.Post(new HttpResponseType(_state));
yield break;
}
以下は、置換メッセージに対するハンドラーです。このハンドラーはServiceHandlerBehavior.Exclusiveで宣言されることに注意してください。それが状態を修正することを示しています。一つだけのExclusiveハンドラーだけが一時に実行でき、ConcurrentハンドラーはExclusiveハンドラーが走っている間、実行できません。
/// <summary>
/// Replace Handler
/// </summary>
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator<ITask> ReplaceHandler(Replace replace)
{
_state = replace.Body;
replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
yield break;
}
参考:属性について
属性には、次の特徴があります。
- 属性は、プログラムにメタデータを追加します。メタデータは、コンパイラ命令やデータの説明など、プログラムに埋め込まれた情報のことです。
- プログラムでは、リフレクションを使用して固有のメタデータを調べることができます。詳細については、「リフレクションによる属性へのアクセス (C# プログラミング ガイド)」を参照してください。
- 属性は一般に、COM とやり取りをするときに使用されます。
リフレクションによる属性へのアクセス
カスタム属性を定義してソース コードで使用できたとしても、その情報を取得し、それに基づいて処理を実行する手段がなくては、価値のある機能とはいえません。C# では、リフレクション システムを使用して、カスタム属性で定義された情報を取得できます。ここで重要となるメソッドは GetCustomAttributes です。このメソッドは、ソース コードの属性に対応するオブジェクトの配列を実行時に返します。このメソッドには、オーバーロードされたバージョンがいくつかあります。
[Author("H. Ackerman", version = 1.1)]
class SampleClass
上の属性宣言は、概念的には下の式と同等です。
ただし、SampleClass に対して属性を問い合わせるまで、コードは実行されません。SampleClass に対して GetCustomAttributes を呼び出すと、Author オブジェクトが生成されて、上記のように初期化されます。クラスに他の属性がある場合は、他の属性オブジェクトが同じように作成されます。作成後、GetCustomAttributes は、配列内の Author オブジェクトとその他の属性オブジェクトを返します。この配列に対して反復処理を行うことで、各配列要素の種類に基づいて適用された属性を特定し、属性オブジェクトから情報を取得できます。
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute
]
public class Author : System.Attribute
{
string name;
public double version;
public Author(string name)
{
this.name = name;
version = 1.0; // Default value
}
public string GetName()
{
return name;
}
}
[Author("H. Ackerman")]
private class FirstClass
{
// ...
}
// No Author attribute
private class SecondClass
{
// ...
}
[Author("H. Ackerman"), Author("M. Knott", version = 2.0)]
private class ThirdClass
{
// ...
}
class TestAuthorAttribute
{
static void Main()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
private static void PrintAuthorInfo(System.Type t)
{
System.Console.WriteLine("Author information for {0}", t);
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // reflection
foreach (System.Attribute attr in attrs)
{
if (attr is Author)
{
Author a = (Author)attr;
System.Console.WriteLine(" {0}, version {1:f}", a.GetName(), a.version);
}
}
}
}
Arbiter.Receive
http://msdn2.microsoft.com/en-us/library/microsoft.ccr.core.arbiter.receive.aspx
Parameters
persist
Boolean
True if receiver can stay registered with the port after the first message
port
Port<(Of )>
Port instance to register receiver with
handler
Handler<(Of )>
User delegate that executes on message arrival at the port
Return Value
Instance of Receiver arbiter
-p:50000 -t:50001 -m:"Samples\Config\RoboticsTutorial3.manifest.xml" -m:"samples\config\Parallax.MotorIrBumper.manifest.xml"
-p:50000 -t:50001 -m:"Samples\Config\RoboticsTutorial2.manifest.xml" -m:"samples\config\Parallax.MotorIrBumper.manifest.xml" -m:"samples\config\Parallax.BoeBot.Drive.manifest.xml"
「yield return」はハンドラーにタスクが完了するまで待ち状態に入ります。
参考:スタブについて
--------------------------------------------------------
スタブ 【stub】
あるプログラムが他のプログラムを呼び出す際に仲介となるプログラム。スタブの仲介を受けることで、プロセス間通信やクライアント・サーバ間でのオブジェクト呼び出しなどを、通常の手続き呼び出しと同様に扱うことができるようになる。
アプリケーションがOSの機能であるシステムコールなどを利用する場合、直接目的のシステムコールを呼び出すのではなく、スタブを呼び出してシステムコールの呼び出しはスタブに任せることが多い。
また、分散環境のプログラムがサーバ上のオブジェクトを呼び出す場合に仲介するプログラムも、同じくスタブと呼ばれる。こちらはC言語やC++、Javaなどの環境で利用でき、プログラム本体から独立してインターフェースが提供されることで、プログラム自体では通信を意識しなくともサーバ上のオブジェクトが呼び出せるようになっている。
--------------------------------------------------------------------------
Partner属性では次のように記述する
Partnerに対するユニークな名前
Partnerとして用いるサービスのContract
creationポリシー
サービスが開始されると、このPartnerの新しいインスタンスが作られます。
もしこれに失敗するとサービスも失敗し、Startメソッドも呼ばれません。
Partner |
[Partner("SubMgr", Contract = submgr.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.CreateAlways)]
private submgr.SubscriptionManagerPort _submgrPort = new submgr.SubscriptionManagerPort();
|
Partner属性は_submgrPortフィールドに適用されています。この_submgrPortフィールドはSubscriptionManagerのメインサービスポートとして宣言されています。このポートへポストされた任意のメッセージはこのサービスに対して作られたSubscriptionManagerに転送されます。
pwmRightはBoeBotControl.csのpublic void SetSpeed(double? left, double? right)で設定されている
BasicStamp2.csの中からは_control.SetSpeed(_state.MotorSpeed.LeftSpeed, _state.MotorSpeed.RightSpeed);のように変更している。
----------------------------------------------------------
RoboticsTutorial2.csに次の行を追加
using boe = Microsoft.Robotics.Services.BasicStamp2.Proxy;
しかし、これは何だか分からないので、知らせるために「参照設定」でBASICStamp2.Y2006.M06.proxyを追加する。
パートナーとして次を追加する
[Partner("BASICStamp2", Contract = boe.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate, Optional = false)]
private boe.BasicStamp2Operations _boebotPort = new boe.BasicStamp2Operations();
Service Tutorial 2 - 状態を更新する
Step 1: サービスに新しいメッセージを追加する
サービスはカウンターをその状態に格納する必要があり、メッセージはカウンターがインクリメントされるように定義される必要があります。これらのすべての変更はServiceTutorial2Types.csで起こります。
これから行う最初の変更は、整数のプロパティTicksをサービスの状態型として追加することです。
private int _ticks;
[DataMember]
public int Ticks
{
get { return _ticks; }
set { _ticks = value; }
}
次に、メッセージにTickプロパティをインクリメントするように定義する必要があります。
下のコードの抜粋はIncrementTickメッセージを定義しています。メッセージはDsspOperationから派生されており、三つの要素、action、body、response portを持ちます。下で宣言されているIncrementTickメッセージは、Updateジェネリック・クラスから派生されていて、それは動作動詞にUpdate(特にUpdateRequest)を設定します。本体の型はIncrementTickRequestとして宣言されています。その応答(response)ポートはPortSetとして定義されています。
Update動詞はサービス状態の部分を修正するメッセージに用いられます。この場合、Tickプロパティのみに影響を及ぼすだけです。
本体は、そのサービスが要求している活動メッセージを実行するどんな情報にも用いられています。
この場合、IncrementTickメッセージはTickプロパティを状態が一つだけ増えるようにするため、本体はフィールドを含んでいる必要はありません。コンストラクタはIncrementTickメッセージに対して作ったり本体を設定したするように定義されています。
応答ポートはそのサービスが成功か失敗かを発信者に通信できるように許可します。このメッセージの中で、殆どのUpdateメッセージと同様に、DefaultUpdateResponseTypeは更新が成功したという信号として十分です。IncrementTickメッセージは失敗パスを持たないので、Fault型はサービスにメッセージが送られる際に問題があるときに発信者に知らせるDSS基盤によって用いられます。
ServiceTutorial2Types.csで、次のメッセージとリクエスト型を追加します。
public class IncrementTick : Update<IncrementTickRequest, PortSet<DefaultUpdateResponseType, Fault>>
{
public IncrementTick()
: base(new IncrementTickRequest())
{
}
}
[DataContract]
public class IncrementTickRequest
{
}
IncrementTickメッセージをサービスがサポートしているメッセージのリストに追加します。
IncrementTickメッセージをこのサービスに今すぐ送ったならば、そのメッセージはそのサービスが終了するまでポートに居座り、メモリーリークを引き起こします。これはまだメッセージハンドラーを定義していないためです。
/// <summary>
/// ServiceTutorial2 Main Operations Port
/// </summary>
[ServicePort]
public class ServiceTutorial2Operations : PortSet<DsspDefaultLookup, DsspDefaultDrop, Get, HttpGet, Replace, IncrementTick>
{
}
Step 2: メッセージハンドラーを実装する
次のステップはIncrementTickメッセージに対するハンドラーを実装することです。ServiceTutorial2.csで、ServiceTutorial2クラスにハンドラーを追加します。
このハンドラーはサービスの状態のTickプロパティをインクリメントし、プロパティの変更をログし、発信者(caller)に成功応答を送ります。
Service Tutorial 1 (C#) - Creating a Serviceで説明したように、ServiceHandler属性はこのメソッドがメインサービスポートに送られるメッセージタイプをハンドルすると宣言しています。
この場合、このハンドラーはサービスの状態を修正するため、その状態には排他的アクセスが必要です。従って、そのハンドラーはServiceHandlerBehavior.Exclusiveで宣言されます。
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator<ITask> IncrementTickHandler(IncrementTick incrementTick)
{
_state.Ticks++;
LogInfo("Tick: " + _state.Ticks);
incrementTick.ResponsePort.Post(DefaultUpdateResponseType.Instance);
yield break;
}
サービスはそのサービスの状態の中に新しいフィールド、そのフィールドをインクリメントするようにサービスに知らせるメッセージ、サービスの状態を実際に変更するハンドラーを持ちます。次のステップはその状態を変更させます。
Step 3: メッセージを内部的にポストする
サービスは大体一秒に一回Tickプロパティをインクリメントします。これをするには、ある種のタイマーが必要になります。.NETフレームワークは利用できるタイマー機能を用意しています。しかしながら、CCRはCCRシステムのポートとreceiverの中で直接タイマーを利用できるメカニズムを用意しています。
やらなければならない最初のことは、タイマーが通知する度にメッセージを送るポートを宣言することです。次の行はDateTimeをポストすることのできるポートを宣言しています。_mainPort宣言の後のサービスクラスにこのメンバーフィールドを追加してください。
private Port _timerPort = new Port();
Startメソッドに二行を加えてください。
- 宣言したポートにDateTimeをポストします。デバッグに役立つけれども、サービスの実行にはポストされたDateTimeの値は重要ではありません。
- _timerPortポートに対するハンドラーをアクティブにする。
_timerPortフィールドはメインサービスポートの一部ではないため、ハンドラーメソッドにServiceBehavior宣言を加えることによってハンドラーをアクティブにはできません。Activeメソッドがアクティブさせるタスクのリストに入れます。ここで宣言されているタスクArbiter.Receive(...)は、ポート上の単純なレシーバー(receiver)です。この場合、メッセージがそのポート_timerPortに到着したときに、TimerHandlerメソッドが呼ばれるように指定しています。最初の引数(boolean true)はレシーバーは持続的であることを示し、そのポートへの全てのメッセージはそのハンドラーによって受け取られます。
CCRはActivate()を呼ぶように渡された全てのタスクを管理します。CCRはDSSが作った並列タスクディスパッチ基盤です。
Start()メソッドは次のようになります。
/// <summary>
/// Service Start
/// </summary>
protected override void Start()
{
base.Start();
_timerPort.Post(DateTime.Now);
Activate(Arbiter.Receive(true, _timerPort, TimerHandler));
}
メッセージが_timerPortに到着したときにTimerHandlerが呼ばれます。次の二つを行います。
- メインサービスポートにIncrementTickメッセージをポストします。これはIncrementTickHandlerの実行を引き起こします。
- 1000ミリ秒のタイムアウト間隔でアクティブになります。このレシーバーに対するハンドラーとしてのメソッドを宣言していないので、匿名デリゲート(.NET 2.0の新しい特徴)が用いられます。これは、Arbiter.Receiveを呼ぶ際にインラインでハンドラーを書くことを許します。匿名デリゲートで、DateTimeの値を_timerPortポートにポストします。このレシーバーは持続的ではないことに注意してください。-- 最初の引数がfalseです。TimeoutPort()メソッドによって作られたポートが指定された間隔(この場合1000ミリ秒)が過ぎた後で一つのメッセージを受け取るからです。
Activate()メソッドが呼ばれたときに、このサービスに対するアクティブタスクのリストにArbiter.Receiveを呼ぶことによって定義されるタスクを追加します。Activate呼び出しの中で定義されているオペレーションは現在のスレッドには独立にスケジュールされるでしょう。この場合、TimerHandlerメソッドは、Activateへの呼び出しの後に直接戻ります。タイマーが通知するのは待ちません。
TimeoutPortを通知する(この場合、1000ミリ秒後)呼び出しの中でタイマーの間隔を指定したときに、Arbiter.Receive()メソッドは一度限りのレシーバーを定義します。Arbiter.Receive()の第三引数として指定されている匿名デリゲートを引数として渡します。
void TimerHandler(DateTime signal)
{
_mainPort.Post(new IncrementTick());
Activate(
Arbiter.Receive(false, TimeoutPort(1000),
delegate(DateTime time)
{
_timerPort.Post(time);
}
)
);
}
Step 4: See the Service in Action
いつもの方法でビルドし、サービスを実行させてください。
ブラウザでhttp://localhost:50000/servicetutorial2にアクセスすると、以下のようなページが見えるはずです。

ブラウザでServiceTutorial2の状態を表示させる。
Tickプロパティに注意してください。ブラウザをリロード(refresh)すると、この数字が時間とともに増えていくのが分かるでしょう。
ここで、ブラウザでhttp://localhost:50000/console/outputにアクセスしてください。ブラウザ上にサービスによってログされたメッセージが表示されます。
Row | Time | Level | Description (mouse-over for details) |
0 | 14:50:55 | ** | Validating Contract Directory Cache |
1 | 14:50:57 | ** | Contract Directory is initialized |
2 | 14:50:57 | ** | Attempting to load manifest: file:///C:/MSRS/samples/config/ServiceTutorial2.manifest.xml |
3 | 14:50:58 | ** | Service uri: http://localhost:50000/servicetutorial2 |
4 | 14:50:57 | ** | Initial manifest loaded successfully. |
5 | 14:50:59 | ** | Tick: 1 |
6 | 14:50:59 | ** | Tick: 2 |
7 | 14:51:00 | ** | Tick: 3 |
8 | 14:51:01 | ** | Tick: 4 |
9 | 14:51:02 | ** | Tick: 5 |
10 | 14:51:03 | ** | Tick: 6 |
11 | 14:51:04 | ** | Tick: 7 |
12 | 14:51:05 | ** | Tick: 8 |
13 | 14:51:06 | ** | Tick: 9 |
14 | 14:51:07 | ** | Tick: 10 |
15 | 14:51:08 | ** | Tick: 11 |
16 | 14:51:09 | ** | Tick: 12 |
17 | 14:51:10 | ** | Tick: 13 |
18 | 14:51:11 | ** | Tick: 14 |
19 | 14:51:12 | ** | Tick: 15 |
20 | 14:51:13 | ** | Tick: 16 |
Clicking on each row will open details about that message:
16 | 14:51:09 | ** | Category | StdOut | Level | Info | Time | 2006-06-08T14:51:09.8709299-07:00 | Subject | Tick: 12 | Source | http://localhost:50000/servicetutorial2 | CodeSite | Boolean MoveNext()() at line:92, fileC:\MSRS \Samples\ServiceTutorials\ServiceTutorial2 \ServiceTutorial2.cs | |
DsspServiceBaseクラスは、Category、Level、Subjectフィールドのログを変更することができるように、色々なオーバーロードを用いて、様々なログのメソッドを定義しています。これらのメソッドは、LogInfo、LogWarning、LogErrorを含んでいます。Time、Source、CodeSiteのエントリーは自動的に投入されます。
Service Tutorial 3 - 状態を維持する
Step 1: ファイルから初期状態を読む
ファイルからサービスの初期状態を読むためには、サービス実装クラスの状態メンバーに宣言を追加し、初期状態ファイルがない場合を扱う必要があります。
ServiceTutorial3.csファイルで、次の変更をしてください。
- サービス状態の宣言にInitialStatePartner属性を付け加えてください。これは状態の初期値に設定するDSSコンストラクタを動作させます。InitialStatePartnerはサービスパートナーの特別な使い方です。(パートナーはService Tutorial 4 (C#) - Supporting Subscriptions and Service Tutorial 6 (C#) - Retrieving State and Displaying it Using an XML Transformでより詳細に説明されます)
- ServiceUriパラメータは初期状態ドキュメントの場所を指定します。これは格納ディレクトリに相対なURIとして表現されます。この場合では、初期状態ドキュメントは、store/ServiceTutorial3.xmlになるはずです。
上級
サービスマニフェストは任意のパートナー宣言の実行時オーバーライドをすることができます。これは、異なった場所からサービスをロードしたり、状態を維持したりすることを許します。
次のように状態宣言を修正してください。
/// <summary>
/// Service State
/// </summary>
[InitialStatePartner(Optional = true, ServiceUri = "ServiceTutorial3.xml")]
private ServiceTutorial3State _state = new ServiceTutorial3State();
初期状態パートナーを宣言するときに、Optional = trueを指定したので、そのサービスは初期状態ドキュメントが見つからない時でさえも開始するでしょう。そのような場合、_stateフィールドはnullになります。Startメソッドの中で、デフォールトの初期化をさせるために、base.Start()を呼び出す直前に、このチェックを加えてください。
if (_state == null)
{
_state = new ServiceTutorial3State();
}
Step 2: 状態を維持する
現在のところ、そのサービスはスタートアップでドキュメントをロードするように試みますが、それを見つけられません。同じ場所に状態を維持する能力を付け加えます。
いつ状態を維持するかという正確な論理は問題のサービスの性質に依存します。このサービスでは、その状態は10チック毎に維持します。状態を維持するために、DsspServiceBaseクラスのSaveStateメソッドを呼び、現在の値を与えます。この呼び出しは、Mountサービス(サービスに対してファイルシステム能力を与える)のReplaceメッセージをポストする非同期プロセスをラップします。状態オブジェクトがMountサービスにポストされた時、そのオブジェクトのクローンが作られます。これは直列化プロセスの破損でサービス状態の遅れた変更を防ぎ、サービスのオペレーションで同時に起こるサービスの状態の修正を許します。
チックカウントをログする行の後に、IncrementTickHandlerメソッドに次のコードを追加してください。
if (_state.Ticks % 10 == 0)
{
LogInfo("Store State");
base.SaveState(_state);
}
10秒以上でサービスを走らせることによって状態を維持させることもできます。DSSノードを停止させ、そのサービスをリストアすることは、最後に保存した状態でサービスを再開することになります。
上で述べたように状態は、store\ServiceTutorial3.xmlファイルに保存されます。暫くサービスを走らせ、そのファイルの内容を見てみましょう。
<?xml version="1.0" encoding="utf-8"?>
<ServiceTutorial3State xmlns="http://schemas.tempuri.org/2006/06/servicetutorial3.html">
<Member>This is my State!</Member>
<Ticks>530</Ticks>
</ServiceTutorial3State>
ブラウザでhttp://localhost:50000/servicetutorial3を開き、比較してみましょう。

ブラウザでServiceTutorial3の状態を見る
ファイルに直列化された状態は、HTTPのために直列化された状態の形と同等です。ここで、サービスを停止させ、リスタートさせましょう。Tickカウンターは最後に保存された状態から再開します。
Step 3: 非同期応答(response)を管理する
上で状態を維持している一方、エラーの可能性を無視しています。サービスを簡単な変更でSaveStateが成功したか失敗したかどうかをチェックするようにすることができます。
タスクを作り、この場合ではChoiceですが、C#のキーワードであるyield returnでタスクを戻ります。
yield returnでタスクから戻ることは、ハンドラーにそのタスクが完了するまで待ち状態に入らせます。これは非同期オペレーションの列を作ることを許します。
SaveStateメソッドはMountサービスにReplaceメッセージをポストします。そのSaveStateメソッドはメッセージをResponseポートに戻します。
Choiceタスクは一つ以上のメッセージ型でPortSetにポストされた最初のメッセージに対してのハンドラーを実行します。この場合、ResponsePortは二つのメッセージ型を定義します。
- DefaultReplaceResponseType - 成功に対して用いる
- Fault - 失敗の状況で用いる
IncrementTickHandlerの中で、base.SaveState(_state)の呼び出しの行を下のコードで置き換えてください。ハンドラーは二つの匿名デリゲートで、各々のメッセージタイプのものです。もし失敗がレポートされたならば、エラーをログします。
yield return Arbiter.Choice(
base.SaveState(_state),
delegate(DefaultReplaceResponseType success) { },
delegate(W3C.Soap.Fault fault)
{
LogError(null, "Unable to store state", fault);
}
);
この変更の効果を知るために、読み取り専用にするために、store\ServiceTutorial3.xmlのファイルのパーミッションを変更してみましょう。これは、SaveStateメッセージを失敗させ、エラーメッセージがログされます。ConsoleOutputを見てみましょう。
Row | Time | Level | Description (mouse-over for details) |
0 | 14:50:55 | ** | Validating Contract Directory Cache |
1 | 14:50:57 | ** | Contract Directory is initialized |
2 | 14:50:57 | ** | Attempting to load manifest: file:///C:/MSRS/samples/config/ServiceTutorial3.manifest.xml |
3 | 14:50:58 | ** | Service uri: http://localhost:50000/servicetutorial3 |
4 | 14:50:57 | ** | Initial manifest loaded successfully. |
5 | 14:50:59 | ** | Tick: 371 |
6 | 14:50:59 | ** | Tick: 372 |
7 | 14:51:00 | ** | Tick: 373 |
8 | 14:51:01 | ** | Tick: 374 |
9 | 14:51:02 | ** | Tick: 375 |
10 | 14:51:03 | ** | Tick: 376 |
11 | 14:51:04 | ** | Tick: 377 |
12 | 14:51:05 | ** | Tick: 378 |
13 | 14:51:06 | ** | Tick: 379 |
14 | 14:51:07 | ** | Tick: 380 |
15 | 14:51:07 | ** | Store State |
16 | 14:51:07 | *** | Unable to store state: http://www.w3.org/2003/05/soap-envelope:Receiver -> http://schemas.microsoft.com/xw/2004/10/dssp.html:OperationFailed |
17 | 14:51:08 | ** | Tick: 381 |
18 | 14:51:09 | ** | Tick: 382 |
19 | 14:51:10 | ** | Tick: 383 |
20 | 14:51:11 | ** | Tick: 384 |
21 | 14:51:12 | ** | Tick: 385 |
22 | 14:51:13 | ** | Tick: 386 |
属性は、型、メソッド、プロパティなどの宣言に関係する情報を C# コードに関連付けます。プログラム要素に関連付けられた属性は、リフレクションと呼ばれる方法によって実行時に照会できます。
属性には 2 つの形式があります。
共通言語ランタイム (CLR: Common Language Runtime) に定義されている属性。
コードに特別な情報を追加するために、ユーザーが作成するカスタム属性。この情報は、後でプログラムによって取得できます。
[System.Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}
Author anonymousAuthorObject = new Author("H. Ackerman");
anonymousAuthorObject.version = 1.1;
Service Tutorial 4
Step1: Subscribeメッセージをサポートする
Subscribeメッセージの定義を付け加える。そのサービスによってサポートされるメッセージリストの中にそれを入れます。SubscribeRequestTypeクラスとSubscribeResponseType はMicrosoft.Dss.ServiceModel.Dssp名前空間で定義されている標準の型です。
Subscribeメッセージの定義 |
public class Subscribe : Subscribe>
{
}
|
サービスのoperationポートにSubscribeメッセージを入れると次のようになります。
ServiceTutorial4 Main Operations Port |
///
/// ServiceTutorial4 Main Operations Port
///
[ServicePort]
public class ServiceTutorial4Operations : PortSet<
DsspDefaultLookup,
DsspDefaultDrop,
Get,
HttpGet,
Replace,
IncrementTick,
Subscribe>
{
}
|
注意
読みやすくするためにエントリーを一行一行に書いてあります。
ServiceTutorial4.csファイルにおいてサービスを実装するクラスにSubscribeHandlerメソッドを加えます。
このハンドラーはsubscriberのアドレスをログする |
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator<ITask> SubscribeHandler(Subscribe subscribe)
{
SubscribeRequestType request = subscribe.Body;
LogInfo("Subscribe request from: " + request.Subscriber);
yield break;
}
|
Step2: Subscriptionマネージャーを用いる
subscriptionをサポートする次の仕事はsubscriberのリストを管理することです。subscriptionをサポートすることはサービスにとっては普通のことで、標準のDSSコンポーネント(SubscriptionManager)があり、これを用いてsubscriberを管理します。ちょっと道草をして、PartnerサービスとしてのSubscriptionManagerを開始してみましょう。
SubscriptionManagerを追加するためにServiceTutorial4.csを開きましょう。SubscriptionManagerは標準サービスですから、プロジェクトに他の参照を付け加える必要はありません。タイピング回数を少なくするために、submgrというaliasをusingを用いて作ります。
よく使うものはこのようにする |
using submgr = Microsoft.Dss.Services.SubscriptionManager;
|
サービスを実装するクラスの中にpartnerサービスを宣言するコードを追加します。
- Partner属性の記述
- partnerに対するユニークな名前
- partnerとして用いるサービスのContract
- creation policy
ここではSubMgrという名前のpartnerを作り、このContractはhttp://schemas.microsoft.com/xw/2005/01/subscriptionmanager.htmlです。サービスが開始されたときにこのparterの新しいインスタンスが作られます。もし、作成に失敗したときには同様にサービスの作成も失敗し、Startメソッドは呼ばれません。
_mainPortの定義の後ろの行にサービスの実装クラスの定義を付け加えます |
[Partner("SubMgr", Contract = submgr.Contract.Identifier,
CreationPolicy = PartnerCreationPolicy.CreateAlways)]
private submgr.SubscriptionManagerPort
_submgrPort = new submgr.SubscriptionManagerPort();
|
Partner属性は_submgrPortフィールドに適用されています。この_submgrPortフィールドはSubscriptionManagerのメインサービスポートとして宣言されます。このポートへの任意のメッセージの投函はこのサービスを作られたSubscriptionManagerのインスタンスに転送されます。
Step3: Subscriberリストを管理する
メッセージを送ることができるpartnerとしてSubscriptionManagerがあります。subscriberのリストの管理から始めてみましょう。DsspServiceBaseは必要なことを行うメソッドを持っています。
SubscribeHandlerの yield breakの直前に次のコードを追加します |
SubscribeHelper(_submgrPort, request, subscribe.ResponsePort);
|
このメソッドはSubscriptionManagerにInsertメッセージを送ります。SubscriptionManagerはsubscriptionを管理する責任を負います。
Step4: Notification(通知)を送る
subscriptionをサポートする最後の部分はsubscriberにNotificationを送ることです。このサービスがSubscriptionManagerを用いるので、行うことの全ては、SubscriptionManagerに状態変化を投函(post)することです。SubscriptionManagerはsubscribされたpartnerにNotificationを送ります。
状態変化を起こす場所が二か所あり、それはIncrementTickHandlerとReplaceHandlerです。
状態が全部入れ替わった時に、新しいサービスの状態でsubscriberにNotificationを送ります。DsspServiceBaseはSendNotificationヘルパー機能を持っていて、これは内部的にsubscriberに送るためのメッセージの本体とアクションを持ってSubscriptionManagerへSubmitメッセージを送ります(この場合ではReplaceRequest)。
ReplaceHandlerの_stateの後の行に次の行を加えます |
base.SendNotification(_submgrPort, replace);
|
同様に、状態がIncrementTickメッセージを処理するサービスによって更新された場合、このイベントのNotificationがsubscriberに送られます。
IncrementTickHandlerのLogInfo呼び出しの後に次の行を加えます |
base.SendNotification(_submgrPort, incrementTick);
|
subscriptionモデルは対称的です。サービスはsubscriberにそれがそれ自身で処理できる状態変化のメッセージを通知することができます。ですから、この例題でのサービスの場合では、サービス状態を変更する二つメッセージがあり、ReplaceとIncrementTickです。
Step5: 新しいsubscriberと同期する
IncrementTick更新は現在のチック数を保持していないで、単純にTick countは一つずつカウントアップするべきであるという意味の内容を運んでいます。ですからsubscriberはチック数を知りません。分かるのは開始してから発生するチックの数だけです。もし必要ならば、新しいsubscriberにReplace notification送ることによって、現在のチック数を送ることができます。
新しいsubscriberがうまく追加されたかどうかを検出するためにSubscribeHandlerメソッドを修正することから始めましょう。
SubscribeHelperメソッドはInsertメッセージをSubscriptionManagerに送ります。これは非同期動作です。
SubscribeHelperはSuccessFailurePortを返します。これは二つのメッセージ型(SuccessResult と Exception)を操作することのできるPortSetです。この結果を操作するために、yield retrunを用いてChoiceタスクを返します。Choiceタスクは二つのdelegateを持ちます。SuccessResultを処理するものと、Exceptionを処理するものです。
SuccessResultメッセージが戻されたときsubscriptionが正常に確立されたことを知り、サービス(Replace notificationのような)の現在の状態をsubscriberに送ります。もしExceptionが返されたならば、このエラーをログします。
注意
下で用いられているSendNotificationメソッド呼び出しにおいて、通常の2つのパラメータではなくて、むしろ3つあります。この場合、二番目のパラメータはsubscriberのアドレスです。これはsubscription managerに特定のsubscriberだけにnotificationを送らせます。コードの中でこの時点でReplaceメッセージが利用できないため、この呼び出しはSendNotificationに対するオーバーロードを用います。これはメッセージ本体を持ってくることによって、またどの型のメッセージ型が通知されたかを示すために、ジェネリックの型パラメータ(この場合Replace)を用いることによって実行されます。
SubscribeHandler |
[ServiceHandler(ServiceHandlerBehavior.Concurrent)]
public IEnumerator SubscribeHandler(Subscribe subscribe)
{
SubscribeRequestType request = subscribe.Body;
LogInfo("Subscribe request from: " + request.Subscriber);
yield return Arbiter.Choice(
SubscribeHelper(_submgrPort, request, subscribe.ResponsePort),
delegate(SuccessResult success)
{
base.SendNotification<Replace>(_submgrPort, request.Subscriber, _state);
},
delegate(Exception e)
{
LogError(null, "Subscribe failed", e);
}
);
yield break;
}
|

Service Tutorial 5
Service Proxyに参照を加える
ServiceTutorial4からのサービスとのpartner関係を確立して、それからそのサービスにsubscribeする必要があります。この最初の段階はpertnerサービスに対する参照を作ることです。これを行うために、proxyについて少し理解する必要があります。
- サービスが作られたときに、三つのアセンブリが作られます。
- メインサービス(あるいは実装)アセンブリ
例えば、 ServiceTutorial5.Y2006.M06.dll -- サービスの機能を含む
- proxyアセンブリ
例えば、 ServiceTutorial5.Y2006.M06.proxy.dll -- すべてのpublicに対するスタブを含む
- transformアセンブリ
例えば、ServiceTutorial5.Y2006.M06.transform.dll -- 型をproxyとサービスアセンブリとの間の変換をするコードを含む
メッセージを他のサービスに送るために、実装アセンブリではなくサービスのproxyアセンブリを参照します。これは、いくつかの理由のためにされます。他のnodeのサービスと通信しているとしたら、ローカルマシンの実装アセンブリのコピーを持っていないでしょう。同様にproxyによっても。pertnerサービスは行いたいサービスの文脈内で実行するコードを持っていません。
pertnerとしてServiceTutorial4を用いるために、プロジェクトにアセンブリ(ServiceTutorial4.Y2006.M06.proxy.dll)の参照を追加する必要があります。
参考
ServiceTutorial4.Y2006.M06.proxy.dllファイルはServiceTutorial4プロジェクトに対するproxyです。
同様にDssRuntime.Proxy.dllの参照を追加します。
Step 2: Pertnerサービスにsubscribeする
ServiceTutorial5.csに次の変更を加えてください。
Service Tutorial 4 proxyに対するaliasを追加してください。
Proxyという接尾辞がつきます |
using rst4 = Robotics.ServiceTutorial4.Proxy
|
Service Tutorial 4でsubscriptionマネージャーのpartnerを追加するのと同じ方法でpartnerを追加します。rst4.ServiceTutorial4Operationsの型のフィールド_clockNotifyを追加する必要もあり、そのサービスのoperation portの型はService Tutorial 4で作成されました。このフィールドはsubscriptionを作成したときにこのサービスがnotificationを受け取るポートです。
partnerを作る |
[Partner("Clock", Contract = rst4.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]
rst4.ServiceTutorial4Operations _clockPort = new rst4.ServiceTutorial4Operations();
rst4.ServiceTutorial4Operations _clockNotify = new rst4.ServiceTutorial4Operations();
|
ここまでで、あと残っていることはpartnerサービスにsubscribe(署名)することです。
Service Tutorial 4を思い起こしてみてください。そこで作られたサービスはSubscribeメッセージをサポートしています。proxyが作られた時にユーティリティ機能は各々のサポートされているメッセージに対して作られます。この場合、そのサービスはSubscribeメッセージに対してユーティリティ機能を呼び出します。このオーパロードに対する一つの引数はsubscriptionがnotificationを送るべきnotification(通知)ポートです。
Startメソッドの終りに次のコードを追加してください |
_clockPort.Subscribe(_clockNotify);
|
ビルドしこのサービスを走らせた場合、http://localhost:50000/console/outputログ の中に次のメッセージが確認できます。
|
.
.
.
Starting manifest load: file:///C:/MSRS/samples/config/ServiceTutorial5.manifest.xml
Manifest load complete
Service uri: http://localhost:50000/servicetutorial5
Service uri: http://localhost:50000/servicetutorial4
Subscribe request from: http://localhost:50000/servicetutorial5/NotificationTarget/
7cf955e7-73ff-47cd-bdc0-d3a3a8251c49
Tick: 21
Tick: 22
Tick: 23
Tick: 24
|
このログの中で、注意する二つのことは、第一にServiceTutorial4とServiceTutorial5の両方のサービスが開始されます。次にsubscribe要求を記録しているログメッセージがが操作されます。ここに渡されるURIはnotificationポートのアドレスです。これはポートにnotificationを送るためのDSS基礎構造です(_clockNotify)。
Step 3: Notificationを操作する
サービスをsubscribeすることの目的はサービスによってnotificationを送るように操作することです。ServiceTutorial4のサービスのサービスがIncrementTickとReplaceメッセージに対してnotificationを送っていたことを思い起こすでしょう。そうした訳で、この次のステップはこれらのnotificationを捜査することです。
これらのnotificationにハンドラーを追加するところから始めましょう。notificationはこのサービスのメインサービスポートには送られませんから、ServiceHandler属性を用いることはできません。その代りに、操作する予定の各々のnotificationメッセージに対する受取タスクを活性化する必要があります。
Startメソッドから動作します |
protected override void Start()
{
base.Start();
Activate(
Arbiter.Receive(true, _clockNotify, NotifyTickHandler),
Arbiter.Receive(true, _clockNotify, NotifyReplaceHandler)
);
_clockPort.Subscribe(_clockNotify);
}
|
分かるように、二つのreceiverは次のように明記します。
- 操作するメッセージ型
- rst4.IncrementTick
- rst4.Replace
- メッセージが到着するポート
- メッセージに対するハンドラーメソッド
次に、これらのメッセージ型の各々に対してのハンドラーを実装します。
メインサービスの実装クラス(ServiceTutorial5)で、次のメソッドを追加してください |
private void NotifyTickHandler(rst4.IncrementTick incrementTick)
{
LogInfo("Got Tick");
}
private void NotifyReplaceHandler(rst4.Replace replace)
{
LogInfo("Tick Count: " + replace.Body.Ticks);
}
|
参考
これらのハンドラーのどちらも、それが取り扱うメッセージに、response(応答)を送りません。responseに期待しない通知メッセージを取り扱います。
このサービスを走らせた時に、Tick: [tick number]メッセージとTick Count: [tick number]、Subscribe request from: [notification port]を従えたGot Tickメッセージがコンソール上に表示されます。
Step 4: 同期状態
ServiceTutorial5サービスはService Tutorial 4 (C#) - Supporting Subscriptionsからそのpartnerと同期した状態を保持するのに必要な情報を全て持っています。
ServiceTutorial5Types.csを修正するところから始めましょう。
サービスがチック数を含むようにサービスの状態を変えてみましょう。Service Tutorial 4とは違ってこのサービスを保持するために、Memberプロパティを削除し、TickCountという名前の整数プロパティを作ります。
ServiceTutorial5Types.cs |
private int _tickCount;
[DataMember]
public int TickCount
{
get { return _tickCount; }
set { _tickCount = value; }
}
|
次のものをサポートするようにoperationポートを変更します。
- ServiceTutorial4で定義されたIncrementTickメッセージ
- このサービスに対する新しいメッセージSetTickCount
Main Operations Port |
///
/// ServiceTutorial5 Main Operations Port
///
[ServicePort]
public class ServiceTutorial5Operations : PortSet<
DsspDefaultLookup,
DsspDefaultDrop,
Get,
HttpGet,
Replace,
rst4.IncrementTick,
SetTickCount>
{
}
|
ステップ2で行ったと同じ方法で、ServiceTutorial5Types.csに、usingディレクティブを追加します。
SetTickCountメッセージとその本体の型、SetTickCountRequestは
次のコードで示すように、ServiceTutorial5Types.csで宣言されます。
|
public class SetTickCount : Update>
{
public SetTickCount()
{
}
public SetTickCount(int tickCount)
: base(new SetTickCountRequest(tickCount))
{
}
}
[DataContract]
[DataMemberConstructor]
public class SetTickCountRequest
{
public SetTickCountRequest()
{
}
public SetTickCountRequest(int tickCount)
{
_tickCount = tickCount;
}
private int _tickCount;
[DataMember]
public int TickCount
{
get { return _tickCount;}
set { _tickCount = value;}
}
}
|
[DataContract]と一緒に使われる[DataMemberConstructor]属性は
、当該クラスの各々の[DataMember]メンバーを作る初期化コンストラクタのproxyを創生するように明記しています。言い換えると、SetTickCountRequest 型から[DataMemberConstructor]を取り去ると、proxy型の中のデフォールトのSetTickCountRequest()コンストラクタだけを持つことになります。
ServiceTutorial5.csの中のサービス実装クラスでこれら二つのメッセーに対応したハンドラーを追加します。
状態を修正するので、これらのメッセージはexclusiveとして印づけられています |
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator IncrementTickHandler(rst4.IncrementTick incrementTick)
{
_state.TickCount++;
incrementTick.ResponsePort.Post(DefaultUpdateResponseType.Instance);
yield break;
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public IEnumerator SetTickCountHandler(SetTickCount setTickCount)
{
_state.TickCount = setTickCount.Body.TickCount;
setTickCount.ResponsePort.Post(DefaultUpdateResponseType.Instance);
yield break;
}
|
最後に、notificationハンドラーからメインサービスポートに適切なメッセージを送ることによって、notificationハンドラーをこれらのメッセージハンドラーに接続してください。
NotifyTickHandlerとNotifyReplaceHandlerメソッドは次のようになります |
private void NotifyTickHandler(rst4.IncrementTick incrementTick)
{
LogInfo("Got Tick");
_mainPort.Post(new rst4.IncrementTick(incrementTick.Body));
}
private void NotifyReplaceHandler(rst4.Replace replace)
{
LogInfo("Tick Count: " + replace.Body.Ticks);
_mainPort.Post(new SetTickCount(replace.Body.Ticks));
}
|
参考
NotifyTickHandlerメソッドにおいて、IncrementTick型のメッセージがハンドルされ、IncrementTick型のメッセージはメインサービスポートに送られます。IncrementTick引数がメインポートに直接送らないのは何故でしょうか? ResponsePortは正しく設定されるのを確実にするためには新しいメッセージが作られる必要があるのです。
上級
NotifyTickHandlerメソッドにおいて、IncrementTickメッセージがハンドルされ、IncrementTickメッセージがメインポートに送出されます。NotifyReplaceHandlerメソッドでは、rst4.Replaceメッセージがハンドルされ、SetTickCountメッセージがメインポートに送出されます。何故新しいメッセージ型が必要なのでしょうか? rst4.ReplaceメッセージはDSSPのaction verbReplaceを用いています。これはServiceTutorial4では適切で、サービスの状態を置換していますが、ここでのServiceTutorial5では、概念的に状態の一つのメンバーを変えることを表すため、不適切です。この理由から、SetTickCountメッセージはUpdate verbを用いて定義され、状態を修正するのに用いられます。
Service Tutorial 6
Step 1: 他のサービスの状態を得る
ServiceTutorial6Types.csで、ServiceTutorial6StateクラスにClockとInitialTicksのプロパティを追加します。
これらのプロパティはどのように他のサービスの状態を得るかを例証するために用いられています |
private string _clock;
[DataMember]
public string Clock
{
get { return _clock; }
set { _clock = value; }
}
private int _initialTicks;
[DataMember]
public int InitialTicks
{
get { return _initialTicks; }
set { _initialTicks = value; }
}
|
ServiceTutorial6.csで、Startメソッドの中の _clockPort.Subscribe呼び出しの行を削除し、その代わりに次のコードを入れます。イテレータメソッドOnStartupをサービスの実行時に同時に走るようにします。
同時実行 |
SpawnIterator(OnStartup);
|
OnStartupメソッドを書きましょう。このメソッドが行う最初のことはGetメッセージをServiceTutorial4サービスパートナーへ送ることです。このメッセージを送るためには、_clockPortフィールドでGetメソッドを呼び出します。これはGetメッセージを作り、サービスに送ります。イテレータはyeild return を用いてChoiceタスクを戻します。利用できる2つの選択は、ServiceTutorial4のGetメッセージによって定義される2つの可能なresponse(応答)メッセージタイプです。一般的に、サービスのGetメッセージはサービスの状態あるいはFaultを返します。
ここで、サービスの状態は成功の経路のときにローカル変数stateに格納され、失敗の経路ではfaultがログされます。
参考
この機能は、using W3C.Soap;がファイルの先頭で宣言されていることを仮定しています。 |
private IEnumerator OnStartup()
{
rst4.ServiceTutorial4State state = null;
yield return Arbiter.Choice(
_clockPort.Get(GetRequestType.Instance),
delegate(rst4.ServiceTutorial4State response)
{
state = response;
},
delegate(Fault fault)
{
LogError(null, "Unable to Get state from ServiceTutorial4", fault);
}
);
|
|
参考
上記のコードは、リクエストとresponseをハンドリングするために簡潔な構文を用いています。Arbiter.Choiceをyield reternをする代わりに、ここではChoiceオブジェクトを返すGetメソッドのオーバーロードを用いています。受け取ったresponseはstate変数に割り当てられます。これは成功のresponse型にResponsePortをキャストすることによって暗黙のうちになされます。もしそのResponsePortが失敗で満ちていた場合、割り当てられるstateの値はnullになります。
Arbiter.Choiceを用いるこのメソッドのようなものに対しては
Appendix B を参照してください。より理解できるようになります。
Step 2: サービスの状態を表示するためにXSLTを用いる
Service Tutorial 1 (C#) - Creating a Service,では、ウェブブラウザーでサービスの状態のXML表現をどのように表示するかを学びました。そのような状態は、ユーザーにより分かり易くするためにHTML(あるいはテキスト、あるいは他のXML表現)に変換することが出来ます。この過程は3つだけのステップが必要なだけです。
- どのようにその状態を変換するかを記述するXSLTファイルを書く。
- サービスアセンブリの組み込み型リソースとしてXSLTを追加します。
- その状態の直列化されたXML表現でXSLTにリンクする。
始めにMicrosoft Robotics Studio がインストールされたディレクトリのサブディレクトリstore\transformsの中にあるXSLTスタイルシートを参照するためにMount Serviceをどのように用いるかを示します。
ServiceTutorial6.csのファイルの中で、_transformと呼ばれるstring型を宣言し、 Mountサービスを通してXSLTファイルの相対パスをそれに割り当てます。
|
string _transform = "/mountpoint/store/transforms/ServiceTutorial6.xslt";
|
|
ServiceTutorial6.csのファイルの中で、_transformと呼ばれるstring型を
宣言し、Mountサービスを通してXSLTファイルの相対パスをそれに割り当てます。
string _transform = "/mountpoint/store/transforms/ServiceTutorial6.xslt";
参考
DssHostポートの数はDSSノードが開始するまで分かりません。従って、そのstoreから参照された変換のためのURIはランタイム時に作成されるか相対パスで指定されなければなりません。
HttpGetHandlerメソッドは、XSLTのパスをHttpResponseTypeコンストラクタに渡します。
httpGet.ResponsePort.Post(new HttpResponseType(System.Net.HttpStatusCode.OK, _state, _transform));
ServiceTutorial6.xsltファイルをstore\transformsにコピーしてください。そしてビルドしサービスを走らせてください。すると次のようなページを見ることが出来ます。
http://localhost:50000/servicetutorial6
重要
Internet ExplorerでXSLTをデバッグするとき、変化を見るためにいつも新しいブラウザのウィンドウあるいはタブを開きます。Ctrl+F5を押したとしても、XSLTエラーが起こったときに、IEのキャッシュは時々ページをリフレッシュしません。新しいウィンドウのページを開くことはいつもそのページをリロードします。
Step 3: 組み込みリソースとしてのXSLTを用いる
XSLTファイルをstore\transformsディレクトリにコピーすることは必ずしも適切ではありません。サービスを展開するのに複雑さを増やします。
Microsoft Robotics Studioはアセンブリに組み込まれたリソースをロードすることをサポートしています。XSLTファイルを組み込むためには、Microsoft Visual Studioにあるソリューションエクスプローラを選択します。プロパティウィンドウで
ビルド アクションフィールドをコンテンツから埋め込まれたリソースに変更してください。そのリソースはデフォールトのプロジェクトの名前空間とファイル名を結合した名前になります。この場合ですと、Robotics.ServiceTutorial6.ServiceTutorial6.xsltとなります。
[EmbeddedResource("Robotics.ServiceTutorial6.ServiceTutorial6.xslt")]
string _transform = null;
_transformフィールドの値はランタイム時にサービスの開始に投入されます。
デフォールトのGetHttpGetハンドラーを使うような状況の時に、組み込みXSLTリソースを組み込む簡単な方法があります。この場合、nullのtransformフィールドを定義する代わりに、[EmbeddedResource]属性を用いることによって次のことができます。
これは、HttpGetresponseに対する示されたXSL変換を用いて、GetとHttpGetの両方のメッセージを扱うデフォールトのハンドラーを暗黙的に使うとベースクラスに知らせます。GetとHttpGetの動作は暗黙的にハンドルされるためにサービスの動作ポートにすでになければいけないことに注意してください。
参考
名前空間の衝突を避けるためにMicrosoft Robotics StudioにインストールされているService Tutorial 6プロジェクトは上で説明したのと異なった名前空間を用いています。プロジェクトの中でデフォールトの名前空間を確認するには、プロジェクトのPropertiesを開きアプリケーションタブにある既定の名前空間を見てください。
Step 4: DSS XSLT テンプレートを用いる
上の変換された状態表示が他のDSSサービスの場合のように共通のレイアウトを持たないことに気付いたかも知れません。Microsoft Robotics Studioは一貫性のある外観にするために、全てのDSSサービスに対して共通のXSLTテンプレートを用います。このテンプレートをサービスにどのように適用するかを学ぶためにはTutorial for Using Default DSS XSLT Templateを見てください。
参考
これは上級の段階で、次の指導書続けるためには、この段階を完成させる必要はありません。しかしながら、製品に対するサービスの開発をするときには、ここに提示してあるガイドラインに従うことを強く勧めます。
Appendix A:
ServiceTutorial6.xslt |
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rst6="http://schemas.tempuri.org/2006/06/servicetutorial6.html">
<xsl:output method="html"/>
<xsl:template match="/rst6:ServiceTutorial6State">
<html>
<head>
<title>Service Tutorial 6</title>
<link rel="stylesheet" type="text/css"
href="/resources/dss/Microsoft.Dss.Runtime.Home.Styles.Common.css" />
</head>
<body style="margin:10px">
<h1>Service Tutorial 6</h1>
<table border="1">
<tr class="odd">
<th colspan="2">Service State</th>
</tr>
<tr class="even">
<th>Clock:</th>
<td>
<xsl:value-of select="rst6:Clock"/>
</td>
</tr>
<tr class="odd">
<th>Initial Tick Count:</th>
<td>
<xsl:value-of select="rst6:InitialTicks"/>
</td>
</tr>
<tr class="even">
<th>Current Tick Count:</th>
<td>
<xsl:value-of select="rst6:TickCount"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
|
Appendix B: DSSPイテレータハンドラーのrequest/responseに対する代替パターン
上記のOnStartup()メソッドで、多少簡単なイテレータにおけるDSS request/responsesをハンドルする代替法を用い、全てのresponse型に対する匿名メソッドを定義するよりも興味のわくそれらのresponseメッセージのみをハンドルできるようにします。。上記のOnStartup()メソッドは次のコードと同等です。違いを見るには、二つのyield returnの部分を比べます。
CCRイテレータについてのより詳しい情報はCCR Iteratorsを見てください。
private IEnumerator OnStartup()
{
rst4.ServiceTutorial4State state = null;
yield return Arbiter.Choice(
_clockPort.Get(GetRequestType.Instance),
delegate(rst4.ServiceTutorial4State response)
{
state = response;
},
delegate(Fault fault)
{
LogError(null, "Unable to Get state from ServiceTutorial4", fault);
}
);
if (state != null)
{
ServiceTutorial6State initState = new ServiceTutorial6State();
initState.InitialTicks = state.Ticks;
PartnerType partner = FindPartner("Clock");
if (partner != null)
{
initState.Clock = partner.Service;
}
Replace replace = new Replace();
replace.Body = initState;
_mainPort.Post(replace);
}
yield return Arbiter.Choice(
_clockPort.Subscribe(_clockNotify),
delegate(SubscribeResponseType response) { },
delegate(Fault fault)
{
LogError(null, "Unable to subscribe to ServiceTutorial4", fault);
}
);
}
Service Tutorial 7 - Advanced Topics
Step 1: 他のノードで走っているサービスを購読する
「Service Tutorial 5 (C#) - Subscribing」と続いて修正した「Service Tutorial 6 (C#) - Retrieving State and Displaying it Using an XML Transform」で作ったサービスで、
パートナーは"Clock"という名前で定義されました。このパートナーは次のプロパティで宣言されます。
- 名前Clock
- 「Service Tutorial 4 (C#) - Supporting Subscriptions」で作られたサービスのコントラクト
- UseExistingOrCreateパートナー作成ポリシー
Service Tutorial 5 or 6 のサービスがこのパートナー宣言で開始すると、Service Tutorial 4のサービスが現在のノードですでに走っていなければ、同様に開始されます。
<?xml version="1.0" ?>
<Manifest
xmlns="http://schemas.microsoft.com/xw/2004/10/manifest.html"
xmlns:d="http://schemas.microsoft.com/xw/2004/10/dssp.html"
xmlns:st6="http://schemas.tempuri.org/2006/06/servicetutorial6.html">
<CreateServiceList>
<!-- Service Tutorial 6 -->
<ServiceRecordType>
<d:Contract>http://schemas.tempuri.org/2006/06/servicetutorial6.html</d:Contract>
<d:PartnerList>
<d:Partner>
<d:Service>http://localhost:40000/servicetutorial4</d:Service>
<d:Name>st6:Clock</d:Name>
</d:Partner>
</d:PartnerList>
</ServiceRecordType>
</CreateServiceList>
</Manifest>
これを確かめるには走らせる二つのノードが必要になります。
- httpに対して40000のポートを用いたノードで、Service Tutorial 4を走らせる
- httpの他のポート(50000)を用いたノードで、上のマニフェストを走らせる
これらのノードを(順番に)開始するためには二つのMicrosoft Robotics Studioのコマンドプロンプトを走らせます。
最初は
dsshost /p:40000 /t:40001 /m:"Samples\Config\ServiceTutorial4.manifest.xml"
これはポート40000で走るようにノードが開始し、Service Tutorial 4サービスが開始します。
.\Samples\Configディレクトリで、上記マニフェストをServiceTutorial7.manifest.xmlとして保存してください。
二つ目のMicrosoft Robotics Studioのコマンドプロンプトを走らせます。
dsshost /p:50000 /t:50001 /m:"Samples\Config\ServiceTutorial7.manifest.xml"
上記のマニフェストを用いてポート50000でノードを開始し、Service Tutorial 6 のサービスを開始し、ポート40000で走っているノードでClockパートナーを探すようにサービスに命令します。
参考
Clockパートナーはネットワークを超えてアクセスできる他のマシンで走ることができます。ここでは分かりやすくするために同じマシン上でデモしています。
Step 2: 他のノードでサービスを開始する