WCF回調操作簡單的說就是由服務端來調用客戶端的方法,回調時原先的服務端和客戶端將發生對調,服務端成爲客戶端,客戶端成爲服務端。
WCF回調的必要條件:
1、並不是所有的綁定都支持回調操作,只有具有雙向通信能力的綁定纔可以使用回調。比如HTTP是與連接無關的因此不能用於回調。這就說明我們不能使用BasicHttpBinding或WSHttpBinding進行回調。WCF中NetTcpBinding和NetNamedPipeBinding支持回調,因爲從本質上講TCP和IPC協議支持雙向通信。WCF中wsDualHttpBinding也是支持回調的因爲它實際上是設置了兩個HTTP通道
定義回調契約
一個服務契約若要定義回調,必須專門定義一個用於回調的契約。一個服務契約最多包含一個回調契約,一個服務契約一旦定義了回調契約那客戶端必須支持這個回調。那如何爲一個服務契約定義回調呢?使用ServiceContract特性的CallBackContract特性,代碼如下
[ServiceContract(CallbackContract = typeof(ISomeCallbackContract))]
public interface IService1
{
[OperationContract(IsOneWay=true)]
void DoSomething();
}
//用於回調的契約
interface ISomeCallbackContract
{
[OperationContract(IsOneWay=true)]
void SomeCallbackMethod();
[OperationContract(IsOneWay = true)]
void SomeCallbackMethod2();
}
客戶端回調設置
客戶端需要實現服務端定義的那個用於回調的契約ISomeCallbackContract,然後實例化回調對象再通過它創建一個上下文對象InstanceContext。然後用代理把這個回調的引用傳回服務端
下面通過代碼實例說明:
服務端代碼
[ServiceContract(CallbackContract = typeof(ISomeCallbackContract))]
public interface IService1
{
[OperationContract(IsOneWay=true)]
void DoSomething();
}
//用於回調的契約
interface ISomeCallbackContract
{
[OperationContract(IsOneWay=true)]
void SomeCallbackMethod();
[OperationContract(IsOneWay = true)]
void SomeCallbackMethod2();
}
實現服務的類:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class Service1 : IService1
{
static List<ISomeCallbackContract> m_Callbacks = new List<ISomeCallbackContract>();
public void DoSomething()
{
//獲得由客戶端傳入的回調終結點的引用,使用這個引用可以調用客戶端方法
ISomeCallbackContract callback = OperationContext.Current.GetCallbackChannel<ISomeCallbackContract>();
if (m_Callbacks.Contains(callback) == false)
{
m_Callbacks.Add(callback);
}
Trace.WriteLine("DoSomething");
//調用客戶端實現的SomeCallbackMethod2方法,
callback.SomeCallbackMethod2();
CallClients();
}
static public void CallClients()
{
Action<ISomeCallbackContract> invoke = delegate(ISomeCallbackContract callback)
{
callback.SomeCallbackMethod();
};
m_Callbacks.ForEach(invoke);
}
}
客戶端代碼:
//客戶端實現回調接口,使用VS自動生成代理的時候,對於回調接口的命名默認是以服務契約接口名稱+Callback,而不是原先在服務端定義的回調接口的名子
public class CallBack : ServiceReferenceCallBack.IService1Callback
{
public void SomeCallbackMethod()
{
MessageBox.Show("SomeCallbackMethod()", "MyClient");
}
public void SomeCallbackMethod2()
{
MessageBox.Show("SomeCallbackMethod2()", "MyClient");
}
}
客戶端調用
首先生成回調接口實現的對象,然後用這個對象去實例化一個上下文對象context ,在創建客戶端代理對象的時候在構造方法中使用這個上下文對象context 就可以把客戶端回調對象的引用作爲消息的一部分傳送到服務端,在服務端就可以通過OperationContext.Current.GetCallbackChannel<ISomeCallbackContract>()來獲得這個回調對象的引用,服務端有了這個回調對象的引用就可以去調用客戶端實現的回調方法
ServiceReferenceCallBack.IService1Callback callback = new CallBack();
InstanceContext context = new InstanceContext(callback);
ServiceReferenceCallBack.Service1Client sc = new WCFPrograme.ServiceReferenceCallBack.Service1Client(context);
sc.DoSomething();
總結:
1、必須使用支持雙向通信的綁定如NetTcpBinding、NetNamedPipeBinding、WSDualHttpBinding
2、用 CallbackContract 爲服務定義回調,回調的接口在服務端定義。
例:
[ServiceContract(CallbackContract = typeof(ISomeCallbackContract))]
3、服務端用 OperationContext.Current.GetCallbackChannel<T>()獲取調用當前操作的客戶端實例通道
例:
ISomeCallbackContract callback = OperationContext.Current.GetCallbackChannel<ISomeCallbackContract>()
4、服務端獲得客戶端實例通道的對象以後就可以調用客戶端方法
例:
callback.SomeCallbackMethod2();
5、客戶端生成代理以後,由客戶端去實現回調接口
例:
public class CallBack : ServiceReferenceCallBack.IService1Callback
{
//實現接口
}
6、客戶端創建回調的對象並生成操作調用的上下文並由客戶端代理對象傳回服務端
ServiceReferenceCallBack.IService1Callback callback = new CallBack();
InstanceContext context = new InstanceContext(callback);
ServiceReferenceCallBack.Service1Client sc = new WCFPrograme.ServiceReferenceCallBack.Service1Client(context);
sc.DoSomething();
配置文件:使用wsDualHttpBinding的時候,需要在客戶端配置文件中需要指定客戶端基址clientBaseAddress,服務可以通過這個地址與客戶端取得聯繫,這是因爲wsDualHttpBinding需要兩條http通道,而是使用netTcpBinding時不需要
<endpoint address="http://localhost:8731/Design_Time_Addresses/WcfCallBack/Service1/"
binding="wsDualHttpBinding" bindingConfiguration="WSDualHttpBinding_IService1"
contract="ServiceReferenceCallBack.IService1" name="WSDualHttpBinding_IService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_IService1" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
clientBaseAddress="http://localhost:8008">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" />
<security mode="Message">
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsDualHttpBinding>