WCF 代理 是怎么工作的?用代码说话

1.WCF生成代理的方式

2.WCF代理原理

第一个问题引用 一篇Robin‘s博文[WCF生成客户端对象方式解析] 讲述了创建客户端服务对象的方法

1.代理构造法

a.开启服务后,添加服务引用

b.知道元数据地址,通过svcutli.exe生成代理类和配置文件

c.从服务契约DLL中导出元数据,然后更具本地的元数据文件生成代理类和配置文件

d.知道元数据地址,自己编写代码生成(使用ServiceContractGenerator类等),生成代理类和配置文件

2.通道工厂(ChannelFactory<T>)

a.知道终结点地址,绑定协议(ABC中的A和B)

b.只知道元数据终结点地址(代码中使用MetadataResover类获取服务信息)

文章最后附有代码:代码,可以下下来运行测试等。

下边来看看生成的代理是什么样的。

1.添加服务引用 生成了一个 继承自 System.ServiceModel.ClientBase<Wcf.Client.ServiceReference1.IService> 的 ServiceClient

public interface IService {

        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/DoWork", ReplyAction="http://tempuri.org/IService/DoWorkResponse")]
        void DoWork();

        [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/GetData", ReplyAction="http://tempuri.org/IService/GetDataResponse")]
        Wcf.Client.ServiceReference1.MyData GetData(int field);
    }

 [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    public partial class ServiceClient : System.ServiceModel.ClientBase<Wcf.Client.ServiceReference1.IService>, Wcf.Client.ServiceReference1.IService {

        public ServiceClient() {
        }

        public ServiceClient(string endpointConfigurationName) :
                base(endpointConfigurationName) {
        }

        public ServiceClient(string endpointConfigurationName, string remoteAddress) :
                base(endpointConfigurationName, remoteAddress) {
        }

        public ServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
                base(endpointConfigurationName, remoteAddress) {
        }

        public ServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
                base(binding, remoteAddress) {
        }

        public void DoWork() {
            base.Channel.DoWork();
        }

        public Wcf.Client.ServiceReference1.MyData GetData(int field) {
            return base.Channel.GetData(field);
        }
    }
}

生成的代码中有接口,服务客户端(ServiceClient)

调用DoWork()和GetData(int field) 方法,是调用的 ClientBase<T>中Channel对象的DoWork()和GetData(field)方法

ClientBase<T>

public abstract class ClientBase<TChannel> : ICommunicationObject, IDisposable where TChannel : class
{
    ……//其他内容
    protected TChannel Channel { get; }
    public ChannelFactory<TChannel> ChannelFactory { get; }
}

Channel 的类型是TChannel ,同时可以发现内部有个ChannelFactory。

2.使用ChannelFactory<T>

ChannelFactory<Proxys.IService> channelFactory = new ChannelFactory<Proxys.IService>(bind);
var channel = channelFactory.CreateChannel(address);
 using (channel as IDisposable)
 {
    channel.DoWork();
    Wcf.Proxys.MyData myData = channel.GetData(10);
}

每次我们看代码都只能看到这里,却不能知道ClientBase<T>,ChannelFatctory<T>是怎么起到代理作用的,怎么把方法的调用转换成底层的交互的。

我们来找找源头:(本来是想反编译ServiceModel.dll,翻遍以后有不能看到代码内容,好在Mono下有相应的模块内容,可以在“开源中国社区”看到一些代码)

ClientBase<T> Mono开源代码地址

212  protected TChannel Channel {
213  get { return (TChannel) (object) InnerChannel; }
214 }

看到212行代码,channel返回的属性InnerChannel

204  public IClientChannel InnerChannel {
205  get {
206         if (inner_channel == null)
207                inner_channel = (IClientChannel) (object) CreateChannel ();
208              return inner_channel;
209          }
210     }

通过CreateChannel ()创建的对象

304     protected virtual TChannel CreateChannel ()
305     {
306         return ChannelFactory.CreateChannel ();
307     }

CreateChannel ()方法是用ChannelFactory.CreateChannel (); 创建对象

188     public ChannelFactory<TChannel> ChannelFactory {
189         get { return factory; }
190         internal set {
191             factory = value;
192             factory.OwnerClientBase = this;
193         }
194     }

跳转到 ChannelFactory<T>类 地址

104     public TChannel CreateChannel ()
105     {
106         EnsureOpened ();
107
108         return CreateChannel (Endpoint.Address);
109     }

CreateChannel方法的一个重载方法

133     public virtual TChannel CreateChannel (EndpointAddress address, Uri via)
134     {
135
   #if MONOTOUCH
136         throw new InvalidOperationException ("MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance");
137
  #else
138         var existing = Endpoint.Address;
139         try {
140
141         Endpoint.Address = address;
142         EnsureOpened ();
143         Endpoint.Validate ();
144         Type type = ClientProxyGenerator.CreateProxyType (typeof (TChannel), Endpoint.Contract, false);
145         // in .NET and SL2, it seems that the proxy is RealProxy.
146         // But since there is no remoting in SL2 (and we have
147         // no special magic), we have to use different approach
148         // that should work either.
149         object proxy = Activator.CreateInstance (type, new object [] {Endpoint, this, address ?? Endpoint.Address, via});
150         return (TChannel) proxy;
151         } catch (TargetInvocationException ex) {
152             if (ex.InnerException != null)
153                 throw ex.InnerException;
154             else
155                 throw;
156         } finally {
157             Endpoint.Address = existing;
158         }
159
  #endif
160     }

翻一下一下注释:在.NET和SL2.0中,看来好像这个代理是真实代理,因为在SL2.0中没有是用Remoting(并且也没有独特魔力),我们必须是用不同的方式使它依然能够工作运行起来。

这里注意两点:

1.Type type = ClientProxyGenerator.CreateProxyType (typeof (TChannel), Endpoint.Contract, false);

2.object proxy = Activator.CreateInstance (type, new object [] {Endpoint, this, address ?? Endpoint.Address, via});

先来看看简单一点的第二个方法的解释:

MSDN中对对该Activator.CreateInstance(type,object[]) 的解释是”使用与指定参数匹配程度最高的构造函数创建指定类型的实例”

两个参数分别是:需要生成的对象的类;构造函数传入的参数,系统会更具参数的数量、顺序和类型的匹配程度调用相应的构造函数。

看看第一个方法原型是什么样子:

069     public static Type CreateProxyType (Type requestedType, ContractDescription cd, bool duplex)
070     {
071         ClientProxyKey key = new ClientProxyKey (requestedType, cd, duplex);
072         Type res;
073         lock (proxy_cache) {
074             if (proxy_cache.TryGetValue (key, out res))
075                 return res;
076         }
077
078         string modname = "dummy";
079         Type crtype =
080
#if !NET_2_1
081             duplex ? typeof (DuplexClientRuntimeChannel) :
082
#endif
083             typeof (ClientRuntimeChannel);
084
085         // public class __clientproxy_MyContract : (Duplex)ClientRuntimeChannel, [ContractType]
086         var types = new List<Type> ();
087         types.Add (requestedType);
088         if (!cd.ContractType.IsAssignableFrom (requestedType))
089             types.Add (cd.ContractType);
090         if (cd.CallbackContractType != null && !cd.CallbackContractType.IsAssignableFrom (requestedType))
091             types.Add (cd.CallbackContractType);
092         CodeClass c = new CodeModule (modname).CreateClass ("__clientproxy_" + cd.Name, crtype, types.ToArray ());
093
094         //
095         // public __clientproxy_MyContract (
096         //  ServiceEndpoint arg1, ChannelFactory arg2, EndpointAddress arg3, Uri arg4)
097         //  : base (arg1, arg2, arg3, arg4)
098         // {
099         // }
100         //
101         Type [] ctorargs = new Type [] {typeof (ServiceEndpoint), typeof (ChannelFactory), typeof (EndpointAddress), typeof (Uri)};
102         CodeMethod ctor = c.CreateConstructor (
103             MethodAttributes.Public, ctorargs);
104         CodeBuilder b = ctor.CodeBuilder;
105         MethodBase baseCtor = crtype.GetConstructors (
106             BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) [0];
107         if (baseCtor == null) throw new Exception ("INTERNAL ERROR: ClientRuntimeChannel.ctor() was not found.");
108         b.Call (
109             ctor.GetThis (),
110             baseCtor,
111             new CodeArgumentReference (typeof (ServiceEndpoint), 1, "arg0"),
112             new CodeArgumentReference (typeof (ChannelFactory), 2, "arg1"),
113             new CodeArgumentReference (typeof (EndpointAddress), 3, "arg2"),
114             new CodeArgumentReference (typeof (Uri), 4, "arg3"));
115         res = CreateProxyTypeOperations (crtype, c, cd);
116
117         lock (proxy_cache) {
118             proxy_cache [key] = res;
119         }
120         return res;
121     }
122 }
123

注意内容:

1.内部使用了缓存。

2.使用了动态编译.

3.根据具不同的duplex [in]生成不同的类(代理)类型:

4.看看res = CreateProxyTypeOperations (crtype, c, cd); 这句话发生了什么

126     protected static Type CreateProxyTypeOperations (Type crtype, CodeClass c, ContractDescription cd)
127     {
128         // member implementation
129         BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
130         foreach (OperationDescription od in cd.Operations) {
131             // FIXME: handle properties and events.
132
#if !NET_2_1
133             if (od.SyncMethod != null)
134                 GenerateMethodImpl (c, crtype.GetMethod ("Process", bf), od.Name, od.SyncMethod);
135
#endif
136             if (od.BeginMethod != null)
137                 GenerateBeginMethodImpl (c, crtype.GetMethod ("BeginProcess", bf), od.Name, od.BeginMethod);
138             if (od.EndMethod != null)
139                 GenerateEndMethodImpl (c, crtype.GetMethod ("EndProcess", bf), od.Name, od.EndMethod);
140         }
141
142         Type ret = c.CreateType ();
143         return ret;
144     }
259     static void GenerateEndMethodImpl (CodeClass c, MethodInfo endProcessMethod, string name, MethodInfo mi)
260     {
261         CodeMethod m = c.ImplementMethod (mi);
262         CodeBuilder b = m.CodeBuilder;
263         ParameterInfo [] pinfos = mi.GetParameters ();
264
265         ParameterInfo p = pinfos [0];
266         CodeArgumentReference asyncResultRef = m.GetArg (0);
267
268         CodeVariableDeclaration paramsDecl = new CodeVariableDeclaration (typeof (object []), "parameters");
269         b.CurrentBlock.Add (paramsDecl);
270         CodeVariableReference paramsRef = paramsDecl.Variable;
271         b.Assign (paramsRef,
272               new CodeNewArray (typeof (object), new CodeLiteral (pinfos.Length - 1)));
273         /**
274         for (int i = 0; i < pinfos.Length - 2; i++) {
275             ParameterInfo par = pinfos [i];
276             if (!par.IsOut)
277                 b.Assign (
278                     new CodeArrayItem (paramsRef, new CodeLiteral (i)),
279                     new CodeCast (typeof (object),
280                         new CodeArgumentReference (par.ParameterType, par.Position + 1, "arg" + i)));
281         }
282         */
283
#if USE_OD_REFERENCE_IN_PROXY
284         CodePropertyReference argMethodInfo = GetOperationMethod (m, b, name, "EndMethod");
285
#else
286         CodeMethodCall argMethodInfo = new CodeMethodCall (typeof (MethodBase), "GetCurrentMethod");
287
#endif
288         CodeLiteral argOperName = new CodeLiteral (name);
289
290         CodeVariableReference retValue = null;
291        if (mi.ReturnType == typeof (void))
292             b.Call (m.GetThis (), endProcessMethod, argMethodInfo, argOperName, paramsRef, asyncResultRef);
293         else {
294             CodeVariableDeclaration retValueDecl = new CodeVariableDeclaration (mi.ReturnType, "retValue");
295             b.CurrentBlock.Add (retValueDecl);
296             retValue = retValueDecl.Variable;
297             b.Assign (retValue,
298                 new CodeCast (mi.ReturnType,
299                     b.CallFunc (m.GetThis (), endProcessMethod, argMethodInfo, argOperName, paramsRef, asyncResultRef)));
300         }
301         // FIXME: fill out parameters
302         if (retValue != null)
303             b.Return (retValue);
304     }

CreateProxyTypeOperations  看方法名就大概清楚:创建代理的操作(方法)。

GenerateEndMethodImpl  方法的具体实现。(最后这个方法很重要,不过部分代码我没有看明白,还请看明白的人还望不吝赐教)

ClientBase<T> 内部使用了 ChannelFactory<T>

这里使用的Mono代码说明、如果与.Net有区别也是有可能的。

至少基本说明了一个问题,代理是根据描述(元数据、接口等来源)动态生成“接口的对象”,

该对象的方法体(方法名、参数、返回值)都与接口一致,方法体对方法传入的值进行了处理。

只要获取到了传递过来的方法、参数、返回值信息等,就可以想怎么样就怎么样、为所欲为了。

C#客户端在调用WCF服务的时候,很多情况使用Remoting下RealProxy,RealProxy具有一个抽象的Invoke方法:public abstract IMessage Invoke(IMessage msg);

MSDN对RealProxy.Invoke(IMessage msg)方法的解释是 地址

当调用受 RealProxy 支持的透明代理时,它将调用委托给 Invoke 方法。 Invoke 方法将 msg 参数中的消息转换为 IMethodCallMessage,并将其发送至 RealProxy 的当前实例所表示的远程对象。

比如:

public class CalculatorServiceRealProxy : RealProxy
    {
        public CalculatorServiceRealProxy():base(typeof(ICalculatorService)){}
        public override IMessage Invoke(IMessage msg)
        {
            IMethodReturnMessage methodReturn = null;
            IMethodCallMessage methodCall = (IMethodCallMessage)msg;
            var client = new ChannelFactory<ICalculatorService>("CalculatorService");
            var channel = client.CreateChannel();
            try
            {
                object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
                methodCall.Args.CopyTo(copiedArgs, 0);
                object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs);
                methodReturn = new ReturnMessage(returnValue,
                                                copiedArgs,
                                                copiedArgs.Length,
                                                methodCall.LogicalCallContext,
                                                methodCall);
                //TODO:Write log
            }
            catch (Exception ex)
            {
                var exception = ex;
                if (ex.InnerException != null)
                    exception = ex.InnerException;
                methodReturn = new ReturnMessage(exception, methodCall);
            }
            finally
            {
                var commObj = channel as ICommunicationObject;
                if (commObj != null)
                {
                    try
                    {
                        commObj.Close();
                    }
                    catch (CommunicationException)
                    {
                        commObj.Abort();
                    }
                    catch (TimeoutException)
                    {
                        commObj.Abort();
                    }
                    catch (Exception)
                    {
                        commObj.Abort();
                        //TODO:Logging exception
                        throw;
                    }
                }
            }
            return methodReturn;
        }
    }
static void Main(string[] args)
        {

            ICalculatorService proxy = (ICalculatorService)new CalculatorServiceRealProxy().GetTransparentProxy();

            Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));
            Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));
            Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));
            Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));

            Console.ReadKey();
        }

ICalculatorService proxy = (ICalculatorService)new CalculatorServiceRealProxy().GetTransparentProxy();  获取到动态“实现”ICalculatorService  的 实体对象,

每次调用的时候都会调用实现的RealProxy的Invoke方法

IMethodCallMessage methodCall = (IMethodCallMessage)msg;  把msg转换成 IMethodCallMessage ;

var channel = client.CreateChannel();这句话创建了代理对象;

object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs); 执行这个代理对象;

methodReturn = new ReturnMessage(returnValue,
                                                copiedArgs,
                                                copiedArgs.Length,
                                                methodCall.LogicalCallContext,
                                                methodCall); 返回值处理

这样的情况下 使用了两次代理。

这样做就具有了AOP的特征,好处也很多

1.做日志记录;

2.异常处理;

3.这个地方做了所有的远程连接操作,对调用这来讲,与通常的非服务调用没有区别,比如这里的“关闭连接”;

上边的代码值得优化一下,比如:

1.CalculatorServiceRealProxy 应该改成CalculatorServiceRealProxy <T>,ICalculatorService 通过 T传入,这样灵活性增加不少。

2.endpointConfigurationName 应该也放到CalculatorServiceRealProxy 的构造函数上;或者系统做默认规则处理,比如:配置节点名称就是去掉相应接口名称前的“I”,等等。

希望与大家多多交流  QQ:373934650;群Q:227231436

如果有用请大伙儿帮忙推荐一下,谢谢!

2014-7-24 03:00:43(待续)

WCF 代理 是怎么工作的?用代码说话,布布扣,bubuko.com

时间: 2024-12-14 15:26:20

WCF 代理 是怎么工作的?用代码说话的相关文章

微软ASP.NET 电商网站开发实战 MVC6 +HTML5 +WCF+WebAPI+NoSQL+mongoDB+Redis+Core视频 代码 面试题

<微软ASP.NET 电商网站开发实战 MVC6 +HTML5 +WCF+WebAPI+NoSQL+mongoDB+Redis+Core 视频 代码 面试题 >下载网盘:https://yunpan.cn/cP7SNIjgJYYjA  访问密码 7fc6 微软特邀讲师 徐雷FrankXuLei 2016 授课 更新:.NET Core 1.0高并发框架+面试题更新:高性能缓存 Redis.NoSQL面试题 安装,增删改查 RedisHelper帮助类 购物车 会话服务器更新:REST WebA

js 也来 - 【拉勾专场】抛弃简历!让代码说话!

前些日子谢亮兄弟丢了一个链接在群里,我当时看了下,觉得这种装逼题目没什么意思,因为每种语言都有不同的实现方法,你怎么能说你的方法一定比其他语言的好,所以要好的思路 + 好的语言特性运用才能让代码升华. 题目如下:<[拉勾专场]抛弃简历!让代码说话!> ? FizzBuzzWhizz 你是一名体育老师,在某次课距离下课还有五分钟时,你决定搞一个游戏.此时有100名学生在上课.游戏的规则是: 1. 你首先说出三个不同的特殊数,要求必须是个位数,比如3.5.7. 2. 让所有学生拍成一队,然后按顺序

通过代码的方式完成WCF服务的寄宿工作

使用纯代码的方式进行服务寄宿 服务寄宿的目的是为了开启一个进程,为WCF服务提供一个运行的环境.通过为服务添加一个或者多个终结点,使之暴露给潜在的服务消费,服务消费者通过匹配的终结点对该服务进行调用,除去上面的两种寄宿方式,还可以以纯代码的方式实现服务的寄宿工作. 新建立一个控制台应用程序,添加System.ServiceModel库文件的引用. 添加WCF服务接口:ISchool使用ServiceContract进行接口约束,OperationContract进行行为约束 1 using Sy

Java三大器之过滤器(Filter)的工作原理和代码演示

一.Filter简介 Filter也称之为过滤器,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能.例如实现URL级别的权限访问控制.过滤敏感词汇.压缩响应信息等一些高级功能. Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter.通过Fi

工作中常用代码--DateUtils

工作中经常遇到处理日期的问题, 当然有一些优秀开源的库(例如 joda-time, 功能十分强大), 不过个人还是比较偏好自写一点常用的代码, 毕竟开源库中我们实际使用到的功能并不多,如果引入库,个人感觉造成一些资源浪费(纯属个人观点).下面就是我常用一个工具类,DateUtils,  仅仅就封装了一些本人工作中常用到的方法,这儿贴出来,代码如有不当之处,麻烦指出(不胜感激): /** * @author ying.dong * DateUtils */ public class DateUti

北京赛车PK10改单软件——已经整套源码的工作原理和代码的编写方式实战分享

北京赛车PK10改单技术分享. 无需账号和密码,业务QQ:博客昵称或者点击联系.只需要提供网址即可做到无痕修改,大家可以放心下载使用,禁止用于非法行业 本北京赛车PK10改单软件都是免费提供使用的 本软件支持各种网盘程序改单只要能读取到数据库成功率百分之百:支持北京赛车PK10改单.时时彩改单,快三改单 六合彩网站改单,各类高频彩改单,各种数据修改 下面我们来介绍下软件操作的详细的操作步骤 <package id="Microsoft.Web.Infrastructure" ve

JAVA NIO工作原理及代码示例

简介:本文主要介绍了JAVA NIO中的Buffer, Channel, Selector的工作原理以及使用它们的若干注意事项,最后是利用它们实现服务器和客户端通信的代码实例. 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. ByteBuffer 1.1直接缓冲区和非直接缓冲区 下面是创建ByteBuffer对象的几种方式 static ByteBuffer allocate(int capacity) static Byte

Java三大器之监听器(Listener)的工作原理和代码演示

现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁.主要作用是:做一些初始化的内容添加工作.设置一些基本的内容.比如一些参数或者是一些固定的对象等等.首先来看一下ServletContextListener接口的源代码: public abstract interface ServletContextListener ext

爱工作、爱代码。想对待你的初恋爱人一样~

成功.一个需要时间.作品.去磨练的过程. 想要一个好的作品.你必须去热爱.全身心的爱它.了解它.感受它的每一次脾气.去抚慰.需要 去了解它的脾气.认识它.让你们成为无话不说的好朋友. 每一时刻.每一个角色.每一次聆听.每一件物品.没一行代码.都应该去认真对待.用心对待.因为他们都是有灵魂的.热爱他们.他们也会热爱你. 摆正自己的心态.給自己一个时间限度.去完成某些事情. 世间每一个任务.没一个角色都有戏份可言.人生如戏.演绎好你如戏的每一分钟. 随着世间的积累.知道的越多.你会跨界去尝试不同角色