WCF 项目应用连载[9] - 契约中的委托 & 事件参数处理

来源:互联网 发布:xp不能访问网络位置 编辑:程序博客网 时间:2024/06/05 07:07


这节是一节辅助内容。。将说清楚Lig日志系统中是怎样使用事件参数与委托参数的。


1) WCF服务接口中不能直接使用委托参数。
2) WCF采用回调接口来实现双向通信,我们可以将WCF回调接口消息封装为事件消息,在客户端接口订阅该事件消息,从而将Server消息发送给Client。

(WCF服务接口在Server端实现,WCF服务接口声明的回调接口在客户端实现)


8.1 WCF服务接口与回调接口

___________________________________________________________________________________________________________

[ServiceContract(CallbackContract = typeof(ILigAgentCallback))]    public interface ILigAgent    {        [OperationContract]        LigStatus RegisterClient(string name);        [OperationContract]        LigStatus UnregisterClient(int clientID);        [OperationContract]        LigStatus Subscribe(int clientID);        [OperationContract]        LigStatus Unsubscribe(int clientID);        [OperationContract]        void LigMessage(string message, LigLevel level);        [OperationContract]        void LigInfo(string message);        [OperationContract]        void LigDebug(string message);        [OperationContract]        void LigWarn(string message);        [OperationContract]        void LigError(string message);        [OperationContract(Name = "LigFatal")]        void LigFatal(string message);        [OperationContract(Name = "LigFatalex")]        void LigFatal(string message, Exception ex);        [OperationContract]        void SayHelloToServer(string message);        [OperationContract]        void Initialize(string ligPath);    }    public interface ILigAgentCallback    {        [OperationContract(IsOneWay = true)]        void OnNotifyMessage(string message);        [OperationContract(IsOneWay = true)]        void OnNotifyOnline(LigArgs args);    }


关键点:客户端ILigger接口出现委托参数

public interface ILigger    {        bool ConnectStatus { get; }        int ClientID { get; }        bool SubscribeOnlineEvent(EventHandler onlined);        bool UnsubscribeOnlineEvent(int clientID);        void LigMessage(string message, int level);        void LigInfo(string message);        void LigDebug(string message);        void LigWarn(string message);        void LigError(string message);        void LigFatal(string message);    }


我们在ILigger中看到了含委托参数的接口:
bool SubscribeOnlineEvent(EventHandleronlined);

为什么我们不直接在ILigAgent中加入这样的接口?下这种做法理论是是可行的,也不会导致程序编译错误。如果客户端调用下面的含委托参数的接口,会导致WCF客户端运行调用错误。。客户端调用会报一个通道状态为Fault的异常。。
那我们如来来用呢?
[OperationContract]
bool SubscribeOnlineEvent(EventHandleronlined);


8.1.1 WCF回调接口

___________________________________________________________________________________________________________


WCF的双向通信通过回调接口实现。每一个WCF服务接口都可以显式的声明一个回调接口

回调接口由客户端实现,这样我们可以在Server端通过

public sealed class OperationContext : IExtensibleObject<OperationContext>

中的成员获取客户端回调实例,然后用这该实例(T) 调用回调接口中的方法成员,从而将消息从服务端发送给指定客户端。。

public T GetCallbackChannel<T>();


8.1.2 WCF回调接口实现 - 事件封装
___________________________________________________________________________________________________________


        private event EventHandler onlineEvent;        private event EventHandler MessageRecv;

1) 回到LiggerBase类,我们Ligger使用的基类。的LiggerBase内部有两个事件参数:onlineEvent与MessageRecv。

2)在LiggerBase的内部我们定义了一个LigAgentCallback 实现了WCF的回调接口ILigAgentCallback,并在LiggerBase内部订阅了msgReceived事件与onlined事件
这样,WCF回调接口中的消息发送送到了LiggerBase内部的OnNotifyMessage与OnNotifyOnline。

【LiggerBase内部订阅LigAgentCallback的msgReceived事件与onlined事件】
        private void SubscribedEvents()        {            this.callbackInstance.msgReceived += new MessageHandler(OnNotifyMessage);            this.callbackInstance.onlined += new OnlineHandler(OnNotifyOnline);        }

【WCF中LigAgentCallback 回调消息类】

#region InnerClasses        class LigAgentCallback : ILigAgentCallback        {            internal LigAgentCallback() { }            internal event MessageHandler msgReceived;            internal event OnlineHandler onlined;            public void OnNotifyMessage(string message)            {                if (this.msgReceived != null)                {                    this.msgReceived(message);                }            }            public void OnNotifyOnline(LigArgs args)            {                if (this.onlined != null)                {                    this.onlined(args);                }            }            internal void Release()            {                if (this.msgReceived != null)                {                    Delegate[] items = this.msgReceived.GetInvocationList();                    foreach (Delegate item in items)                    {                        this.msgReceived -= (MessageHandler)item;                    }                }                if (this.onlined != null)                {                    Delegate[] items = this.onlined.GetInvocationList();                    foreach (Delegate item in items)                    {                        this.onlined -= (OnlineHandler)item;                    }                }            }        }        #endregion


【ILigger接口订阅内部事件onlineEvent事件消息】

        public bool SubscribeOnlineEvent(EventHandler onlined)        {            bool isSubscribed = false;            if (this.onlineEvent != null)            {                Delegate[] items = this.onlineEvent.GetInvocationList();                foreach (Delegate item in items)                {                    if (onlined == (EventHandler)item)                    {                        isSubscribed = true;                        break;                    }                }            }            if (isSubscribed) return true;            this.onlineEvent += onlined;            return true;        }


【LiggerBase内部订阅的WCF回调接口消息 , 此处LiggerBase将WCF回调接口中的消息传递给了客户端订阅onlineEvent事件绑定的方法列表。。。】

        private void OnNotifyOnline(LigArgs args)        {            LiggerEventArgs eventArgs = new LiggerEventArgs();            eventArgs.ClientID = args.ClientID;            eventArgs.ConnectStatus = args.ConnectStatus;            eventArgs.Message = args.Message;            this.clientID = args.ClientID;            this.connectStatus = args.ConnectStatus;            if (this.onlineEvent != null)            {                this.onlineEvent(this,eventArgs);            }        }


8.2 WCF服务接口事件封装解决之道


___________________________________________________________________________________________________________


8.2.1 原理:在回调接口中引入中间变量(事件参数)进行消息交换


A传递信息给B  不是A直接传给B,而是A先传给CC将消息封装与转换,再传给B

8.2.2 Ligger接口中事件参数处理的设计思路:

1)  将WCF回调接口封装到LiggerBase内部,然后在LiggerBase内部订阅WCF回调接口中的消息。

        WCF回调接口传参跨了服务端与客户端的边界。
        Ligger内部的接口是没有跨边界的,因为Ligger封装了LiggerBase,Ligger只从LiggerBase获取消息。

2)  LiggerBase获取WCF回调接口中的消息。

3)  LiggerBase要做的事件是,将WCF回调接口消息转换为Ligger中的事件消息。从而将消息传给订阅该事件的客户端


完了。有这3步就完成了 A – C – B通信的过程。前面提到的问题也解决了。
这儿,我们在Ligger中引入了这样的一个中间变量C进行消息转换,将WCF回调接口消息转换为事件消息  解决了WCF中事件传参的问题。。


8.3 为什么WCF服务接口中不能出现委托参数

___________________________________________________________________________________________________________

这与WCF数据契约序列化方式有关

1)默认条件下,WCF采用XML方式序列化,DataContractSerializerWCF的默认序列化器,该类从XmlObjectSerializer类继承。ClientServer之间以XML Message方式进行信息交互。。

2)而事件与委托参数在WCF中是不能用XML方式序列化的,只能用SOAP方式。所以,WCF服务接口中不能出现委托参数。除非你可以改变WCF服务接口序列化的方式为SOAP..



8.4  Remoting中的远程对像接口可以出现委托参数
___________________________________________________________________________________________________________

RemotingSOAP这种方式进行了支持。方法是:


1) 在注册TcpChannel的时候,将BinaryServerFormatterSinkProvider类的TypeFilterLevel属性设置为TypeFilterLevel.Full
2) 同时含委托与事件参数的类需要继承MarshalByRefObject
这样的好处是,我们在远程对象的接口中能直接出现事件与委托参数。,但是即便这样做,Remoting也需要依赖于中间变量对事件参数进行间接传递。

WCF没有没有提供这样的便利。


8.5 关于WCF服务接口中不能出现委托参数最后遗留原因

___________________________________________________________________________________________________________

关于这个问题的最终答案,我也追寻了很久。。。。

最后我在O'Reilly的《WCF编程》一书中找到了一段像样的答案

关于委托:

原书是这样描述的:

WCF对委托以及事件的支持都不够好。这是因为委托的内部调用列表的具体结构是本地的,客户端或服务无法跨服务边界共享委托列表的结构。此外,我们不能保证内部列表中的目标对象都是可序列化的,或者都是有效的数据契约。这会导致序列化的操作时而成功,时而失败。因此,最佳实践是不要将委托成员或事件作为数据契约的一部分。”

这段话的意思是说:
客户端或服务无法跨服务边界共享委托列表,最好的方式是不要将委托成员或事件作为数据契约或服务契约接口参数中的一部分。

实际上这不是本质的原因,本质原因是WCF默认序列化方式为XML方式,而非事件与委托要求的SOAP。。不使用委托不是解决之道。这儿我们其实需要的是一种解决方法,而不是一段话。。。。

8.6 最后
___________________________________________________________________________________________________________

You are lucky。你在Ligger中看到了这种问题的有效解决方案。  即消息交换采用中间变量:

___________________________________

A->C->B而非A->B。。。。。。
___________________________________
我们用LiggerBase内部事件订阅WCF回调接口消息,LiggerBase中的接口再订阅LiggerBase内部事件消息,从而将消息从Server端通过LiggerBase内部事件订阅WCF回调接口消息传给了Client.....

这儿。。。Ligger实际上只发生了对LiggBase的近接调用,注意此处我们没用Ligger继承LiggerBase,是因为Ligger不想暴露LiggerBase对WCF服务接口封装资源。。。这样让Client-Server间的耦合降低。。。。。。


原创粉丝点击