首页 > 代码库 > WCF 代理 是怎么工作的?用代码说话
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 ();107108 return CreateChannel (Endpoint.Address);109 }
CreateChannel方法的一个重载方法
133 public virtual TChannel CreateChannel (EndpointAddress address, Uri via)134 {135 #if MONOTOUCH136 throw new InvalidOperationException ("MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance");137 #else138 var existing = Endpoint.Address;139 try {140141 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 have147 // no special magic), we have to use different approach148 // 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 else155 throw;156 } finally {157 Endpoint.Address = existing;158 }159 #endif160 }
翻一下一下注释:在.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 }077078 string modname = "dummy";079 Type crtype =080#if !NET_2_1081 duplex ? typeof (DuplexClientRuntimeChannel) :082#endif083 typeof (ClientRuntimeChannel);084085 // 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 ());093094 //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);116117 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 implementation129 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_1133 if (od.SyncMethod != null)134 GenerateMethodImpl (c, crtype.GetMethod ("Process", bf), od.Name, od.SyncMethod);135#endif136 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 }141142 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 ();264265 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_PROXY284 CodePropertyReference argMethodInfo = GetOperationMethod (m, b, name, "EndMethod");285#else286 CodeMethodCall argMethodInfo = new CodeMethodCall (typeof (MethodBase), "GetCurrentMethod");287#endif288 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 parameters302 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 =http://www.mamicode.com/ 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 = http://www.mamicode.com/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(待续)