C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2007/11/18 16:05
WCF では通常、エンドポイントやバインディング等の設定をコンフィギュレーションに記述します。しかし、コンフィギュレーションは実行ファイルと関連付けがされており、PowerShell との相性は最悪です。PowerShell の実行ファイルは PowerShell.exe ですので、PowerShell.exe.config というファイルが関連付けられますが、WCF クライアントを利用するために PowerShell.exe.config を用意してそこに WCF の設定を記述する、というのはさすがにナンセンスです。

そこで、そんなことをせずとも任意のコンフィギュレーションを使用して WCF クライアントを簡単に利用するための関数を作成しました。また、他にも WCF クライアントを利用する上で役立つ関数を作成しました。
作成した関数は全部で 4 つです。


New-WCFClientContents 関数
WSDL を元に、クライアントコンテンツを生成します。クライアントコンテンツには、WCF クライアントを含むアセンブリと WCF クライアントのコンフィギュレーションが含まれます。
内部的には、SvcUtil.exe を使用してコードを生成し、その後コンパイルを行っています。

New-WCFClient 関数
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) をインスタンス化します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。

New-WCFChannelFactory 関数
指定したサービスコントラクトのためのチャネルファクトリのインスタンスを生成します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。
この関数は主に、SvcUtil.exe を使用せず ChannelFactory ジェネリック クラスを使用して WCF クライアントを利用したい、という時に役立ちます。

Get-OperationContract 関数
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトから、オペレーションコントラクトの一覧 ( String 配列 ) を取得します。


基本は、New-WCFClientContents 関数でクライアントコンテンツを作成し、New-WCFClient 関数でクライアントオブジェクトを生成、その後オペレーションコントラクトを呼び出したり、Get-OperationContract でどんなオペレーションコントラクトがあるかを確認したり、といった使い方になります。
しかし、それぞれの関数は独立しています。例えば、New-WCFClientContents 関数を使わずに生成したクライアントコンテンツに対しても、New-WCFClient 関数を利用することができます。

なお、New-WCFClient 関数と New-WCFChannelFactory 関数は、両方とも、後述の New-WCFClientOrFactory 関数を内部で使用しています。


【 New-WCFClientContents 関数 】
WSDL を元に、クライアントコンテンツを生成します。クライアントコンテンツには、WCF クライアントを含むアセンブリと WCF クライアントのコンフィギュレーションが含まれます。
内部的には、SvcUtil.exe を使用してコードを生成し、その後コンパイルを行っています。

[ パラメータ ]
・metadataPaths
WSDL の URL またはローカルパスを指定します。ローカルパスで指定する場合、WSDL 以外のメタデータドキュメントのパスも全て指定する必要があります。

・outputDirectory
コンテンツの出力先ディレクトリのパスを指定します。

・assemblyName
生成されるアセンブリ名 ( 拡張子は含めない ) を指定します。
省略した場合は、"Client" という名前が付けられます。

・referenceAssemblyNames
アセンブリの参照設定を指定します。
基本的なアセンブリは既定で追加されますので、通常このパラメータは省略します。

[ 戻り値 ]
作成されたコンテンツに関する情報を格納するハッシュテーブルを返します。各キーの説明を次に記述します。

・Assembly
アセンブリオブジェクト。

・ClientClasses
WCF クライアントクラスの配列。

・ServiceContractInterfaces
サービスコントラクトインターフェイスの配列。

・AssemblyPath
アセンブリのローカルパス。

・ConfigurationPath
コンフィギュレーションのローカルパス。

・EndpointConfigurationNames
コンフィギュレーション内に存在するエンドポイント設定の名前の配列。

[ コード ]
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;
}




【 New-WCFClient 関数 】
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) をインスタンス化します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。

[ パラメータ ]
・clientClass
SvcUtil.exe が生成したクライアントクラス ( ClientBase ジェネリッククラスの派生クラス ) を指定します。

・configurationPath
コンフィギュレーションのローカルパスを指定します。
省略した場合、引数 clientClass が定義されているアセンブリに対応付けられているコンフィギュレーションが使用されます。例えば、引数 clientClass が定義されているアセンブリが "Client.dll"  の場合、 そのアセンブリと同じディレクトリ内にある "Client.dll.config" が使用されます。

・endpointConfigurationName
コンフィギュレーション内のエンドポイント要素を示す名前を指定します。
コンフィギュレーション内にエンドポイント要素が一つしかない場合はこのパラメータを省略することができます。

[ 戻り値 ]
クライアントクラスのインスタンスを返します。

[ コード ]
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;
}


※この関数は、後述の New-WCFClientOrFactory 関数を内部で使用しています。



【 New-WCFChannelFactory 関数 】
指定したサービスコントラクトのためのチャネルファクトリのインスタンスを生成します。エンドポイントやバインディングの設定には任意のコンフィギュレーションを指定できます。
この関数は主に、SvcUtil.exe を使用せず ChannelFactory ジェネリック クラスを使用して WCF クライアントを利用したい、という時に役立ちます。

[ パラメータ ]
・serviceContractInterface
サービスコントラクトインターフェイスを指定します。

・configurationPath
コンフィギュレーションのローカルパスを指定します。
省略した場合、引数 clientClass が定義されているアセンブリに対応付けられているコンフィギュレーションが使用されます。例えば、引数 clientClass が定義されているアセンブリが "Client.dll"  の場合、 そのアセンブリと同じディレクトリ内にある "Client.dll.config" が使用されます。

・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;
}


※この関数は、後述の New-WCFClientOrFactory 関数を内部で使用しています。



【 New-WCFClientOrFactory 関数 】
New-WCFClient 関数、New-WCFChannelFactory 関数が内部で使用する関数です。つまり、New-WCFClient 関数、New-WCFChannelFactory 関数を使用するにはこの関数が必要となりますが、この関数を利用者が直接呼び出すことはありません。

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;
}




【 Get-OperationContract 関数 】
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトから、オペレーションコントラクトの一覧を取得します。

[ パラメータ ]
・target
クライアントオブジェクト、またはクライアントクラス、またはサービスコントラクトを指定します。

[ 戻り値 ]
オペレーションコントラクトの一覧を返します。

[ コード ]
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;
}




【 関連記事 】
WCF サービス開発には PowerShell を活用しよう


【 ダウンロード 】
自作の PowerShell 関数は、以下の記事からまとめてダウンロードできます。

YokoKen.PowerShell.Scripts
スポンサーサイト