WCF では、例外を一元的にハンドリングするための機構が別の方法で提供されています。
Global.asax に比べて、手順がやや面倒となりますが、クライアントにフォールトメッセージを通知することを考慮した仕組みにはなっています。
【 IErrorHandler インターフェイス 】
例外を一元ハンドリングするのは、 IErrorHandler インターフェイス (System.ServiceModel.Dispatcher) を実装した独自のクラスとなります。
このインターフェイスが提供するメソッドは以下の2つです。
[ ProvideFault メソッド ]
ProvideFault メソッド は、サービス内部からスローされた例外をハンドリングします。このメソッド内でクライアントに渡すフォールトメッセージを生成することができます。
ロギングなどの処理はこのメソッドではなく、後述の HandleError メソッドで行います。
このメソッドのパラメータは以下の3つです。
error
型は Exception クラス (System) です。
このパラメータにより、サービス内部からスローされた例外を取得できます。
version
型は MessageVersion クラス (System.ServiceModel.Channels) です。
このパラメータにより、現在の SOAP 通信 における SOAP のバージョンを取得できます。
fault
型は Message クラス (System.ServiceModel.Channels) です。また、このパラメータは参照渡しとなっています。
サービス内部からスローされた例外が、 FaultException クラス (System.ServiceModel) であった場合、それを表わすフォールト メッセージがこのパラメータにより取得できます。サービス内部からスローされた例外が、 FaultException クラスでない場合、このパラメータは null となります。
この ProvideFault メソッド内で Message クラスを生成して、このパラメータに設定することができます。これにより、クライアントに渡すフォールトメッセージを指定することができるわけです。
・ProvideFault メソッドの実装例
FaultException 以外の、ハンドルされなかった例外が発生した場合に、予期せぬエラーが発生したという通知をクライアントに対して行うための実装例を以下に示します。
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (fault != null)
{
return;
}
FaultReasonText[] reasonTexts =
new FaultReasonText[]
{
new FaultReasonText("予期せぬエラーが発生しました。", "ja-JP"),
new FaultReasonText("An unexpected error occurred.", "en-US")
};
FaultReason reason = new FaultReason(reasonTexts);
FaultCode code = FaultCode.CreateReceiverFaultCode("unexpected", "http://schemas.yokoken.net/Hoge");
FaultException faultException = new FaultException(reason, code);
MessageFault messageFault = faultException.CreateMessageFault();
string action = OperationContext.Current.IncomingMessageHeaders.Action;
fault = Message.CreateMessage(version, messageFault, action);
}
[ HandleError メソッド ]
HandleError メソッド は、サービス内部からスローされた例外と、通信時に発生した例外をハンドリングします。
サービス内部からスローされた例外は、ProvideFault メソッドが先にハンドリングします。その後、このメソッドがハンドリングします。ProvideFault メソッドでフォールト メッセージを生成した場合でも、HandleError メソッドでハンドリングされる例外は変わりません。つまり、サービス内部でスローされた例外がハンドリングされます。
ロギングや、MSMQ の有害メッセージ ( Poison Message ) の処理などはこのメソッドで行います。
このメソッドのパラメータは以下の1つです。
error
型は Exception クラス (System) です。
このパラメータにより、サービス内部からスローされた例外や、通信時に発生した例外を取得できます。
・戻り値について
このメソッドの戻り値は bool 型の値です。true を返した場合、例外はハンドルされたものとして扱われ、false を返した場合、例外はハンドルされなかったものとして扱われます。
MSMQ で有害メッセージを処理した等の場合は、ここで true を返します。何も処理しなかったり、ロギングのみを行う場合は false を返します。
( ただ、正直な話、これらの具体的な違いは不明です。色々試してみましたが、 true を返しても false を返しても、挙動の違いは見受けられませんでした。Reflector でコードを覗いてみると、一応特定の条件下において false が返された場合に、チャネルの中止処理を行うようになっているようです。 )
・HandleError メソッドの実装例
FaultException 以外の、ハンドルされなかった例外 ( 通信時の例外含む ) が発生した場合に、例外の情報をコンソールへ出力するための実装例を以下に示します。
public bool HandleError(Exception error)
{
if (error is FaultException)
{
return false;
}
string errorType = error.GetType().FullName;
string errorMessage = error.Message;
string errorStackTrace = error.StackTrace;
Console.WriteLine("[ Error ]\r\nType: {0}\r\nMessage: {1}\r\nStack Trace: {2}", errorType, errorMessage, errorStackTrace);
return false;
}
【 エラー ハンドラの登録 】
[ ChannelDispatcher へ登録 ]
IErrorHandler 実装クラスを定義しただけでは、エラー ハンドラとして動作してくれません。IErrorHandler 実装クラスを登録する処理を実装する必要があります。
登録は、ChannelDispatcher クラス (System.ServiceModel.Dispatcher) のインスタンスの、ErrorHandlers プロパティ に対して行います。このプロパティに格納されているコレクションオブジェクトに対してIErrorHandler 実装クラスのインスタンスを追加してやります。
ServiceHostBase クラス (System.ServiceModel) の ChannelDispatchers プロパティ には、ChannelDispatcherCollection クラス (System.ServiceModel.Dispatcher) のインスタンスが格納されています。このコレクションから、ChannelDispatcher オブジェクトを取得することができます。
このコレクション自体は、ChannelDispatcherBase クラス (System.ServiceModel.Channels) のコレクションとして定義されています。ChannelDispatcherBase クラスは、ChannelDispacher クラスの基本クラスですが、ErrorHandlers プロパティは定義されていません。つまり、ChannelDispacher クラスへのキャストが行えるか検証し、キャストを行う必要があります。 ( ただ、ChannelDispatcherBase クラスは抽象クラスであり、このクラスの派生クラスは ChannelDispacher クラスしかありません。つまり、事実上ChannelDispatcherCollection クラスは ChannelDispacher のコレクションです。しかし、それでもキャストができるかどうかの検証はしっかり行うことを推奨します。 )
[ 登録はサービス ビヘイビアで行う ]
登録処理は独自のビヘイビアで行います。ビヘイビアからではなく、サービス ホストを直接操作して登録処理を行っても、エラー ハンドラは動作してくれません。
ビヘイビアはサービス ビヘイビアまたはエンドポイント ビヘイビアとなります。
独自のビヘイビアを定義する方法については以下の記事を参照してください。
独自のビヘイビアを定義する
独自のビヘイビアを定義する 2
【 実装例 】
エラー ハンドラの実装例と、それを適用するためのサービス ビヘイビアの実装例を以下に示します。
エラー ハンドラ
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
internal sealed class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
if (error is FaultException)
{
return false;
}
string errorType = error.GetType().FullName;
string errorMessage = error.Message;
string errorStackTrace = error.StackTrace;
Console.WriteLine("[ Error ]\r\nType: {0}\r\nMessage: {1}\r\nStack Trace: {2}", errorType, errorMessage, errorStackTrace);
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (fault != null)
{
return;
}
FaultReasonText[] reasonTexts =
new FaultReasonText[]
{
new FaultReasonText("予期せぬエラーが発生しました。", "ja-JP"),
new FaultReasonText("An unexpected error occurred.", "en-US")
};
FaultReason reason = new FaultReason(reasonTexts);
FaultCode code = FaultCode.CreateReceiverFaultCode("unexpected", "http://schemas.yokoken.net/Hoge");
FaultException faultException = new FaultException(reason, code);
MessageFault messageFault = faultException.CreateMessageFault();
string action = OperationContext.Current.IncomingMessageHeaders.Action;
fault = Message.CreateMessage(version, messageFault, action);
}
}
サービス ビヘイビア
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
[AttributeUsage(AttributeTargets.Class)]
public class ErrorHandlerAttribute : Attribute, IServiceBehavior
{
private IErrorHandler errorHandler;
public ErrorHandlerAttribute()
{
this.errorHandler = new ErrorHandler();
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
if (dispatcher == null)
{
continue;
}
dispatcher.ErrorHandlers.Add(this.errorHandler);
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
これで、サービス クラスに ErrorHandlerAttribute クラスを属性として付加すれば、サービスにエラー ハンドラが適用されます。
コンフィギュレーションやコードから、サービスホストに対してビヘイビアを適用する方法は、こちらの「独自のビヘイビアを定義する」という記事を参照してください。
ビヘイビアには、サービス ビヘイビア、エンドポイント ビヘイビア、コントラクト ビヘイビア、オペレーション ビヘイビアの4種類がありますが、このうち、エンドポイント ビヘイビア以外のビヘイビアは、カスタム属性として定義することもできます。
例えば、サービス ビヘイビアは IServiceBehavior インターフェイス (System.ServiceModel.Description) を実装するクラスとして定義し、更に Attribute クラス (System) を継承させます。
以下に、サービス ビヘイビアの実装例を示します。
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
[AttributeUsage(AttributeTargets.Class)]
public sealed class HelloWorldBehaviorAttribute : Attribute, IServiceBehavior
{
private bool silent;
public bool Silent
{
get
{
return this.silent;
}
set
{
this.silent = value;
}
}
public HelloWorldBehaviorAttribute()
{
this.silent = false;
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
if (this.silent)
{
return;
}
serviceHostBase.Opened +=
delegate
{
Console.WriteLine("Hello World !");
};
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
このサービス ビヘイビアを適用すると、前回と同じく、サービス ホストのオープン時にコンソールへ "Hello World !" と出力します。
以下に、適用例を示します。
[HelloWorldBehavior(Silent = false)]
public sealed class Service1 : IService1
{
/*...*/
}
開発と同じく、出だしが一番面倒ですね。
あと、XML ベースでそのまま公開しようと思ってましたけど、ちょっと無理そう(´・ω・`)
だって、FireFox だと、実体参照がうまく扱えなかったりJavaScriptがうまく動かなかったりするんだもん(´・ω・`)
しょうがないから、XSLT変換してからサーバーにアップロードすることにします。
あれ、そうするとFC2でも別にいいのか・・・。わざわざ他の所探して登録まで済ませたというのに( ゚Дメ )グヲヲヲッ
まぁ、それはいいとして、とりあえずWeb サイトできるまでは、今まで通りこのブログで WCF を扱っていこうかと思います。 ( Web サイトできたあとも、このブログでも WCF を扱うかもしれませんが。 )
ちなみに、次回は WCF での一元的なエラーハンドリングの方法を解説しようかと思います。先ほど書いた 独自ビヘイビアの記事 は、実はこのための布石だったりします ^^;
【 独自のビヘイビア クラスを定義 】
ビヘイビアには、サービス ビヘイビア、エンドポイント ビヘイビア、コントラクトビヘイビア、オペレーションビヘイビアの4種類があります。
これらは、それぞれ利用できる適用方法が、以下のように異なります。
- サービス ビヘイビア、エンドポイント ビヘイビアは、コンフィギュレーションで適用することができます。
- コントラクト ビヘイビア、サービス ビヘイビア、オペレーション ビヘイビアは、プログラムでカスタム属性として適用することができます。
- 全てのビヘイビアは、プログラムで ServiceHost オブジェクトをカスタマイズして適用することができます。
[ サービス ビヘイビア ]
IServiceBehavior インターフェイス (System.ServiceModel.Description) を実装したクラスで表されます。このインターフェイスには、以下の3つのメソッドが用意されています。
AddBindingParameters メソッド
ApplyDispatchBehavior メソッド
Validate メソッド
[ エンドポイント ビヘイビア ]
IEndopointBehavior インターフェイス (System.ServiceModel.Description) を実装したクラスで表されます。
このインターフェイスには、以下の4つのメソッドが用意されています。
AddBindingParameters
ApplyClientBehavior
ApplyDispatchBehavior
Validate
[ コントラクト ビヘイビア ]
IContractBehavior インターフェイス (System.ServiceModel.Description) を実装したクラスで表されます。このインターフェイスには、以下の4つのメソッドが用意されています。
AddBindingParameters メソッド
ApplyClientBehavior メソッド
ApplyDispatchBehavior メソッド
Validate メソッド
[ オペレーション ビヘイビア ]
IOperationBehavior インターフェイス (System.ServiceModel.Description) を実装したクラスで表されます。このインターフェイスには、以下の4つのメソッドが用意されています。
AddBindingParameters メソッド
ApplyClientBehavior メソッド
ApplyDispatchBehavior メソッド
Validate メソッド
各インターフェイスでは共通の名前のメソッドが用意されています。 ( IServiceBehavior のみ、ApplyClientBehavior メソッドが存在しません。 )
AddBindingParameters メソッド
バインディング要素にデータを追加するためのメソッドです。
ApplyClientBehavior メソッド
クライアントアプリケーションでカスタマイズを行うためのメソッドです。
ApplyDispatchBehavior メソッド
サービスアプリケーションでカスタマイズを行うためのメソッドです。
Validate メソッド
ビヘイビアを適用するための前提条件の検証を行います。
[ 実装について ]
ビヘイビア クラスを作成する場合、適切なインターフェイスを実装することになります。当然、独自のプロパティを用意することも可能です。
ビヘイビア クラスは、開発者がコードから直接操作することも想定されるため、インターフェイスは明示的に実装した方が良いかと思います。
[ 実装例 ]
以下に、サービス ビヘイビア クラスの実装例を示します。
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
public sealed class HelloWorldBehavior : IServiceBehavior
{
private bool silent;
public bool Silent
{
get
{
return this.silent;
}
set
{
this.silent = value;
}
}
public HelloWorldBehavior()
{
this.silent = false;
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
if (this.silent)
{
return;
}
serviceHostBase.Opened +=
delegate
{
Console.WriteLine("Hello World !");
};
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
}
このビヘイビアを設定すると、サービス ホストがオープンした時にコンソールへ "Hello World !" という文字列を出力するようになります。Silent プロパティが true に設定されている場合は、この動作を追加しません。
【 独自のビヘイビアをプログラムから ServiceHost に適用する 】
プログラムから ServiceHost に適用する場合、特別な処理は不要で、通常のビヘイビア設定方法と変わりません。コードでビヘイビアを設定する方法については以下の記事を参照してください。
C#と諸々 - WCFのホスティング
【 独自のビヘイビアをコンフィギュレーションで適用する 】
サービス ビヘイビア、エンドポイント ビヘイビアは、コンフィギュレーションから適用することができます。しかし、ビヘイビア クラスを定義しただけでは、コードからは扱えてもコンフィギュレーションで扱うことができません。コンフィギュレーションで扱うためには、拡張エレメントを定義する必要があります。
[ 拡張エレメント クラス ]
拡張エレメントを定義するには、 BehaviorExtensionElement クラス (System.ServiceModel.Configuration) の継承クラスを定義します。
BehaviorExtensionElement クラスには、以下の2つの抽象メンバが用意されています。
BehaviorType プロパティ
ビヘイビア クラスの型情報を返すように実装します。
CreateBehavior メソッド
ビヘイビア クラスのインスタンスを生成して返すように実装します。
この際、拡張エレメントの属性 ( 後述 ) で設定された値を、ビヘイビア クラスのインスタンスに反映させる必要があります。
[ 拡張エレメントの属性 ]
ビヘイビア クラスにプロパティを定義した場合、拡張エレメントでもそれを設定できるようにする必要があります。そのためには、拡張エレメント クラスにプロパティを用意して、ConfigurationPropertyAttribute クラス (System.Configuration) をプロパティの属性として付加する必要があります。
[ 実装例 ]
先ほどの例で定義した HelloWorldBehavior ビヘイビアを、コンフィギュレーションで扱うための拡張エレメント クラスの例を以下に示します。
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
public sealed class HelloWorldElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get
{
return typeof(HelloWorldBehavior);
}
}
protected override object CreateBehavior()
{
HelloWorldBehavior result = new HelloWorldBehavior();
result.Silent = this.Silent;
return result;
}
[ConfigurationProperty("silent", DefaultValue = false)]
public bool Silent
{
get
{
return (bool)base["silent"];
}
set
{
base["silent"] = value;
}
}
private ConfigurationPropertyCollection properties;
protected override ConfigurationPropertyCollection Properties
{
get
{
if (this.properties == null)
{
ConfigurationPropertyCollection propertys = new ConfigurationPropertyCollection();
propertys.Add(new ConfigurationProperty("silent", typeof(bool), false, null, null, ConfigurationPropertyOptions.None));
this.properties = propertys;
}
return this.properties;
}
}
}
[ コンフィギュレーションで適用 ]
独自のビヘイビアをコンフィギュレーションで適用する場合、 [ system.serviceModel ] - [ extensions ] セクションに、拡張エレメントを追加して、コンフィギュレーションで扱う際の名称を設定する必要があります。
先ほど定義した拡張エレメントを extensions セクションに追加する例を以下に示します。 ( アセンブリ名と拡張エレメントの名前空間は、共に "WCFCustomize" としました。 )
<extensions>
<behaviorExtensions>
<add name="helloWorld" type="WCFCustomize.HelloWorldElement, WCFCustomize, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
extensions セクションに拡張エレメントを追加すると、その際に name 属性に指定した名称をセクション名として、bihavior セクション内にて設定することが可能となります。
以下に、使用例を示します。
<behaviors>
<serviceBehaviors>
<behavior name="SampleBehavior">
<helloWorld silent="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
これで、独自のビヘイビアがサービスに適用されるようになりました。サービス ホストがオープンした時に "Hello World !" と出力されるはずです。 ( コンソールに出力されるので、コンソールアプリでセルフ ホスティングしてください。 )
【 独自のビヘイビアを属性として適用 】
以下の記事を参照してください。
C#と諸々 - 独自のビヘイビアを定義する 2
このアカウントはファイル・ディレクトリへのアクセス権などが制限されていて、ログファイルを出力することすらできません。
これを解決するには、対象のファイル・ディレクトリへ適切な権限を付加する必要があります。
インストーラで、この権限付加の処理を行いたい場合、インストーラのカスタム動作でこれを行います。
以下に例を示します。
//using System;
//using System.Configuration.Install;
//using System.IO;
//using System.Security.AccessControl;
[RunInstaller(true)]
public partial class CustomInstaller : Installer
{
public CustomInstaller()
{
InitializeComponent();
}
public override void Install(System.Collections.IDictionary stateSaver)
{
string assemblyPathName = this.Context.Parameters["assemblypath"];
int fileDivisionIndex = assemblyPathName.LastIndexOf('\\');
string installDirectoryPathName = assemblyPathName.Remove(fileDivisionIndex);
DirectoryInfo installDirectoryInfo = new DirectoryInfo(installDirectoryPathName);
// インストール先ディレクトリの ACL ( アクセス制御リスト ) を取得
DirectorySecurity installDirectoryACL = installDirectoryInfo.GetAccessControl();
// Network Service アカウントの書き込み権限を表すオブジェクトを生成
FileSystemAccessRule writingAuthorityOfWorkerProcess = new FileSystemAccessRule("NT AUTHORITY\\NETWORK SERVICE", FileSystemRights.Write, AccessControlType.Allow);
// ACL に、Network Service アカウントの書き込み権限を追加する
installDirectoryACL.AddAccessRule(writingAuthorityOfWorkerProcess);
// インストール先ディレクトリの ACL を設定
installDirectoryInfo.SetAccessControl(installDirectoryACL);
base.Install(stateSaver);
}
}
この例では、インストール先ディレクトリに対して、Network Serviceアカウントが書き込みを行うための権限を付加しています。
これにより、Web アプリがインストール先ディレクトリ内にログファイルを出力することが可能となります。
インストーラ、カスタム動作自体の説明はここでは省略します。これについては以下のチュートリアルを参考にしてください。
Windows インストーラでの配置に関するチュートリアル
そんで、そっちで WCF 関連の情報を扱っていくつもりです。
Web サイトは XML ベースで構築しようと考えています。
で、そうなると FC2 さんの無料ホームページスペースでは無理なんですよね・・・。
XML ファイル自体は配置することが許可されてますが、肝心の XSLT ファイルが許可されていないみたいなんです orz
Yahoo! ジオシティーズ なら XML も XSLT も許可されているんだけど、広告がデカデカとしててちょっと・・・。
どっかいいトコないかなぁ。
ところで、僕は今まで XML ベースの Web サイトをインターネット上で見かけたことがない気がするんですけど、なんででしょうか?
デザインを完全に統一できるし、変更に強いし、デザインと構造の分離も完全だし、それほど難しくもなさそうなんだから、それなりに普及していてもいい気がするんですけど ^^;
今まで実際に XML ベースの Web サイトを作ったことがないんですけど、もしかして何か落とし穴がある?
ビルド前に実行するコマンド ライン
"%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /u "$(TargetName)"
ビルド後に実行するコマンド ライン
"%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /i "$(TargetFileName)"
これで、ビルドと同時にアセンブリが GAC にインストールされます。
で、なんか僕も PowerShell と何かを連携させたいなとうずうずしてきたので、最近マイブームの WCF を PowerShell で扱ってみようかと思います。
構成はこんな感じ
- WCF サービスは C# で作成 ( どうせだから Visual Studio は使わず )
- WCF のホストは PowerShell
- WCF のクライアントも PowerShell
[ 0. 準備 ]
この記事では、基本的に PowerShell を使ってほとんどの操作を進めていきます。
0-1. 必要なもの
以下の3つがあれば大丈夫なはずです。
- Windows PowerShell 1.0 ( 適切なのを選んでください。 )
- .NET Framework 3.0 ( .NET 3.0 は .NET 2.0 + α です。 )
- .NET Framework 2.0 SDK ( .NET 3.0 入れてからね。 )
0-2. OS が Windows Vistaの場合の注意点
もし、OS が Windows Vistaである場合、管理者権限を要求される場面があります。なので、 OS が Windows Vista の場合は、PowerShell を管理者モードで実行してください。
管理者モードで実行するには、PowerShell のショートカットを右クリックして [ 管理者として実行 ] を選択します。
0-3. 作業フォルダ
C ドライブ直下に work という名前のフォルダを作成し、そこで作業を行うことにします。
以下のコマンドを実行してください。
(New-Item "C:\work1" -itemType "directory") | Set-Location -path {$_.FullName}
これで、 work フォルダが作成され、PowerShell のカレントパスが "C:\work" に移動します。
[ 1. WCF サービス作成 ]
1-0. 概要
まずは WCF サービス をクラス ライブラリとして作成します。
Visual Studio は使わず、メモ帳でコードファイルを書いて、コンパイルにはC# コンパイラをPowerShell 上から扱います。
1-1. WCF サービスの概要
アクセス数をカウントする簡単なサービスです。
Access メソッドを呼び出すたびにカウントされます。
GetCount メソッドでアクセス数を取得できます。
Release メソッドでサービスのインスタンスが解放されます。つまり、カウントがリセットされます。 ( この程度のサービスなら、インスタンスはそのままで、カウントを 0 に設定すれば済みますが^^; )
サービスのインスタンスはクライアント毎に用意されます。つまり、アクセス数は各クライアント毎にカウントされます。
アセンブリ名
Sample.WCFServices.dll
定義する型
Sample.WCFServices.ICounterService インターフェイス
Sample.WCFServices.CounterService クラス
1-2. ICounterService インターフェイスの作成
まず、 ICounterService.cs ファイルを作成し、そのファイルをメモ帳で開きます。
以下のコマンドを実行してください。
$icounterServiceCodeFile = New-Item "ICounterService.cs" -itemType "file";
notepad $icounterServiceCodeFile.FullName;
これで、作業フォルダに ICounterService.cs ファイルが作成され、そのファイルがメモ帳で開かれます。
次に、メモ帳で以下のコードを貼り付けます。
using System;
using System.ServiceModel;
namespace Sample.WCFServices
{
[ServiceContract(Namespace = "http://schemas.sample.net/WCFServices", SessionMode = SessionMode.Allowed)]
public interface ICounterService
{
[OperationContract]
void Access();
[OperationContract]
void Release();
[OperationContract]
int GetCount();
}
}
貼り付けましたら、保存を行い、メモ帳を閉じます。
1-3. CounterService クラスの作成
CounterService.cs ファイルを作成し、そのファイルをメモ帳で開きます。
以下のコマンドを実行してください。
$counterServiceCodeFile = New-Item "CounterService.cs" -itemType "file";
notepad $counterServiceCodeFile.FullName;
これで、作業フォルダに CounterService.cs ファイルが作成され、そのファイルがメモ帳で開かれます。
次に、メモ帳で以下のコードを貼り付けます。
using System;
using System.ServiceModel;
namespace Sample.WCFServices
{
[ServiceBehavior(Namespace = "http://schemas.sample.net/WCFServices", InstanceContextMode = InstanceContextMode.PerSession)]
public class CounterService : ICounterService
{
private int count;
public void Access()
{
try
{
checked
{
this.count++;
}
}
catch (OverflowException)
{
FaultReasonText overflowReasonText = new FaultReasonText("もう数えられない。。。");
FaultReason overflowReason = new FaultReason(overflowReasonText);
FaultCode overflowCode = FaultCode.CreateReceiverFaultCode("overflow", "http://schemas.sample.net/WCFServices/Fault");
throw new FaultException(overflowReason, overflowCode);
}
}
[OperationBehavior(ReleaseInstanceMode = ReleaseInstanceMode.AfterCall)]
public void Release()
{
}
public int GetCount()
{
return this.count;
}
}
}
貼り付けましたら、保存を行い、メモ帳を閉じます。
1-4. アセンブリに署名を付けるためにキー ペアを生成
えっと、アセンブリに署名を付けます。なんで付けるのかというと、今回、クライアントも PowerShell で作成します。その際、ジェネリック クラスを利用し、ジェネリック 引数に ICounterService インターフェイスを指定します。・・・で、どうも PowerShell でジェネリック引数に指定する型のアセンブリは、GAC に登録されている必要があるみたいです。なので、Sample.WCFServices.dll に署名を付けて GAC に登録します。
まぁ、複数のアプリで参照されるアセンブリは GAC に登録するべきですしね。( WSDL を公開して、Svcutil.exe でプロキシ クラスを生成する場合は不要です。でも、今回はそうしません^^; )
以下のコマンドを実行してください。
&"$env:programfiles\Microsoft Visual Studio 8\SDK\v2.0\Bin\sn.exe" -k Sample.WCFServices.snk
このコマンドにより、作業フォルダに Sample.WCFServices.snk という名前のキー ペア ファイルが生成されます。
1-5. コンパイル
1-2 と 1-3 で作成した2つのコードファイルを、クラス ライブラリ形式にコンパイルします。
以下のコマンドを実行してください。
&"$env:windir/Microsoft.NET/Framework/v2.0.50727/csc.exe" /out:"Sample.WCFServices.dll" /target:library /lib:"$env:windir\Microsoft.NET\Framework\v3.0\Windows Communication Foundation" /reference:"System.ServiceModel.dll" /optimize+ /keyfile:"Sample.WCFServices.snk" *.cs;
このコマンドにより、2つのコードファイルがコンパイルされ、作業フォルダに Sample.WCFServices.dll というファイルが出力されます。また、先ほど生成したキー ペアにより、アセンブリに署名が施されます。
1-6. GAC に登録
1-4 で解説したように、今回は WCF サービスのアセンブリを GAC へと登録します。
以下のコマンドを実行してください。
&"$env:programfiles\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /i "Sample.WCFServices.dll"
このコマンドにより、 Sample.WCFService.dll が GAC へと登録されます。
なお、後で Sample.WCFService.dll を GAC から削除する際には、以下のコマンドで削除可能です。 ( 今はまだ削除しないでくださいね。 )
&"$env:programfiles\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /u "Sample.WCFServices"
[ 2. PowerShell で WCF をホスティング ]
ExecuteCounterServiceHosting という関数を定義し、その中でホスティングの一連の処理を行います。ここでは、NetTcpBinding を使うことにします。
以下のコードを全てコピーし、PowerShell に貼り付けてください。
function ExecuteCounterServiceHosting
{
# WCF必須のクラス ライブラリを GAC からロード
[void][System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel");
# 自作の WCF サービス クラス ライブラリを GAC からロード
[void][System.Reflection.Assembly]::LoadWithPartialName("Sample.WCFServices");
# サービス クラスの型情報
$counterServiceType = [Sample.WCFServices.CounterService];
# ベース アドレス
$counterServiceAddress = New-Object "System.Uri" @("net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc");
# バインディングの設定
$counterServiceBinding = New-Object "System.ServiceModel.NetTcpBinding" @([System.ServiceModel.SecurityMode]::None);
$counterServiceBinding.Namespace = "http://schemas.sample.net/WCFServices";
# コントラクトの取得
$counterServiceContractType = [Sample.WCFServices.ICounterService];
# サービス ホスト作成
$counterServiceHost = New-Object "System.ServiceModel.ServiceHost" @($counterServiceType, $counterServiceAddress);
[void]$counterServiceHost.AddServiceEndpoint($counterServiceContractType, $counterServiceBinding, [System.String]::Empty);
# ホスティング 開始
$counterServiceHost.Open();
# 待機
[System.Console]::WriteLine("WCF サービス の ホスティングを開始しました。");
foreach($dispatcher in $counterServiceHost.ChannelDispatchers)
{
[System.Console]::WriteLine("Listening uri: {0}", $dispatcher.Listener.Uri.ToString());
}
[System.Console]::WriteLine("Enter キーを押すと終了します。");
[System.Console]::ReadLine();
# ホスティング 終了
$counterServiceHost.Close();
# エラートラップ
trap
{
if (($counterServiceHost -ne $()) -and ($counterServiceHost.State -eq [System.ServiceModel.CommunicationState]::Opened))
{
$counterServiceHost.Close();
}
break;
}
}
この関数を呼び出せば、PowerShell 上で WCF ホスティングが開始されます。
この関数を呼び出すには、以下のコードを実行してください。
ExecuteCounterServiceHosting;
成功すると、以下のようなメッセージが出力されます。
WCF サービス の ホスティングを開始しました。
Listening uri: net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc
Enter キーを押すと終了します。
ホスティングを終了させるには、Enter キーを押します。 ( でも、まだ押しちゃだめ>< )
[ 3. PowerShell で WCF サービスにアクセス ]
現在開いている PowerShell とは別に、もうひとつ PowerShellを起動してください。
この別 PowerShell が、クライアントを担当します。
ConnectToCounterService という関数を定義し、その中で WCF サービスへのアクセスの一連の処理を行います。
以下のコードを全てコピーし、PowerShell に貼り付けてください。
function ConnectToCounterService
{
# WCF必須のクラス ライブラリ
[void][System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel");
# 自作の WCF サービス クラス ライブラリ
[void][System.Reflection.Assembly]::LoadWithPartialName("Sample.WCFServices");
# CounterService のチャネルを生成
$contractAssemblyQualifiedName = [Sample.WCFServices.ICounterService].AssemblyQualifiedName;
$counterServiceBinding = New-Object "System.ServiceModel.NetTcpBinding" @([System.ServiceModel.SecurityMode]::None);
$counterServiceChannelFactory = New-Object "System.ServiceModel.ChannelFactory``1[[$contractAssemblyQualifiedName]]" @($counterServiceBinding, "net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc");
$counterServiceProxy = $counterServiceChannelFactory.CreateChannel();;
# 選択肢作成
$accessMethodChoice = New-Object "System.Management.Automation.Host.ChoiceDescription" @("&Access", "CounterService サービスの Access メソッドを実行");
$releaseMethodChoice = New-Object "System.Management.Automation.Host.ChoiceDescription" @("&Release", "CounterService サービスの Release メソッドを実行");
$getCountMethodChoice = New-Object "System.Management.Automation.Host.ChoiceDescription" @("&GetCount", "CounterService サービスの GetCount メソッドを実行して戻り値を表示");
$exitChoice = New-Object "System.Management.Automation.Host.ChoiceDescription" @("&Exit", "終了");
$methodChoiceDescription = [System.Management.Automation.Host.ChoiceDescription[]]@($accessMethodChoice, $releaseMethodChoice, $getCountMethodChoice, $exitChoice);
# メソッド選択肢ループ
$run = $True;
while ($run)
{
$result = $Host.UI.PromptFOrChoice("[ 処理選択 ]", [System.String]::Empty, $methodChoiceDescription, 0);
switch ($result)
{
0 {$counterServiceProxy.Access();},
1 {$counterServiceProxy.Release();},
2 {$counterServiceProxy.GetCount();},
3 {$run = $False;}
}
}
# 後処理
# プロキシ オブジェクトを IClientChannel にキャストして CloseしたいんだけどPowerShell のキャスト チェックに弾かれる。
# ChannelFactory の Close メソッドが内部で Close してくれているからまぁいいんだけど^^;
#$counterServiceChannel = [System.ServiceModel.IClientChannel]$counterServiceProxy
#$counterServiceChannel.Close();
$counterServiceChannelFactory.Close();
# エラートラップ
trap
{
if (($counterServiceChannelFactory -ne $()) -and ($counterServiceChannelFactory.State -eq [System.ServiceModel.CommunicationState]::Opened))
{
#if (($counterServiceChannel -ne $()) -and ($counterServiceChannel.State -eq [System.ServiceModel.CommunicationState]::Opened))
#{
# $counterServiceChannel.Close();
#}
$counterServiceChannelFactory.Close();
}
}
}
この関数を呼び出せば、PowerShell 上で CounterService サービスへのアクセスが行えます。
この関数を呼び出すには、以下のコードを実行してください。 ( さっき作成した ExecuteCounterServiceHosting 関数はちゃんと実行中のままにしてありますよね? )
ConnectToCounterService;
成功すると、以下のようなメッセージが出力されます。
[ 処理選択 ]
[A] Access [R] Release [G] GetCount [E] Exit [?] ヘルプ (既定値は "A"):
実行したい処理の頭文字を入力して Enter を押せば、処理が実行されます。各処理の説明を表示するには、 "?" と入力して Enter を押してください。
[ 4. 完成 ]
これで完成です。クライアントを複数用意したりして試してみてください。
[ 5. スクリプト ファイル化 ]
毎回コマンドをコピペじゃあちょっと不便なので、スクリプト ファイルとして保存しておく方法を最後に紹介しておきます。
PowerShell のスクリプト ファイルの拡張子は "ps1" です。
ただ、PowerShell の既定の設定ではスクリプト ファイルの実行が許可されていません。
以下のコマンドを実行すると、ローカルファイルとして保存されているスクリプト ファイルの実行が許可されるように設定が変更されます。
Set-ExecutionPolicy RemoteSigned
このあたりの詳細な解説は、以下のコマンドを実行することで見ることができます。この解説を読んだ上で、上記コマンドを実行するかどうかを判断してください。
get-help about_signing | out-host -paging
で、スクリプト ファイルの実行方法ですが、ファイル名を ( 必ずパス付きで ) 入力して Enter を押すだけです。あと、PowerShell の起動時にコマンドライン引数としてスクリプト ファイルのファイル名を指定してやっても実行されます。