でも PowerShell を使えば、そんな面倒なことをせずとも、WCF サービスを思いのままに呼び出せます。
論より証拠と言いますし、まずは実感してみてください。
[ 必要なもの ]
WCF 及び PowerShell を利用するためには、以下の 3 つが必要です。
.NET Framework ( バージョン 3.0 以降であれば OK です。 )
Windows SDK for Windows Vista
Windows PowerShell 1.0 ( 適切なのを選んでください。 )
[ WCF サービス ]
本来は、C# で WCF サービスを実装して IIS ホスティングなりセルフホスティングなりするんですが、この記事ではどうせなので PowerShell 上で作成 & ホスティングします。通信プロトコルには net.tcp プロトコルを使用することとします。また、WSDL を公開するために Http プロトコルも使用します。
まず、PowerShell を管理者権限で起動してください。管理者権限で起動する理由は、ホスティングを行う際に Http プロトコルを利用するためです。管理者権限が無いとエラーになってしまいます。
PowerShell を起動したら、なんも考えず、以下のコードを全てコピーして PowerShell にペーストしてください。
CounterService のホスティング
function Compile-CSCode
{
param([String[]]$sources, [string[]]$referenceAssemblyNames, [String]$assemblyName)
trap { break; }
$tempDirectory = "$Env:Temp\PSCompile";
if (!(Test-Path $tempDirectory))
{
New-Item $tempDirectory -ItemType "Directory" | Out-Null;
}
Remove-Item "$tempDirectory\*" -Recurse -Force;
$codeProvider = New-Object "Microsoft.CSharp.CSharpCodeProvider";
$parameters = New-Object "System.CodeDom.Compiler.CompilerParameters";
$parameters.GenerateExecutable = $False;
$parameters.GenerateInMemory = $True;
$parameters.TreatWarningsAsErrors = $False;
if (($() -ne $assemblyName) -and (0 -lt $assemblyName.Length))
{
$parameters.OutputAssembly = "$tempDirectory\$assemblyName";
}
if ($() -ne $referenceAssemblyNames)
{
$parameters.ReferencedAssemblies.AddRange($referenceAssemblyNames);
}
$result = $codeProvider.CompileAssemblyFromSource($parameters, $sources);
if ($result.Errors.Count -ne 0)
{
$errorMessage = "コンパイルエラーが発生しました。`n";
foreach ($err in $result.Errors)
{
$errorMessage += "{0}:{1}`n Row:{2} Column:{3}`n" -f @($err.ErrorNumber, $err.ErrorText, $err.Line, $err.Column);
}
throw New-Object "ArgumentException" @($errorMessage);
}
return $result.CompiledAssembly;
}
function Compile-CounterService
{
$iCounterServiceCode =
@"
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();
}
}
"@;
$counterServiceCode =
@"
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 CounterService()
{
Console.WriteLine("インスタンスが生成されました。");
}
public void Access()
{
Console.WriteLine("Access メソッドが呼び出されました。");
if (10 < this._count)
{
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);
}
this._count++;
}
[OperationBehavior(ReleaseInstanceMode = ReleaseInstanceMode.AfterCall)]
public void Release()
{
Console.WriteLine("Release メソッドが呼び出されました。インスタンスは破棄されます。");
}
public int GetCount()
{
Console.WriteLine("GetCount メソッドが呼び出されました。");
return this._count;
}
}
}
"@;
$referenceAssemblyNames =
@(
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel").Location,
[System.Reflection.Assembly]::LoadWithPartialName("System.Runtime.Serialization").Location
);
Compile-CSCode @($iCounterServiceCode, $counterServiceCode) $referenceAssemblyNames | Out-Null;
}
function Execute-CounterServiceHosting
{
trap
{
if ($() -ne $counterServiceHost)
{
$counterServiceHost.Abort();
$counterServiceHost.Close();
}
break;
}
if (("Sample.WCFServices.CounterService" -as [Type]) -eq $())
{
Compile-CounterService;
}
$counterServiceType = [Sample.WCFServices.CounterService];
$counterServiceBinding = New-Object "System.ServiceModel.NetTcpBinding" @([System.ServiceModel.SecurityMode]::None);
$counterServiceBinding.Namespace = "http://schemas.sample.net/WCFServices";
$counterServiceContractType = [Sample.WCFServices.ICounterService];
$metadataBehavior = New-Object "System.ServiceModel.Description.ServiceMetadataBehavior";
$metadataBehavior.HttpGetEnabled = $True;
$metadataBehavior.HttpGetUrl = "http://localhost/Sample/WCFServices/CounterService.wsdl";
$counterServiceAddress = [Uri]"net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc";
$counterServiceHost = New-Object "System.ServiceModel.ServiceHost" @($counterServiceType, $counterServiceAddress);
$counterServiceHost.AddServiceEndpoint($counterServiceContractType, $counterServiceBinding, [System.String]::Empty) | Out-Null;
$counterServiceHost.Description.Behaviors.Add($metadataBehavior);
$counterServiceHost.Open();
"WCF サービス の ホスティングを開始しました。";
foreach($dispatcher in $counterServiceHost.ChannelDispatchers)
{
"Listening uri: $($dispatcher.Listener.Uri)";
}
"Enter キーを押すと終了します。";
Read-Host | Out-Null;
$counterServiceHost.Close();
}
Execute-CounterServiceHosting;
以下のメッセージが出力されれば成功です。
WCF サービス の ホスティングを開始しました。
Listening uri: net.tcp://localhost:10000/Sample/WCFServices/CounterService.svc
Listening uri: http://localhost/Sample/WCFServices/CounterService.wsdl
Enter キーを押すと終了します。
これで、「CounterService」 という WCF サービスがホスティングされました。
「CounterService」 は、以下の 3 つのオペレーションコントラクトを提供します。
void Access() ・・・ 呼び出すたびにカウントが +1 されます。
int GetCount() ・・・ 現在のカウントを取得します。
void Release() ・・・ サービスインスタンスを解放します。 ( カウントが 0 にリセットされます。 )
Enter キーを押してしまうとホスティングが終了してしまうので、そのまま何もせずに PowerShell 立ち上げておいてください。
[ WCF クライアント ]
ここからが本題の WCF クライアントです。
まず、準備として PowerShell 上でWCF クライアントを扱うための関数 ( 後述 ) を定義します。
既に立ち上がっている PowerShell とは別に、もう一つ PowerShell を起動してください。こちらは管理者権限で起動する必要はありません。
以下のコードを全てコピーし、今起動した方の PowerShell にペーストしてください。
WCF クライアントを扱うための 4 つの関数
function global:New-WCFClientContents
{
param ([String[]]$metadataPaths, [String]$outputDirectory, [String]$assemblyName = "Client", [string[]]$referenceAssemblyNames)
trap { break; }
[System.Reflection.Assembly]::LoadWithPartialName("System.Configuration") | Out-Null;
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
if ("$metadataPaths".Trim().Length -eq 0) { throw "引数 metadataPaths が不正です。"; }
if ("$outputDirectory".Trim().Length -eq 0) { throw "引数 outputDirectory が不正です。"; }
if ("$assemblyName".Trim().Length -eq 0) { throw "引数 assemblyName が不正です。"; }
# SvcUtil の実行
$svcUtilExe = "$Env:ProgramFiles\Microsoft SDKs\Windows\v6.0\Bin\SvcUtil.exe";
$workDirectory = "$outputDirectory\$([Guid]::NewGuid())"
$configFileName = "$assemblyName.dll.config";
&$svcUtilExe /target:"Code" "$metadataPaths" /directory:"$workDirectory" /config:"$configFileName" /noLogo | Out-Null;
if (!(Test-Path $workDirectory)) { throw "WCF クライアントの作成に失敗しました。;" }
# クライアントコードのコンパイル
$sourceFiles = Get-ChildItem $workDirectory -Include "*.cs" -recurse;
$sourcePaths = ($sourceFiles | % { $_.FullName; });
$codeProvider = New-Object "Microsoft.CSharp.CSharpCodeProvider";
$parameters = New-Object "System.CodeDom.Compiler.CompilerParameters";
$parameters.GenerateExecutable = $False;
$parameters.GenerateInMemory = $False;
$parameters.OutputAssembly = "$outputDirectory\$assemblyName.dll";
$referenceAssemblyNames +=
@(
[System.Reflection.Assembly]::LoadWithPartialName("System").Location,
[System.Reflection.Assembly]::LoadWithPartialName("System.Data").Location,
[System.Reflection.Assembly]::LoadWithPartialName("System.Xml").Location,
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel").Location,
[System.Reflection.Assembly]::LoadWithPartialName("System.IdentityModel").Location,
[System.Reflection.Assembly]::LoadWithPartialName("System.Runtime.Serialization").Location
);
$parameters.ReferencedAssemblies.AddRange($referenceAssemblyNames);
$compilerResult = $codeProvider.CompileAssemblyFromFile($parameters, $sourcePaths);
if ($compilerResult.Errors.Count -ne 0)
{
$errorMessage = "コンパイルエラーが発生しました。`n";
foreach ($err in $compilerResult.Errors)
{
$errorMessage += "{0}:{1}`n Row:{2} Column:{3}`n" -f @($err.ErrorNumber, $err.ErrorText, $err.Line, $err.Column);
}
throw New-Object "ArgumentException" @($errorMessage);
}
# コンテンツの配置
Copy-Item -path "$workDirectory\$configFileName" -Destination "$outputDirectory\$configFileName";
Remove-Item $workDirectory -Recurse -Force;
# 戻り値の設定
$assembly = $compilerResult.CompiledAssembly;
$types = $assembly.GetTypes();
[Type[]]$clientClasses = $types | ? { ($_.BaseType.IsGenericType) -and ($_.BaseType.GetGenericTypeDefinition() -eq [System.ServiceModel.ClientBase``1]); };
[Type[]]$serviceContractInterfaces = $types | ? { $_.IsDefined([System.ServiceModel.ServiceContractAttribute], $False); };
$assemblyPath = "$outputDirectory\$assemblyName.dll";
$configurationPath = "$outputDirectory\$configFileName";
$configuration = [System.Configuration.ConfigurationManager]::OpenExeConfiguration($assemblyPath);
$serviceModelSectionGroup = [System.ServiceModel.Configuration.ServiceModelSectionGroup]::GetSectionGroup($configuration);
[String[]]$endpointConfigurationNames = ($serviceModelSectionGroup.Client.Endpoints | % { $_.Name });
$result =
@{
Assembly = $assembly;
ClientClasses = $clientClasses;
ServiceContractInterfaces = $serviceContractInterfaces;
AssemblyPath = $assemblyPath;
ConfigurationPath = $configurationPath;
EndpointConfigurationNames = $endpointConfigurationNames;
};
return $result;
}
function global:New-WCFClient
{
param ([Type]$clientClass, [String] $configurationPath, [String]$endpointConfigurationName)
trap { break; }
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
if (($() -eq $clientClass.BaseType) -or (!$clientClass.BaseType.IsGenericType) -or ($clientClass.BaseType.GetGenericTypeDefinition() -ne [System.ServiceModel.ClientBase``1]))
{
throw "$clientClass は、System.ServiceModel.ClientBase ジェネリッククラスの派生クラスではありません。";
}
if ([String]::IsNullOrEmpty($configurationPath))
{
$configurationPath = $clientClass.Assembly.Location + ".config";
}
return New-WCFClientOrFactory $clientClass $configurationPath $endpointConfigurationName;
}
function global:New-WCFChannelFactory
{
param ([Type]$serviceContractInterface, [String] $configurationPath, [String]$endpointConfigurationName)
trap { break; }
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
if ((!$serviceContractInterface.IsInterface) -or (!$serviceContractInterface.IsDefined([System.ServiceModel.ServiceContractAttribute], $False)))
{
throw "$serviceContractInterface は、サービスコントラクトではありません。";
}
if ([String]::IsNullOrEmpty($configurationPath))
{
$configurationPath = $serviceContractInterface.Assembly.Location + ".config";
}
$factoryType = [System.ServiceModel.ChannelFactory``1].MakeGenericType($serviceContractInterface);
return New-WCFClientOrFactory $factoryType $configurationPath $endpointConfigurationName;
}
function global:New-WCFClientOrFactory
{
param ([Type]$clientOrFactoryType, [String] $configurationPath, [String]$endpointConfigurationName)
trap { break; }
[System.Reflection.Assembly]::LoadWithPartialName("System.Configuration") | Out-Null;
$serviceModelAssembly = [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel");
# コンフィギュレーションの取得
$clientConfigurationMap = New-Object "System.Configuration.ExeConfigurationFileMap";
$clientConfigurationMap.ExeConfigFilename = $configurationPath;
$clientConfiguration = [System.Configuration.ConfigurationManager]::OpenMappedExeConfiguration($clientConfigurationMap, "None");
$clientServiceModelSectionGroup = [System.ServiceModel.Configuration.ServiceModelSectionGroup]::GetSectionGroup($clientConfiguration);
# エンドポイント要素の取得
if ($clientServiceModelSectionGroup.Client.Endpoints.Count -eq 0) { throw "コンフィギュレーションにエンドポイント要素が存在しません。"; }
if ([String]::IsNullOrEmpty($endpointConfigurationName))
{
if (1 -lt $clientServiceModelSectionGroup.Client.Endpoints.Count) { throw "コンフィギュレーションにエンドポイント要素が複数存在します。"; }
$endpointElement = $clientServiceModelSectionGroup.Client.Endpoints[0];
}
else
{
$endpointElement = $clientServiceModelSectionGroup.Client.Endpoints | ? { $_.Name -eq $endpointConfigurationName; };
if ($() -eq $endpointElement) { throw "コンフィギュレーションに名前が `"$endpointConfigurationName`" のエンドポイント要素が存在しません。。" }
}
# エンドポイントアドレスの取得
$configLoaderType = $serviceModelAssembly.GetType("System.ServiceModel.Description.ConfigLoader", $False, $True);
$flagsByLoadIdentityMethod = [System.Reflection.BindingFlags]([System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Static);
$loadIdentityMethod = $configLoaderType.GetMethod("LoadIdentity", $flagsByLoadIdentityMethod);
&{
trap [System.Reflection.TargetInvocationException] { throw "コンフィギュレーションのエンドポイント ID の指定が不正です。`n$($_.Exception.InnerException)"; }
$identity = $loadIdentityMethod.Invoke($(), @($endpointElement.Identity));
};
$endpointAddress = New-Object "System.ServiceModel.EndpointAddress" @($endpointElement.Address, $identity, $endpointElement.Headers.Headers);
# バインディング設定の取得
if ([String]::IsNullOrEmpty($endpointElement.Binding)) { throw "コンフィギュレーションのエンドポイント要素の binding 属性が不正です。"; }
$bindingSetElement = $clientServiceModelSectionGroup.Bindings[$endpointElement.Binding];
$bindingType = $bindingSetElement.BindingType;
$binding = New-Object $bindingType;
if (![String]::IsNullOrEmpty($endpointElement.BindingConfiguration))
{
$bindingConfigurationElement = $bindingSetElement.ConfiguredBindings | ? { $_.Name -eq $endpointElement.BindingConfiguration; };
if ($() -eq $bindingConfigurationElement) { throw "コンフィギュレーションのエンドポイント要素の bindingConfiguration 属性が不正です。"; }
$bindingConfigurationElement.ApplyConfiguration($binding)
}
# クライアントオブジェクトまたはファクトリオブジェクトの生成
$clientOrFactory = [Activator]::CreateInstance($clientOrFactoryType, @($binding.PSObject.BaseObject, $endpointAddress.PSObject.BaseObject));
if ($clientOrFactory.Endpoint.Contract.Name -ne $endpointElement.Contract)
{
throw "`"$endpointConfigurationName`" のエンドポイント要素はコントラクトが `"$($client.Endpoint.Contract.Name)`" ではありません。";
}
# エンドポイントビヘイビアの適用
if (![String]::IsNullOrEmpty($endpointElement.BehaviorConfiguration))
{
$endpointBehaviorElement = $clientServiceModelSectionGroup.Behaviors.EndpointBehaviors[$endpointElement.BehaviorConfiguration];
$flagsByCreateBehaviorMethod = [System.Reflection.BindingFlags]([System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic);
$createBehaviorMethod = [System.ServiceModel.Configuration.BehaviorExtensionElement].GetMethod("CreateBehavior", $flagsByCreateBehaviorMethod);
$endpointBehaviors = $endpointBehaviorElement | % { $createBehaviorMethod.Invoke($_, $()); };
$endpointBehaviors | % { $clientOrFactory.Endpoint.Behaviors.Add($_); };
}
return $clientOrFactory;
}
function global:Get-OperationContract
{
param ($target)
trap { break; }
[System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel") | Out-Null;
if ($target -isnot [Type])
{
if ($() -ne $target.GetType)
{
$targetType = $target.GetType();
}
else
{
$targetType = [Type]"$target";
}
}
else
{
$targetType = $target;
}
$serviceContractAttributeClass = [System.ServiceModel.ServiceContractAttribute];
if (($targetType.IsInterface) -and ($targetType.IsDefined($serviceContractAttributeClass, $False)))
{
$contractInterface = $targetType;
}
else
{
$contractInterface = $targetType.GetInterfaces() | ? {$_.IsDefined($serviceContractAttributeClass, $False);}
if ($() -eq $contractInterface) { return; }
}
$operationContractAttributeClass = [System.ServiceModel.OperationContractAttribute];
$operationContractMethods = $contractInterface.GetMethods() | ? { $_.IsDefined($operationContractAttributeClass, $False); };
if ($() -ne $operationContractMethods)
{
$operationContractTexts = $operationContractMethods | % { $_.ToString() };
}
return $operationContractTexts;
}
これでクライアント側の準備は完了です。
ここからは、最初に起動した方の PowerShell でホスティングされている 「CounterService」 を利用するサンプルコードになります。
まず、以下のコマンドを実行すると、New-WCFClientContents 関数によって、( 「CounterService」 が公開している WSDL を元に ) クライアントクラスがコンパイルされて "C:\work\wcf" というフォルダ内に作成されます。また、コンフィギュレーションも一緒に作成されます。また、この関数の戻り値から、作成されたコンテンツの情報も取得できます。
CounterService のクライアントコンテンツを作成
$clientContents = New-WCFClientContents "http://localhost/Sample/WCFServices/CounterService.wsdl" "C:\work\wcf" "CounterServiceClient";
以下のコマンドを実行すると、今コンパイルされた クライアントクラスのインスタンスが、 New-WCFClient 関数によって、 PowerShell 上に生成されます。
CounterService のクライアントのインスタンス生成
$client = New-WCFClient $clientContents.ClientClasses[0] $clientContents.ConfigurationPath $clientContents.EndpointConfigurationNames[0];
これで、$client 変数を使用して WCF サービスを利用することができるようになりました。
以下のコマンドを実行すれば、「CounterService」 を利用できていることがわかります。
CounterService にアクセス
$client.Open();
$client.GetCount();
$client.Access();
$client.GetCount();
$client.Release();
$client.GetCount();
$client.Close();
New-WCFChannelFactory 関数を使用すれば、サービスコントラクトインターフェイスから ChannelFactory<T> クラスを生成することもできます。
CounterService 用のチャネルファクトリの生成
$channelFactory = New-WCFChannelFactory ([ICounterService]) "C:\work\wcf\CounterServiceClient.dll.config";
チャネルファクトリにてオープン・クローズ及びクライアントの生成を行えば、先ほどのクライアントオブジェクトと同様に WCF サービスが利用できます。
チャネルファクトリの使用
$channelFactory.Open();
$client = $channelFactory.CreateChannel();
$client.GetCount();
$client.Access();
$client.GetCount();
$client.Release();
$client.GetCount();
$channelFactory.Close();
[ 使用した関数について ]
先ほど、クライアント側の「準備」として、PowerShell 上でWCF クライアントを扱うための関数を定義しました。そして、その関数を使用して WCF クライアントの作成等を行ってきました。
これらの関数は、私が自作した関数で、こちらの記事で紹介しています。詳細な説明を載せていますので、ご覧ください。
また、私が自作した全ての関数 ( WCF 関連以外のものも含む ) は、こちらの記事からダウンロードすることができます。
今回は、説明を簡単にするために、「準備」にコピペという手段を取りましたが、PowerShell の「プロファイル」という機能 ( こことここを参照 ) を利用すれば、一々コピペする必要もなくなります。つまり、最初から New-WCFClientContents 関数や New-Client 関数を使用できるわけです。
PowerShell を使えば、Visual Studio を立ち上げる必要もビルドする必要もありません。PowerShell にやりたいことを記述して Enter を押すだけで即実行されます。WCF 開発には是非 PowerShell を活用してください。
トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/185-292e33ef