[原创]WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构
细算起来,已经有好几个月没有真正的写过文章了。近半年以来,一直忙于我的第一本WCF专著《WCF技术剖析》的写作,一直无暇管理自己的Blog。到目前为止《WCF技术剖析(卷1)》的写作暂告一段落,初步预计于下个月由武汉博文视点出版。在《WCF技术剖析》写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列。本系列的目的在于对《WCF技术剖析》的补充,会对书中的一些内容进行展开讲述,同时会囊括很多由于篇幅的原因忍痛割弃的内容。本系列的第一篇,我将会对WCF的基本架构作一个大致的讲解。不过,一改传统对WCF的工作流程进行平铺直叙,我将另辟蹊径,借助于我们熟悉的ASP.NET作为请求处理平台,通过一个简单的托管程序模拟整个WCF客户端和服务端的架构。Source Code下载:Artech.WcfFrameworkSimulator.zip
WCF框架处理流程和涉及的组件
我们的模拟程序将你搭建一个迷你版的WCF框架,为了展示WCF整个处理流程中使用到一些特殊组件。我们首先来简单介绍一下对于一个简单的WCF服务调用,WCF的客户端和服务端框架的处理流程,和该流程的每一个阶段都使用那些重要组件。
下面的列表列出了WCF服务端框架对于处理一个简单的WCF服务调用请求所提供的功能,以及相应的功能承载的组件:
[*]请求消息的接收和回复消息的发送:服务端在传输层监听与接收来自客户的请求,并将经过编码后的回复消息通过传输层发送到客户端
[*]请求消息的解码和回复消息的编码:将接收到的字节数组通过解码生成请求消息对象,并将回复消息通过编程转化成字节组。消息的编码和解码通过MessageEncoder完成,而MessageEncoderFactory负责创建该对象
[*]请求消息的反序列化和回复消息的序列化:对请求消息进行反序列化,为服务操作的执行生成相应的输入参数,以及将服务操作执行的结果(返回值或者ref/out参数)序列化,并生成回复消息。序列化和反序列化通过DispatchMessageFormatter完成
[*]服务对象的创建:创建或者激活服务对象实例,InstanceProvider用于服务对象的创建或获取
[*]服务操作的执行:调用创建的服务对象的操作方法,并传入经过反序列化生成的输入参数。OperationInvoker完成对服务操作的最终执行
较之服务端的流程,客户端的流程显得相对简单,仅仅包含以下三个必需的阶段:
[*]请求消息的序列化和回复消息的反序列化:生成请求消息并将输入参数序列化到请求消息中,以及对回复消息进行反序列化,转化成方法调用的返回值或者ref/out参数。序列化和反序列化通过ClienthMessageFormatter完成
[*]请求消息的编码和回复消息的解码:对请求消息进行编码生成字节数组供传输层发送,以及将传输层接收到的字节数组解码生成恢复消息。消息的编码和解码通过MessageEncoder完成,而MessageEncoderFactory负责创建该对象
[*]请求消息的发送和回复消息的接收:在传输层将经过编码的请求消息发送到服务端,以及将接收来自服务端的恢复消息
图1精简版WCF客户端与服务端组件
图1反映了进行服务调用的必要步骤和使用到的相关WCF组件。在本案例演示中,我们需要做的就是手工创建这些组件,并通过我们自己的代码利用它们搭建一个简易版的WCF框架。如果读者能够对本案例的实现有一个清晰的理解,相信对于整个WCF的框架就不会感到陌生了。
图2显示了本案例解决方案的基本结构,总共分三个项目。Contracts用于定义服务契约,被服务端和客户端引用。客户端通过一个Console应用模拟,而服务端则通过一个ASP.NET Website实现。
图2 WCF框架模拟案例应用结构
步骤一、通过服务契约类型创建相关组件
WCF在整个服务调用生命周期的不同阶段,会使用到不同的组件。我们通过一个方法将服务端和客户端所需的所有组件都创建出来,为此,我们在Contracts项目中添加了一个Utility类型,在Create方法中创建所有的组件并通过输出参数的形式返回,泛型类型T表示的是服务契约类型。在该方法中,输出参数encoderFactory被服务端和客户端用于消息的编码和解码,clientFormatters和dispatchFormatters以字典的形式包含了基于服务操作的IClientMessageFormatter和IDispatchMessageFormatter,其中clientFormatters和dispatchFormatters的Key分别为操作名称和操作对应的Action。同样通过字典形式返回的operationInvokers和methods用于在服务端执行相应的操作方法,Key同样为操作对应的Action。
1: public static class Utility 2: { 3: public static void Create<T>(out MessageEncoderFactory encoderFactory, 4: out IDictionary<string, IClientMessageFormatter> clientFormatters, 5: out IDictionary<string, IDispatchMessageFormatter> dispatchFormatters, 6: out IDictionary<string, IOperationInvoker> operationInvokers, 7: out IDictionary<string, MethodInfo> methods) 8: { 9: //省略实现 10: } 11: }具体的实现如下,由于在WCF框架中使用的MessageEncoderFactory(TextMessageEncoderFactory)、MessageFormatter(DataContractSerializerOperationFormatter)和OperationInvoker(SyncMethodInvoker)都是一些内部类型,所以只能通过反射的方式创建它们。而操作名称和Action也主要通过反射的原理解析应用在服务方法上的OperationContractAttribute得到。 1: public static void Create<T>(out MessageEncoderFactory encoderFactory, 2: out IDictionary<string, IClientMessageFormatter> clientFormatters, 3: out IDictionary<string, IDispatchMessageFormatter> dispatchFormatters, 4: out IDictionary<string, IOperationInvoker> operationInvokers, 5: out IDictionary<string, MethodInfo> methods) 6: { 7: //确保类型T是应用了ServiceContractAttribute的服务契约 8: object[] attributes = typeof(T).GetCustomAttributes(typeof(ServiceContractAttribute), false); 9: if (attributes.Length == 0) 10: { 11: throw new InvalidOperationException(string.Format("The type \"{0}\" is not a ServiceContract!", typeof(T).AssemblyQualifiedName)); 12: } 13: 14: //创建字典保存IClientMessageFormatter、IDispatchMessageFormatter、IOperationInvoker和MethodInfo 15: clientFormatters = new Dictionary<string, IClientMessageFormatter>(); 16: dispatchFormatters = new Dictionary<string, IDispatchMessageFormatter>(); 17: operationInvokers = new Dictionary<string, IOperationInvoker>(); 18: methods = new Dictionary<string, MethodInfo>(); 19: 20: //MessageEncoderFactory 21: string encoderFactoryType = "System.ServiceModel.Channels.TextMessageEncoderFactory,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; 22: encoderFactory = (MessageEncoderFactory)Activator.CreateInstance(Type.GetType(encoderFactoryType), MessageVersion.Default, Encoding.UTF8, int.MaxValue, int.MaxValue, new XmlDictionaryReaderQuotas()); 23: 24: //得到OperationDecription列表 25: string defaultNamespace = "http://tempuri.org/"; 26: ServiceContractAttribute serviceAttribute = (ServiceContractAttribute)attributes; 27: string serviceNamepace = string.IsNullOrEmpty(serviceAttribute.Namespace) ? defaultNamespace : serviceAttribute.Namespace; 28: string serviceName = string.IsNullOrEmpty(serviceAttribute.Name) ? typeof(T).Name : serviceAttribute.Name; 29: var operations = ContractDescription.GetContract(typeof(T)).Operations; 30: 31: //得到具体的IClientMessageFormatter、IDispatchMessageFormatter和IOperationInvoker的具体类型 32: //IClientMessageFormatter+IDispatchMessageFormatter:DataContractSerializerOperationFormatter 33: //IOperationInvoker:SyncMethodInvoker 34: string formatterTypeName = "System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; 35: Type formatterType = Type.GetType(formatterTypeName); 36: ConstructorInfo formatterConstructor = formatterType.GetConstructor(new Type[] { typeof(OperationDescription), typeof(DataContractFormatAttribute), typeof(DataContractSerializerOperationBehavior) }); 37: string operationInvokerTypeName = "System.ServiceModel.Dispatcher.SyncMethodInvoker,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; 38: Type operationInvokerType = Type.GetType(operationInvokerTypeName); 39: 40: foreach (MethodInfo method in typeof(T).GetMethods()) 41: { 42: attributes = method.GetCustomAttributes(typeof(OperationContractAttribute), true); 43: if (attributes.Length > 0) 44: { 45: OperationContractAttribute operationAttribute = (OperationContractAttribute)attributes; 46: string operationName = string.IsNullOrEmpty(operationAttribute.Name) ? method.Name : operationAttribute.Name; 47: //通过OperationContractAttribute得到Action 48: string action; 49: if (string.IsNullOrEmpty(operationAttribute.Action)) 50: { 51: action = string.Format("{0}{1}/{2}", serviceNamepace, serviceName, operationName); 52: } 53: else 54: { 55: action = operationAttribute.Action; 56: } 57: 58: OperationDescription operation = operations.Where(op => op.Name == operationName).ToArray<OperationDescription>(); 59: //通过反射创建DataContractSerializerOperationFormatter对象 60: object formatter = formatterConstructor.Invoke(new object[] { operation, new DataContractFormatAttribute(), null }); 61: clientFormatters.Add(operationName, formatter as IClientMessageFormatter); 62: dispatchFormatters.Add(action, formatter as IDispatchMessageFormatter); 63: 64: //通过反射创建SyncMethodInvoker对象 65: IOperationInvoker operationInvoker = (IOperationInvoker)Activator.CreateInstance(operationInvokerType, method); 66: operationInvokers.Add(action, operationInvoker); 67: methods.Add(action, method); 68: } 69: }步骤二、创建服务契约和实现服务
接下来为本案例创建一个服务契约和实现该契约。服务契约定义在Contracts项目,具体的服务实现在模拟服务端的ASP.NET Web站点中。简单起见,依然沿用计算服务的例子。
1: 2: public interface ICalculator 3: { 4: 5: double Add(double x, double y); 6: }
1: public class CalculatorService : ICalculator 2: { 3: public double Add(double x, double y) 4: { 5: return x + y; 6: } 7: }步骤三、实现服务端对服务调用请求的处理
我们通过一个ASP.NET的Web Page来模拟WCF服务端对服务请求的处理,下面的Calculator类型相关的代码实际上就是Calculator.aspx的后台代码(Code Behind)。整个处理流程不算复杂。在构造函数中,调用Utility的Create方法,将所需的组件进行初始化,而具体的服务调用请求处理的逻辑在直接写在Web Page的Load事件中。
首先,通过MessageCoderFactory创建MessageEncoder对接收到的以HttpRequest形式体现的服务调用请求进行解码,并生成请求消息。通过请求消息得到当前服务操作的Action属性后,在初始化过程中得到的基于服务契约所有MethodInfo列表中,根据该Action得到当前操作对应的MethodInfo对象。借助于MethodInfo对象得到操作方法的输入参数和输出参数数量后,创建两个对象数组,分别用于保存通过DispatchMessageFormatter对象对于请求消息进行反序列化得到的输入参数,和通过OperationInvoker执行操作方法得到的输出参数。在OperationInvoker执行操作方法之前,通过反射的方式直接创建服务对象,这一步在真正的WCF框架中是通过InstanceProvider实现的。
通过OperationInvoker执行操作方法的结果有两种形式:返回值和输出参数(包括引用参数)。它们通过被传入DispatchMessageFormatter被序列化并生成回复消息对象。回复消息通过MessageCoderFactory创建MessageEncoder进行编码后通过HttpResponse返回。
1: 2: ublic partial class Calculator : System.Web.UI.Page 3: 4: private static MessageVersion messageversion = MessageVersion.Default; 5: private static MessageEncoderFactory encoderFactory; 6: private static IDictionary<string, IDispatchMessageFormatter> dispatchFormatters; 7: private static IDictionary<string, IOperationInvoker> operationInvokers; 8: private static IDictionary<string, MethodInfo> methods; 9: 10: protected Calculator() 11: { 12: IDictionary<string, IClientMessageFormatter> clientFormatters; 13: Utility.Create<ICalculator>(out encoderFactory, out clientFormatters, out dispatchFormatters, out operationInvokers, out methods); 14: } 15: 16: protected void Page_Load(object sender, EventArgs e) 17: { 18: //对HttpPRequest进行解码生成请求消息对象 19: Message request = encoderFactory.Encoder.ReadMessage(this.Request.InputStream, int.MaxValue, "application/soap+xml; charset=utf-8"); 20: 21: //通过请求消息得到代表服务操作的Action 22: string action = request.Headers.Action; 23: 24: //通过Action从MethodInfo字典中获取服务操作对应的MethodInfo对象 25: MethodInfo method = methods; 26: 27: //得到输出参数的数量 28: int outArgsCount = 0; 29: foreach (var parameter in method.GetParameters()) 30: { 31: if (parameter.IsOut) 32: { 33: outArgsCount++; 34: } 35: } 36: 37: //创建数组容器,用于保存请求消息反序列后生成的输入参数对象 38: int inputArgsCount = method.GetParameters().Length - outArgsCount; 39: object[] parameters = new object; 40: dispatchFormatters.DeserializeRequest(request, parameters); 41: 42: List<object> inputArgs = new List<object>(); 43: object[] outArgs = new object; 44: //创建服务对象,在WCF中服务对象通过InstanceProvider创建 45: object serviceInstance = Activator.CreateInstance(typeof(CalculatorService)); 46: //执行服务操作 47: object result = operationInvokers.Invoke(serviceInstance, parameters, out outArgs); 48: //将操作执行的结果(返回值或者输出参数)序列化生成回复消息 49: Message reply = dispatchFormatters.SerializeReply(messageversion, outArgs, result); 50: this.Response.ClearContent(); 51: this.Response.ContentEncoding = Encoding.UTF8; 52: this.Response.ContentType = "application/soap+xml; charset=utf-8"; 53: //对回复消息进行编码,并将编码后的消息通过HttpResponse返回 54: encoderFactory.Encoder.WriteMessage(reply, this.Response.OutputStream); 55: this.Response.Flush(); 56: } 57: 步骤四、实现客户端对服务调用请求的处理
由于在客户端对服务请求的处理是通过一个RealProxy(ServiceChannelFactory)实现的,为了真实模拟WCF处理框架,在这里通过一个自定义RealProxy来实现客户端相关的服务调用请求的处理。下面代码中定义的ServiceRealProxy就是这样一个自定义RealProxy。
用于处理服务调用请求的相关组件对象,比如MessageEncoderFactory和IClientMessageFormatter字典,以及所需的属性,比如消息的版本和服务的目的地址,通过构造函数指定。而具体的请求处理实现在重写的Invoke方法之中。首先通过解析应用在当前方法的上面的OperationContractAttribute得到服务操作的名称,以此为Key从IClientMessageFormatter字典中得到当前服务操作对应的IClientMessageFormatter对象。当前操作方法调用的输入参数通过IClientMessageFormatter对象进行序列化后生成请求消息。为请求消息添加必要的寻址报头后,通过MessageEncoderFactory创建的MessageEncoder对请求消息进行编码。经过编码的消息以HttpRequest的形式发送到服务端,从而完成了服务调用请求的发送。
服务调用的结果通过HttpResponse的形式返回后,先通过MessageEncoder对其解码,并生成回复消息。回复消息通过IClientMessageFormatter进行反序列化后,在消息中以XML InfoSet实行体现的结果被转化成具体的对象,这些对象被最终影射为方法调用的返回值和输出参数(包含引用参数)。
1: namespace Artech.WcfFrameworkSimulator.Client 2: { 3: public class ServiceRealProxy<IContract> : RealProxy 4: { 5: private Uri _remoteAddress; 6: private IDictionary<string, IClientMessageFormatter> _messageFormatters; 7: private MessageVersion _messageVersion = MessageVersion.Default; 8: private MessageEncoderFactory _messageEncoderFactory; 9: 10: public ServiceRealProxy(MessageVersion messageVersion, Uri address, IDictionary<string, IClientMessageFormatter> messageFormaters, MessageEncoderFactory messageEncoderFactory) 11: : base(typeof(IContract)) 12: { 13: object[] attribute = typeof(IContract).GetCustomAttributes(typeof(ServiceContractAttribute), false); 14: if (attribute.Length == 0) 15: { 16: throw new InvalidOperationException(string.Format("The type \"{0}\" is not a ServiceContract!", typeof(IContract).AssemblyQualifiedName)); 17: } 18: this._messageVersion = messageVersion; 19: this._remoteAddress = address; 20: this._messageFormatters = messageFormaters; 21: this._messageEncoderFactory = messageEncoderFactory; 22: } 23: 24: public override IMessage Invoke(IMessage msg) 25: { 26: IMethodCallMessage methodCall = (IMethodCallMessage)msg; 27: 28: //Get Operation name. 29: object[] attributes = methodCall.MethodBase.GetCustomAttributes(typeof(OperationContractAttribute), true); 30: if (attributes.Length == 0) 31: { 32: throw new InvalidOperationException(string.Format("The method \"{0}\" is not a valid OperationContract.", methodCall.MethodName)); 33: } 34: OperationContractAttribute attribute = (OperationContractAttribute)attributes; 35: string operationName = string.IsNullOrEmpty(attribute.Name) ? methodCall.MethodName : attribute.Name; 36: 37: //序列化请求消息 38: Message requestMessage = this._messageFormatters.SerializeRequest(this._messageVersion, methodCall.InArgs); 39: 40: //添加必要的WS-Address报头 41: EndpointAddress address = new EndpointAddress(this._remoteAddress); 42: requestMessage.Headers.MessageId = new UniqueId(Guid.NewGuid()); 43: requestMessage.Headers.ReplyTo = new EndpointAddress("http://www.w3.org/2005/08/addressing/anonymous"); 44: address.ApplyTo(requestMessage); 45: 46: //对请求消息进行编码,并将编码生成的字节发送通过HttpWebRequest向服务端发送 47: HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(this._remoteAddress); 48: webRequest.Method = "Post"; 49: webRequest.KeepAlive = true; 50: webRequest.ContentType = "application/soap+xml; charset=utf-8"; 51: ArraySegment<byte> bytes = this._messageEncoderFactory.Encoder.WriteMessage(requestMessage, int.MaxValue, BufferManager.CreateBufferManager(long.MaxValue, int.MaxValue)); 52: webRequest.ContentLength = bytes.Array.Length; 53: webRequest.GetRequestStream().Write(bytes.Array, 0, bytes.Array.Length); 54: webRequest.GetRequestStream().Close(); 55: WebResponse webResponse = webRequest.GetResponse(); 56: 57: //对HttpResponse进行解码生成回复消息. 58: Message responseMessage = this._messageEncoderFactory.Encoder.ReadMessage(webResponse.GetResponseStream(), int.MaxValue); 59: 60: //回复消息进行反列化生成相应的对象,并映射为方法调用的返回值或者ref/out参数 61: object[] allArgs = (object[])Array.CreateInstance(typeof(object), methodCall.ArgCount); 62: Array.Copy(methodCall.Args, allArgs, methodCall.ArgCount); 63: object[] refOutParameters = new object; 64: object returnValue = this._messageFormatters.DeserializeReply(responseMessage, refOutParameters); 65: MapRefOutParameter(methodCall.MethodBase, allArgs, refOutParameters); 66: 67: //通过ReturnMessage的形式将返回值和ref/out参数返回 68: return new ReturnMessage(returnValue, allArgs, allArgs.Length, methodCall.LogicalCallContext, methodCall); 69: } 70: 71: private int GetRefOutParameterCount(MethodBase method) 72: { 73: int count = 0; 74: foreach (ParameterInfo parameter in method.GetParameters()) 75: { 76: if (parameter.IsOut || parameter.ParameterType.IsByRef) 77: { 78: count++; 79: } 80: } 81: return count; 82: } 83: 84: private void MapRefOutParameter(MethodBase method, object[] allArgs, object[] refOutArgs) 85: { 86: List<int> refOutParamPositionsList = new List<int>(); 87: foreach (ParameterInfo parameter in method.GetParameters()) 88: { 89: if (parameter.IsOut || parameter.ParameterType.IsByRef) 90: { 91: refOutParamPositionsList.Add(parameter.Position); 92: } 93: } 94: int[] refOutParamPositionArray = refOutParamPositionsList.ToArray(); 95: for (int i = 0; i < refOutArgs.Length; i++) 96: { 97: allArgs] = refOutArgs; 98: } 99: } 100: } 101: }在真正的WCF客户端框架下,客户端通过ChannelFactory创建服务代理对象进行服务的调用,在这里我们也创建一个完成相似功能的工厂类型: SerivceProxyFactory,泛型类型T代表服务契约类型。
用于创建服务代理的Create方法很简单:先通过Utility.Create方法创建客户端进行服务调用必须的相关组件对象,通过这些对象连同该方法的参数(消息版本和服务目的地址)创建ServiceRealProxy对象,最终返回的是该RealProxy的TransparentProxy。
1: namespace Artech.WcfFrameworkSimulator.Client 2: { 3: public static class SerivceProxyFactory<T> 4: { 5: public static T Create(MessageVersion messageVersion, Uri remoteAddress) 6: { 7: MessageEncoderFactory encoderFactory; 8: IDictionary<string, IClientMessageFormatter> clientFormatters; 9: IDictionary<string, IDispatchMessageFormatter> dispatchFormatters; 10: IDictionary<string, IOperationInvoker> operationInvokers; 11: IDictionary<string, MethodInfo> methods; 12: Utility.Create<T>(out encoderFactory, out clientFormatters, out dispatchFormatters, out operationInvokers, out methods); 13: ServiceRealProxy<T> realProxy = new ServiceRealProxy<T>(messageVersion, remoteAddress, clientFormatters, encoderFactory); 14: return (T)realProxy.GetTransparentProxy(); 15: } 16: } 17: }那么在最终的客户端代码中就可以借助SerivceProxyFactory创建服务代理进行服务调用了,而这里服务的目标地址实际上是上面用于模拟WCF服务端框架的.aspx Web Page的地址。
1: namespace Artech.WcfFrameworkSimulator.Client 2: { 3: class Program 4: { 5: static void Main(string[] args) 6: { 7: ICalculator calculator = SerivceProxyFactory<ICalculator>.Create(MessageVersion.Default, new Uri("http://localhost/Artech.WcfFrameworkSimulator/Calculator.aspx")); 8: double result = calculator.Add(1, 2); 9: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, result); 10: } 11: } 12: }执行结果:
1: x + y = 3 when x = 1 and y = 2
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]