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(待续)