事件通知服務用于解決多個應用程式之間的事件釋出與預定的問題。在.NET平台上,跨應用程式的事件釋出/預定通常以Remoting作為底層的通信基礎,在此基礎之上,事件通知服務使用中介者模式來簡化跨應用程式的事件通知問題。
本文采用的解決方案中,有兩個重要元件:事件伺服器EventServer和事件用戶端EventClient。EventServer作為中介者,并作為一個獨立的系統,通常可以将其作為windows服務運作。EventServer和EventClient之間的關系如下所示:
每個需要事件通知的應用程式中,都包含了EventClient元件,應用程式通過EventClient與事件伺服器進行互動,而當有事件發生時,EventClient也會觸發相應的事件來通知應用程式。
EventServer和EventClient實作了共同的接口IEventNotification:
public interface IEventNotification
{
void SubscribeEvent(string eventName ,EventProcessHandler handler) ;//預定事件
void UnSubscribeEvent(string eventName ,EventProcessHandler handler) ;//取消預定
void RaiseEvent(string eventName ,object eventContent) ; //釋出事件
}
public delegate void EventProcessHandler(string eventName ,object eventContent) ;
注意,IEventNotification接口中的每個方法的第一個參數是事件名,事件名唯一标志了每個事件,它相當于一個主鍵。
EventClient與包含它的應用程式之間的互動通過本地事件預定/釋出來完成,而與EventServer之間的互動則通過remoting完成。其實作如下:
public class EventClient :MarshalByRefObject ,IEventNotification
private IEventNotification eventServer = null ;
private Hashtable htableSubscribed = new Hashtable() ; //eventName -- Delegate(是一個連結清單)
public EventClient(string eventServerUri)
{
TcpChannel theChannel = new TcpChannel(0) ;
ChannelServices.RegisterChannel(theChannel) ;
this.eventServer = (IEventNotification)Activator.GetObject(typeof(IEventNotification) ,eventServerUri);
}
public override object InitializeLifetimeService()
{
//Remoting對象 無限生存期
return null;
}
#region IEventNotification 成員
//handler是本地委托
public void SubscribeEvent(string eventName, EventProcessHandler handler)
{
lock(this)
{
Delegate handlerList = (Delegate)this.htableSubscribed[eventName] ;
if(handlerList == null)
{
this.htableSubscribed.Add(eventName ,handler) ;
this.eventServer.SubscribeEvent(eventName ,new EventProcessHandler(this.OnRemoteEventHappen)) ;
return ;
}
handlerList = Delegate.Combine(handlerList ,handler) ;
this.htableSubscribed[eventName] = handlerList ;
}
}
public void UnSubscribeEvent(string eventName, EventProcessHandler handler)
if(handlerList != null)
handlerList = Delegate.Remove(handlerList ,handler) ;
this.htableSubscribed[eventName] = handlerList ;
public void RaiseEvent(string eventName, object eventContent)
{
this.eventServer.RaiseEvent(eventName ,eventContent) ;
#endregion
#region OnRemoteEventHappen
/// <summary>
/// 當EventServer上有事件觸發時,EventServer會轉換為用戶端,而EventClient變成遠端對象,
/// 該方法會被遠端調用。是以必須為public
/// </summary>
public void OnRemoteEventHappen(string eventName, object eventContent)
{
object[] args = {eventName ,eventContent} ;
foreach(Delegate dg in handlerList.GetInvocationList())
try
{
dg.DynamicInvoke(args) ;
}
catch(Exception ee)
{
ee = ee ;
需要注意的是,EventClient從MarshalByRefObject繼承,這是因為,當EventServer上有事件被觸發時,也會通過Remoting Event來通知EventClient,這個時候,EventClient就是一個remoting object。另外,OnRemoteEventHappen方法必須為public,因為這個方法将會被EventServer遠端調用。
下面給出EventServer的實作:
public class EventServer :MarshalByRefObject ,IEventNotification
//htableSubscribed内部每項的Delegate連結清單中每一個委托都是透明代理
private Hashtable htableSubscribed = new Hashtable() ; //eventName -- Delegate(是一個連結清單)
public EventServer()
public override object InitializeLifetimeService()
//handler是一個透明代理,指向EventClient.OnRemoteEventHappen委托
this.htableSubscribed.Add(eventName ,handler) ;
IEnumerator enumerator = handlerList.GetInvocationList().GetEnumerator() ;
while(enumerator.MoveNext())
Delegate handler = (Delegate)enumerator.Current ;
handler.DynamicInvoke(args) ;
catch(Exception ee) //也可重試
handlerList = Delegate.Remove(handlerList ,handler) ;
this.htableSubscribed[eventName] = handlerList ;
EventServer的實作是很容易了解的,需要注意的是RaiseEvent方法,該方法在while循環中對每個循環加入了try...catch,這是為了保證,當一個應用程式無法接收通知或接收通知失敗時不會影響到其它的伺服器。
關于事件通知服務,可以總結為以下幾點:
(1)事件通知服務采用了中介者模式,所有的EventClient隻與EventServer(中介者)互動,從EventServer處預定名為eventName的事件,或釋出名為eventName的事件。
(2)各個客戶應用程式是對等的,它們都可以預定事件和釋出事件。
(3)EventServer不會自主地觸發事件,它就像一個公共區(緩存預定者)或轉發器(廣播事件)。
(4)EventServer 将在事件伺服器上作為遠端對象釋出
(5)客戶應用程式将通過EventClient來預定事件、釋出事件。
最後,需要提出的是關于事件伺服器的配置,需要将remoting的權限級别設定為FULL,否則,就會出現事件句柄無法序列化的異常。在我的示例中,EventServer的配置檔案如下:
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown
mode="Singleton"
type="EnterpriseServerBase.XFramework.EventNotification.EventServer,EnterpriseServerBase"
objectUri="SerInfoRemote" />
</service>
<channels>
<channel ref="tcp" port="8888">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="soap" typeFilterLevel="Full" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
請特别注意,标志為紅色的兩句。 并且,在服務端程式啟動時,配置Remoting:
RemotingConfiguration.Configure("EventClient.exe.config");
由于在服務端回調Client時,Client相對變成"Server",是以,Client也必須注冊一個remoting通道。
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="0">
<formatter ref="binary" />
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
并且,在用戶端程式啟動時,配置Remoting: