これを解決する WCF の方法は次のとおりです。
- 必要なデータを含むカスタム コンテキスト オブジェクトを作成する
- データを入力するカスタム動作を作成する
- 動作をサービスに適用する
これには、複数の WCF 拡張ポイントにプラグインする必要があります。コツをつかめば難しいことではありませんが、実装する必要があるすべてのインターフェイスがあるため (メソッドの実装が空の場合でも)、大量の入力が必要になります。これが例です。
最初に単純なサービスを定義します。
[ServiceContract]
public interface ISimple
{
[OperationContract(IsOneWay = true)]
void OneWay();
[OperationContract]
void Default();
}
[OneWayContract]
public class SimpleService : ISimple
{
//[OneWayOperation] // uncomment to Add context data on the operation level instead on contract.
public void OneWay()
{
Console.WriteLine("OneWay() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
}
public void Default()
{
Console.WriteLine("Default() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
}
}
カスタム コンテキスト オブジェクトを使用して、必要な情報を格納します。この場合、操作が IsOneWay の場合に true になる bool です。WCF InstanceContext をラップしているため、実際にサービスをホストせずに単体テストが可能であることに注意してください。このブログから取得したカスタム コンテキストを作成する方法:
public class OneWayContext : IExtension<InstanceContext>
{
public OneWayContext()
{
// if not set, default to false.
IsOneWay = false;
}
public bool IsOneWay { get; set; }
public static OneWayContext Current
{
get
{
OneWayContext context = OperationContext.Current.InstanceContext.Extensions.Find<OneWayContext>();
if (context == null)
{
context = new OneWayContext();
OperationContext.Current.InstanceContext.Extensions.Add(context);
}
return context;
}
}
public void Attach(InstanceContext owner) { }
public void Detach(InstanceContext owner) { }
}
OperationInvoker を作成して、カスタム コンテキストを OperationContext に追加します。WCF OperationInvoker を挿入すると、コール スタックに配置されることに注意してください。したがって、処理しないすべての呼び出しは、フレームワークの「内部」OperationInvoker に渡す必要があります。
public class OneWayBehavior : IOperationInvoker
{
IOperationInvoker innerOperationInvoker;
public readonly bool isOneWay;
public OneWayBehavior(IOperationInvoker innerOperationInvoker, bool isOneWay)
{
this.isOneWay = isOneWay;
this.innerOperationInvoker = innerOperationInvoker;
}
public object[] AllocateInputs()
{
return innerOperationInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Everytime the operation is invoked, add IsOneWay information to the context.
OneWayContext.Current.IsOneWay = this.isOneWay;
return innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
}
public bool IsSynchronous
{
get { return innerOperationInvoker.IsSynchronous; }
}
}
次に、新しい動作をコントラクトに適用します。[OneWayContract] 属性は、操作動作を適用する WCF コントラクト動作を適用します。操作レベルで動作を適用することもできます。
OperationDescription は、既に設定されている WCF 構造に必要なすべての情報を提供することに注意してください。反射に頼る必要はありません。バインディングに依存しません。
public class OneWayOperationAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
// grab "IsOneWay" from the operation description and pass on the behavior's constructor.
dispatchOperation.Invoker = new OneWayBehavior(dispatchOperation.Invoker, operationDescription.IsOneWay);
}
public void Validate(OperationDescription operationDescription)
{
}
}
public class OneWayContractAttribute : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
foreach (OperationDescription operation in contractDescription.Operations)
{
operation.OperationBehaviors.Add(new OneWayOperationAttribute());
}
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
}
ここで、簡単なテストを実行します。
public static class Program
{
static void Main(string[] args)
{
ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
simpleHost.Open();
ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
ISimple proxy = factory.CreateChannel();
proxy.OneWay();
proxy.Default();
Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
Console.ReadLine();
((ICommunicationObject)proxy).Shutdown();
simpleHost.Shutdown();
}
}
出力は次のようになります。
Default() is marked IsOneWay:False
OneWay() is marked IsOneWay:True
Press ENTER to close the host once you see 'ALL DONE'.
すべての抽象化が維持されていることに注意してください。サービスは、動作によって提供されるコンテキスト オブジェクトのみに依存します。これは、属性によってサービスへの依存関係として明確にマークされます。
例[OneWayContract]
では、サービス クラスを配置します。しかし、それを にも適用できるはずです[ServiceContract]
。
完全を期すために、これは貼り付けて実行できる 1 つのコンソール アプリとしてのコード サンプル全体のコピーです。
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace ConsoleWCF
{
[ServiceContract]
public interface ISimple
{
[OperationContract(IsOneWay = true)]
void OneWay();
[OperationContract]
void Default();
}
[OneWayContract]
public class SimpleService : ISimple
{
//[OneWayOperation] // uncomment to Add context data on the operation level instead on contract.
public void OneWay()
{
Console.WriteLine("OneWay() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
}
public void Default()
{
Console.WriteLine("Default() is marked IsOneWay:" + OneWayContext.Current.IsOneWay);
}
}
public class OneWayBehavior : IOperationInvoker
{
IOperationInvoker innerOperationInvoker;
public readonly bool isOneWay;
public OneWayBehavior(IOperationInvoker innerOperationInvoker, bool isOneWay)
{
this.isOneWay = isOneWay;
this.innerOperationInvoker = innerOperationInvoker;
}
public object[] AllocateInputs()
{
return innerOperationInvoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Everytime the operation is invoked, add IsOneWay information to the context.
OneWayContext.Current.IsOneWay = this.isOneWay;
return innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
return innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
return innerOperationInvoker.InvokeEnd(instance, out outputs, result);
}
public bool IsSynchronous
{
get { return innerOperationInvoker.IsSynchronous; }
}
}
public class OneWayOperationAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
// grab "IsOneWay" from the operation description and pass on the behavior's constructor.
dispatchOperation.Invoker = new OneWayBehavior(dispatchOperation.Invoker, operationDescription.IsOneWay);
}
public void Validate(OperationDescription operationDescription)
{
}
}
public class OneWayContractAttribute : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
foreach (OperationDescription operation in contractDescription.Operations)
{
operation.OperationBehaviors.Add(new OneWayOperationAttribute());
}
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
}
public class OneWayContext : IExtension<InstanceContext>
{
public OneWayContext()
{
// if not set, default to false.
IsOneWay = false;
}
public bool IsOneWay { get; set; }
public static OneWayContext Current
{
get
{
OneWayContext context = OperationContext.Current.InstanceContext.Extensions.Find<OneWayContext>();
if (context == null)
{
context = new OneWayContext();
OperationContext.Current.InstanceContext.Extensions.Add(context);
}
return context;
}
}
public void Attach(InstanceContext owner) { }
public void Detach(InstanceContext owner) { }
}
public static class Program
{
static void Main(string[] args)
{
ServiceHost simpleHost = new ServiceHost(typeof(SimpleService), new Uri("http://localhost/Simple"));
simpleHost.Open();
ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(simpleHost.Description.Endpoints[0]);
ISimple proxy = factory.CreateChannel();
proxy.OneWay();
proxy.Default();
Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
Console.ReadLine();
((ICommunicationObject)proxy).Shutdown();
simpleHost.Shutdown();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}