通过一个ASP.NET程序模拟WCF基本架构

开发 后端
本文对WCF的基本架构作了一个大致的讲解。与传统的对WCF的工作流程进行平铺直叙不同,本文将借助于我们熟悉的ASP.NET作为请求处理平台,通过一个简单的托管程序模拟整个WCF客户端和服务端的架构。

在《WCF技术剖析》写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列。本系列的目的在于对《WCF技术剖析》的补充,会对书中的一些内容进行展开讲述,同时会囊括很多由于篇幅的原因忍痛割弃的内容。

本系列的第一篇,我将会对WCF的基本架构作一个大致的讲解。不过,一改传统对WCF的工作流程进行平铺直叙,我将另辟蹊径,借助于我们熟悉的ASP.NET作为请求处理平台,通过一个简单的托管程序模拟整个WCF客户端和服务端的架构。本篇文章的大部分内容节选自《WCF技术剖析(卷1)》第八章。

WCF框架处理流程和涉及的组件

我们的模拟程序将你搭建一个迷你版的WCF框架,为了展示WCF整个处理流程中使用到一些特殊组件。我们首先来简单介绍一下对于一个简单的WCF服务调用,WCF的客户端和服务端框架的处理流程,和该流程的每一个阶段都使用那些重要组件。

下面的列表列出了WCF服务端框架对于处理一个简单的WCF服务调用请求所提供的功能,以及相应的功能承载的组件:

请求消息的接收和回复消息的发送:服务端在传输层监听与接收来自客户的请求,并将经过编码后的回复消息通过传输层发送到客户端

请求消息的解码和回复消息的编码:将接收到的字节数组通过解码生成请求消息对象,并将回复消息通过编程转化成字节组。消息的编码和解码通过MessageEncoder完成,而MessageEncoderFactory负责创建该对象

请求消息的反序列化和回复消息的序列化:对请求消息进行反序列化,为服务操作的执行生成相应的输入参数,以及将服务操作执行的结果(返回值或者ref/out参数)序列化,并生成回复消息。序列化和反序列化通过DispatchMessageFormatter完成

服务对象的创建:创建或者激活服务对象实例,IntanceProvider用于服务对象的创建或获取

服务操作的执行:调用创建的服务对象的操作方法,并传入经过反序列化生成的输入参数。OperationInvoker完成对服务操作的最终执行

较之服务端的流程,客户端的流程显得相对简单,仅仅包含以下三个必需的阶段:

请求消息的序列化和回复消息的反序列化:生成请求消息并将输入参数序列化到请求消息中,以及对回复消息进行反序列化,转化成方法调用的返回值或者ref/out参数。序列化和反序列化通过ClienthMessageFormatter完成

请求消息的编码和回复消息的解码:对请求消息进行编码生成字节数组供传输层发送,以及将传输层接收到的字节数组解码生成恢复消息。消息的编码和解码通过MessageEncoder完成,而MessageEncoderFactory负责创建该对象

请求消息的发送和回复消息的接收:在传输层将经过编码的请求消息发送到服务端,以及将接收来自服务端的恢复消息

 精简版WCF客户端与服务端组件 
图1 精简版WCF客户端与服务端组件

图1反映了进行服务调用的必要步骤和使用到的相关WCF组件。在本案例演示中,我们需要做的就是手工创建这些组件,并通过我们自己的代码利用它们搭建一个建议版的WCF框架。如果读者能够对本案例的实现有一个清晰的理解,相信对于整个WCF的框架就不会感到陌生了。

图2显示了本案例解决方案的基本结构,总共分三个项目。Contracts用于定义服务契约,被服务端和客户端引用。客户端通过一个Console应用模拟,而服务端则通过一个ASP.NET Website实现。

WCF框架模拟案例应用结构 
图2 WCF框架模拟案例应用结构

#p#

步骤一、通过服务契约类型创建相关组件

WCF在整个服务调用生命周期的不同阶段,会使用到不同的组件。我们通过一个方法将服务端和客户端所需的所有组件都创建出来,为此,我们在Contracts项目中添加了一个Utility类型,在Create< T>方法中创建所有的组件并通过输出参数的形式返回,泛型类型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.     }  

具体的实现如下,由于在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[0];  
  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[0];  
  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>()[0];  
  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. namespace Artech.WcfFrameworkSimulator.Contracts  
  2. {  
  3.     [ServiceContract(Namespace = "http://www.artech.com/")]  
  4.     public interface ICalculator  
  5.     {  
  6.         [OperationContract]  
  7.         double Add(double x, double y);  
  8.     }  
  9. }  
  10.    
  11.  
  12. public class CalculatorService : ICalculator  
  13. {  
  14.     public double Add(double x, double y)  
  15.     {  
  16.         return x + y;  
  17.     }  
  18. }  

#p#

步骤三、实现服务端对服务调用请求的处理

我们通过一个ASP.NET的Web Page来模拟WCF服务端对服务请求的处理,下面的Calculator类型相关的代码实际上就是Calculator.aspx的后台代码(Code Behind)。整个处理流程不算复杂。在构造函数中,调用Utility的Create< ICalculator>方法,将所需的组件进行初始化,而具体的服务调用请求处理的逻辑在直接写在Web Page的Load事件中。

首先,通过MessageCoderFactory创建MessageEncoder对接收到的以HttpRequest形式体现的服务调用请求进行解码,并生成请求消息。通过请求消息得到当前服务操作的Action属性后,在初始化过程中得到的基于服务契约所有MethodInfo列表中,根据该Action得到当前操作对应的MethodInfo对象。借助于MethodInfo对象得到操作方法的输入参数和输出参数数量后,创建两个对象数组,分别用于保存通过DispatchMessageFormatter对象对于请求消息进行反序列化得到的输入参数,和通过OperationInvoker执行操作方法得到的输出参数。在OperationInvoker执行操作方法之前,通过反射的方式直接创建服务对象,这一步在真正的WCF框架中是通过InstanceProvider实现的。

通过OperationInvoker执行操作方法的结果有两种形式:返回值和输出参数(包括引用参数)。它们通过被传入DispatchMessageFormatter被序列化并生成回复消息对象。回复消息通过MessageCoderFactory创建MessageEncoder进行编码后通过HttpResponse返回。

  1. public partial class Calculator : System.Web.UI.Page  
  2. {  
  3.     private static MessageVersion messageversion = MessageVersion.Default;  
  4.     private static MessageEncoderFactory encoderFactory;  
  5.     private static IDictionary< string, IDispatchMessageFormatter> dispatchFormatters;  
  6.     private static IDictionary< string, IOperationInvoker> operationInvokers;  
  7.     private static IDictionary< string, MethodInfo> methods;  
  8.  
  9.     protected Calculator()  
  10.     {  
  11.         IDictionary< string, IClientMessageFormatter> clientFormatters;  
  12.         Utility.Create< ICalculator>(out encoderFactory, out clientFormatters, out dispatchFormatters, out operationInvokers, out methods);  
  13.     }  
  14.  
  15.     protected void Page_Load(object sender, EventArgs e)  
  16.     {  
  17.         //对HttpPRequest进行解码生成请求消息对象  
  18.         Message request = encoderFactory.Encoder.ReadMessage(this.Request.InputStream, int.MaxValue, "application/soap+xml; charset=utf-8");  
  19.  
  20.         //通过请求消息得到代表服务操作的Action  
  21.         string action = request.Headers.Action;  
  22.  
  23.         //通过Action从MethodInfo字典中获取服务操作对应的MethodInfo对象  
  24.         MethodInfo method = methods[action];  
  25.  
  26.         //得到输出参数的数量  
  27.         int outArgsCount = 0;  
  28.         foreach (var parameter in method.GetParameters())  
  29.         {  
  30.             if (parameter.IsOut)  
  31.             {  
  32.                 outArgsCount++;  
  33.             }  
  34.         }  
  35.  
  36.         //创建数组容器,用于保存请求消息反序列后生成的输入参数对象  
  37.         int inputArgsCount = method.GetParameters().Length - outArgsCount;  
  38.         object[] parameters = new object[inputArgsCount];  
  39.         dispatchFormatters[action].DeserializeRequest(request, parameters);  
  40.  
  41.         List< object> inputArgs = new List< object>();  
  42.         object[] outArgs = new object[outArgsCount];  
  43.         //创建服务对象,在WCF中服务对象通过InstanceProvider创建  
  44.         object serviceInstance = Activator.CreateInstance(typeof(CalculatorService));  
  45.         //执行服务操作  
  46.         object result = operationInvokers[action].Invoke(serviceInstance, parameters, out outArgs);  
  47.         //将操作执行的结果(返回值或者输出参数)序列化生成回复消息  
  48.         Message reply = dispatchFormatters[action].SerializeReply(messageversion, outArgs, result);  
  49.         this.Response.ClearContent();  
  50.         this.Response.ContentEncoding = Encoding.UTF8;  
  51.         this.Response.ContentType = "application/soap+xml; charset=utf-8";  
  52.         //对回复消息进行编码,并将编码后的消息通过HttpResponse返回  
  53.         encoderFactory.Encoder.WriteMessage(reply, this.Response.OutputStream);  
  54.         this.Response.Flush();  
  55.     }  
  56. }  

步骤四、实现客户端对服务调用请求的处理

由于在客户端对服务请求的处理是通过一个RealProxy(ServiceChannelFactory)实现的,为了真实模拟WCF处理框架,在这里通过一个自定义RealProxy来实现客户端相关的服务调用请求的处理。下面代码中定义的ServiceRealProxy< IContract>就是这样一个自定义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[0];  
  35.             string operationName = string.IsNullOrEmpty(attribute.Name) ? methodCall.MethodName : attribute.Name;  
  36.  
  37.             //序列化请求消息  
  38.             Message requestMessage = this._messageFormatters[operationName].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[GetRefOutParameterCount(methodCall.MethodBase)];  
  64.             object returnValue = this._messageFormatters[operationName].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[refOutParamPositionArray[i]] = refOutArgs[i];  
  98.             }  
  99.         }  
  100.     }  
  101. }  

在真正的WCF客户端框架下,客户端通过ChannelFactory< T>创建服务代理对象进行服务的调用,在这里我们也创建一个完成相似功能的工厂类型: SerivceProxyFactory< T>,泛型类型T代表服务契约类型。

用于创建服务代理的Create方法很简单:先通过Utility.Create< T>方法创建客户端进行服务调用必须的相关组件对象,通过这些对象连同该方法的参数(消息版本和服务目的地址)创建ServiceRealProxy< T>对象,最终返回的是该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.     }  

那么在最终的客户端代码中就可以借助SerivceProxyFactory< T>创建服务代理进行服务调用了,而这里服务的目标地址实际上是上面用于模拟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(12);  
  9.             Console.WriteLine("x + y = {2} when x = {0} and y = {1}"12, result);  
  10.         }  
  11.     }  

执行结果:

x + y = 3 when x = 1 and y = 2

【编辑推荐】

  1. WCF初试,用JQuery实现loading的功能
  2. jQuery调用WCF服务传递JSON对象
  3. WCF的传输安全机制
  4. 详解WCF中的变更处理:不可不知的最佳实践
  5. 使用ASP.NET AJAX调用WCF服务项模板
责任编辑:yangsai 来源: 博客园
相关推荐

2009-07-27 17:46:42

WCF服务ASP.NET应用程序

2009-07-27 17:54:39

WCF服务ASP.NET

2009-07-27 17:38:30

WCF服务ASP.NET 3.5

2009-07-29 17:21:10

2009-08-04 16:24:19

合格的ASP.NET程

2009-07-20 17:39:36

WCF服务ASP.NET AJA

2009-07-21 09:53:55

ASP.NET AJAWCF服务

2011-12-20 10:02:01

ASP.NET

2010-01-26 13:15:42

ASP.NET MVC

2009-07-27 17:51:58

WCF服务ASP.NET

2009-07-23 16:44:50

XML Web服务使用ASP.NET构造

2009-07-23 16:21:07

static变量ASP.NET

2009-12-08 16:59:52

WCF服务

2012-08-27 10:11:43

ASP.NET

2009-08-05 14:27:55

ASP.NET模板基本

2009-07-22 17:45:35

ASP.NET教程

2009-04-07 16:21:35

ASP.NETAJAXWCF

2009-07-22 17:32:40

ASP.NET应用程序

2009-07-21 15:02:19

ASP.NET应用程序

2009-07-28 18:00:11

ASP.NET程序
点赞
收藏

51CTO技术栈公众号