C#と諸々

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

2007/12/09 16:35
SOAP メッセージの検証・変更を行うための PowerShell 関数を作成しました。

【 Set-MessageInspector 関数 】
WCF クライアントが要求メッセージを送信する前、応答メッセージを受信した後の 2 箇所に独自の処理を追加します。
追加した処理からは、要求メッセージまたは応答メッセージの検査・変更を行うことができます。
これにより、例えば、SOAP メッセージをログに記録することができます。

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

・beforeSendRequest
WCF クライアントが要求メッセージを送信する前に行う処理を指定します。
beforeSendRequest に指定する関数内では、要求メッセージ (System.ServiceModel.Channels.Message) を第一引数から取得できます。
また、WCF クライアント オブジェクト チャネル (System.ServiceModel.IClientChannel) を第二引数から取得できます。
関数が戻り値を返した場合、戻り値は afterReceiveReply に指定した関数の第二引数に、相関状態データとして渡されます。

・afterReceiveReply
WCF クライアントが応答メッセージを受信した後に行う処理を指定します。
afterReceiveReply に指定する関数内では、応答メッセージ (System.ServiceModel.Channels.Message) を第一引数から取得できます。
また、相関状態データ (System.Object) を第二引数から取得できます。


[ 戻り値 ]
なし


[ コード ]
function Set-MessageInspector
{
    param ($target, [ScriptBlock]$beforeSendRequest, [ScriptBlock]$afterReceiveReply)
    trap { break; }
   
    function Compile-MessageInspectorCode
    {
        $source =
@"
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;

internal sealed class PSClientMessageInspector : IClientMessageInspector
{
    private static object InvokeScript(ScriptBlock target, params object[] args)
    {
        StringBuilder argsTextBuilder = new StringBuilder();
        if (args != null)
        {
            for (int i = 0; i <= args.Length; i++)
            {
                argsTextBuilder.AppendFormat("`$invokeArgs[{0}] ", i);
            }
        }
        string invokeCommandText = string.Format("`$invokeArgs = `$Input | % {{ `$_; }}; &{{{0}}} {1}", target, argsTextBuilder);
        Command invokeCommand = new Command(invokeCommandText, true, true);
        using (Pipeline invokePipeline = Runspace.DefaultRunspace.CreateNestedPipeline())
        {
            invokePipeline.Commands.Add(invokeCommand);
            Collection<PSObject> invokeResult = invokePipeline.Invoke(args);
            object result = null;
            if ((invokeResult != null) && (invokeResult.Count != 0))
            {
                result = (invokeResult.Count == 1) ? (object)invokeResult[0] : (object)invokeResult;
            }
            return result;
        }
    }
   
    private readonly ScriptBlock _beforeSendRequestScriptBlock;
   
    private readonly ScriptBlock _afterReceiveReplyScriptBlock;
   
    public PSClientMessageInspector(ScriptBlock beforeSendRequestScriptBlock, ScriptBlock afterReceiveReplyScriptBlock)
    {
        this._beforeSendRequestScriptBlock = beforeSendRequestScriptBlock;
        this._afterReceiveReplyScriptBlock = afterReceiveReplyScriptBlock;
    }
   
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if (this._beforeSendRequestScriptBlock == null)
        {
            return null;
        }
        return PSClientMessageInspector.InvokeScript(this._beforeSendRequestScriptBlock, request, channel);
    }

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        if (this._afterReceiveReplyScriptBlock == null)
        {
            return;
        }
        PSClientMessageInspector.InvokeScript(this._afterReceiveReplyScriptBlock, reply, correlationState);
    }
}

public sealed class PSClientMessageInspectorBehavior : IEndpointBehavior
{
    private readonly PSClientMessageInspector _inspector;
   
    public PSClientMessageInspectorBehavior(ScriptBlock beforeSendRequestScriptBlock, ScriptBlock afterReceiveReplyScriptBlock)
    {
        this._inspector = new PSClientMessageInspector(beforeSendRequestScriptBlock, afterReceiveReplyScriptBlock);
    }
   
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(this._inspector);
    }
   
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }
   
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }
   
    public void Validate(ServiceEndpoint endpoint)
    {
    }
}
"@;
        $references =
            @(
                [System.Reflection.Assembly]::LoadWithPartialName("System.ServiceModel").Location,
                [System.Reflection.Assembly]::LoadWithPartialName("System.Management.Automation").Location
            );
        Compile-CSCode $source $references | Out-Null;
    }
   
    $behaviors = $target.Endpoint.Behaviors;
    if ($() -eq $behaviors)
    {
        throw "引数 client が、 System.ServiceModel.ClientBase``1 の派生型、または System.ServiceModel.ChannelFactory``1 の派生型ではありません。";
    }
   
    if ($() -eq ("PSClientMessageInspectorBehavior" -as [Type]))
    {
        Compile-MessageInspectorCode;
    }
    $inspectorBehavior = New-Object "PSClientMessageInspectorBehavior" @($beforeSendRequest, $afterReceiveReply);
    $behaviors.Add($inspectorBehavior);
}



[ 注意 ]
・この関数は、内部で Compile-CSCode 関数 を使用しています。
Compile-CSCode 関数が使用できない場合、この関数は実行に失敗します。

・この関数で取得できる SOAP メッセージは、実際にネットワーク上で通信される SOAP メッセージとは異なる場合がありますので注意してください。
例えば、バインディングにWS-Security が適用されているとネットワーク上で通信される SOAP メッセージは暗号化されますが、この関数で取得した SOAP メッセージは暗号化前もしくは復号後のものになります。
ネットワーク上で実際に通信される SOAP メッセージを調べるには、Microsoft Network Monitor 等のツールを使用してください。


[ 使用例 ]

$client = New-WCFClient Sample.WCF.ServiceClient "C:\Work\WCF\Client.dll.config";
$beforeSendRequest =
    {
        param ($request, $channel)
        return [DateTime]::Now;
    };
$afterReceiveReply =
    {
        param ($reply, $correlationState)
        "requested time: {0}" -f $correlationState | Write-Host;
        $reply | Write-Host;
    };
Set-MessageInspector $client $beforeSendRequest $afterReceiveReply;


このコマンドは、New-WCFClient 関数 を使用して生成されたWCF クライアントのインスタンスに、応答の SOAP メッセージをコンソール出力するための独自の処理を追加します。
応答の SOAP メッセージの出力時には、要求メッセージを送信した日時も出力します。


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

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



2007/12/02 01:42
開発した WCF サービスのちょっとしたテストを行うのに、一々 Visual Studio でプロジェクト作ってサービス参照追加して C# でコード書いてビルドして・・・なんて面倒すぎです。
でも 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 を活用してください。
タグ: .NET C# PowerShell WCF
2007/11/26 00:00
メッセージインスペクタってのを用意してビヘイビアから登録してやれば、WCF のSOAP メッセージのやり取りに独自の処理を挟めそうだ。

IDispatchMessageInspector インターフェイス (System.ServiceModel.Dispatcher) が WCF サービス側、IClientMessageInspector インターフェイス (System.ServiceModel.Dispatcher) が WCF クライアント側。
で、メッセージは Message クラス (System.ServiceModel.Channels) として取得できると。

・・・まだ何も試してないけどw
これを利用して、SOAP メッセージを取得する PowerShell 関数が作れそう (・∀・)ニヤニヤ
タグ: .NET C# WCF
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
2007/10/09 20:03
WCF のクライアントは、サービス モデル メタデータ ユーティリティ ツール (Svcutil.exe) を使用して静的に生成する方法と、ChannelFactory ジェネリック クラス (System.ServiceModel) を使用して動的に生成する方法の2つの方法に大別できます。
静的に生成したクライアントクラスは、ClientBase ジェネリック クラス (System.ServiceModel) を継承します。
動的に生成したクライアントクラスは、IClientChannel インターフェイス (System.ServiceModel) とサービスコントラクトのインターフェイスを実装します。ClientBase ジェネリック クラスは継承していません。

クライアント アーキテクチャ



WCF のクライアントに対して、using ステートメントによる Dispose 呼び出しを行うことは推奨されていないようです。

Using ステートメントに関する問題の回避

using ステートメントによる Dispose 呼び出しを行う代わりに推奨されるクライアントパターンはこちら。

クライアントを使用したサービスの処理



WCF クライアントは、CommunicationException クラス (System.ServiceModel) またはその派生クラスを例外としてスローします。

例外とエラーの処理

エラーの送受信



WCF サービスはセッションを利用して状態を保持することができます。セッションの開始・終了はクライアントによって制御されます。

セッションの使用



WCF のセキュリティはサービスが指定します。クライアントはサービスで指定されたセキュリティに従います。

クライアントのセキュリティ保護



WCF クライアントは、サービスの非同期呼び出しをサポートしています。

方法 : WCF サービス操作を非同期に呼び出す



WCF では双方向に通信を行うサービスを構築できます。

双方向サービス

クライアントのランタイム動作の指定



ここに掲載したページは、だいたい以下のページから辿っていけます。

WCF クライアントの概要



その他。

状態変更の理解
タグ: .NET C# WCF
2007/07/14 11:33
MSDN マガジン 8月号に、Juval Lowy 氏の「宣言型の WCF セキュリティ」 という記事が掲載されています。
この記事では、以下の5つのシナリオで適用すべきセキュリティについての解説と、それを容易に適用できるように Juval Lowy 氏が作成した、「セキュリティ フレームワーク」 についての解説が記述されています。
  • イントラネット アプリケーション
  • インターネット アプリケーション
  • 企業間アプリケーション
  • 匿名アプリケーション
  • セキュリティなし

Juval Lowy 氏が作成した 「セキュリティ フレームワーク」 は、ソースコード及びサンプルコードのセットでダウンロードできます。

基礎: 宣言型の WCF セキュリティ -- MSDN Magazine, August 2007
セキュリティ フレームワーク 及び サンプルコードのダウンロード

タグ: .NET C# WCF
2007/06/17 15:20
WCF では、ServiceMetadataBehavior を使用すると、ホストが開かれる時に WSDL を自動で生成して公開してくれます。この処理を行っているのは WsdlExporter クラス (System.ServiceModel.Description) です。

WsdlExporter クラスには、開発者が WSDL のエクスポートをカスタマイズできるような仕組みが備わっています。
WSDL のエクスポートをカスタマイズするには、IWsdlExportExtension インターフェイス(System.ServiceModel.Description) を実装したクラスを用意します。
更にこのクラスは、コントラクトビヘイビア、エンドポイントビヘイビア、オペレーションビヘイビア、バインディング要素のいずれかとして実装します。 ( これら特有のインターフェイスを実装する際は、特になんらかの処理を行う必要はありません。例えば、コントラクトビヘイビアならIContractBehavior インターフェイスを実装しますが、このインターフェイスの各メソッドの中身は空で大丈夫です。なお、サービスビヘイビアは含まれていないことに注意します。 )

IWsdlExportExtension インターフェイスには次の二つのメソッドが用意されています。
各メソッドは、第一引数で呼び出し元の WsdlExporter を受け取り、第二引数でカスタマイズを行うためのオブジェクトを受け取ります。

ExportContract メソッド
コントラクトに対して生成される WSDL をカスタマイズします。
第二引数で受け取った WsdlContractConversionContext クラス (System.ServiceModel.Description) のオブジェクトに対してカスタマイズを行います。

ExportEndpoint メソッド
エンドポイントに対して生成される WSDL をカスタマイズします。
第二引数で受け取った WsdlEndpointConversionContext クラス (System.ServiceModel.Description) のオブジェクトに対してカスタマイズを行います。


[ 関連記事 ]
C#と諸々 - 独自のビヘイビアを定義する
C#と諸々 - 独自のビヘイビアを定義する2


[ MSDN 関連記事 ]
メタデータ システムの拡張
WCF 拡張に対するカスタム メタデータのエクスポート
方法 : カスタム WSDL をエクスポートする
ServiceDescription と WSDL 参照
System.ServiceModel.Description 名前空間
System.Web.Services.Description 名前空間

タグ: .NET C# WCF WSDL
2007/04/23 23:08
ASP.NET では、Global.asax で例外を一元的にハンドリングするためのエラー ハンドラを用意することができました。しかし、WCF では残念ながら Global.asax がサポートされていません。 ( ASP.NET 互換モードでも非サポート。 )

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 クラスを属性として付加すれば、サービスにエラー ハンドラが適用されます。
タグ: .NET C# WCF 例外処理
2007/04/22 23:28
当初の予定では、一元的なエラーハンドリングの方法を紹介することになっていましたが、その前に、サービスのカスタム属性として適用する独自のビヘイビアを定義する方法を紹介します。
コンフィギュレーションやコードから、サービスホストに対してビヘイビアを適用する方法は、こちらの「独自のビヘイビアを定義する」という記事を参照してください。

ビヘイビアには、サービス ビヘイビア、エンドポイント ビヘイビア、コントラクト ビヘイビア、オペレーション ビヘイビアの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
{
    /*...*/
}
タグ: .NET C# WCF
2007/04/16 01:02
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

タグ: .NET C# WCF