在《通过一个模拟程序让你明白ASP.NET MVC是如何运行的》一文中我通过一个普通的ASP.NET Web程序模拟了ASP.NET MVC的执行流程,现在我们通过类似的原理创建一个用于模拟WCF服务端和客户端工作原理的模拟程序。[源代码从这里下载]
目录
一、基本的组件和执行流程
二、创建自定义HttpHandler实现对服务调用请求的处理
三、定义创建WCF组件的工厂
四、定义HttpModule映射WcfHandler
五、创建自定义的真实代理实现服务的调用
六、定义服务代理工厂
七、服务“寄宿”和调用
我们只模拟WCF完成一个简单的服务调用所必需的组件和流程,右图反映了进行服务调用的必要步骤和使用的相关WCF组件。下面列出了服务端涉及的组件和流程:
相较于服务端的请求监听、消息接收、服务实例激活和操作调用流程,客户端的处理流程显得相对简单,仅仅包含以下3个必需的步骤:
本实例的解决方法依然采用包含Service.Interface、Service和Client三个项目的结构,不过Service项目现在是一个Web应用。也就是说我们通过一个Web应用的方式实现WCF端对服务调用请求的整个处理流程。
对于一个ASP.NET Web应用来说,对请求的处理最终都落实到一个具体的HttpHandler对象上,所以我们通过实现接口System.Web.IHttpHandler自定义了如下一个WcfHandler用于处理针对WCF服务请求的处理。
1: public class WcfHandler: IHttpHandler
2: {
3: //其他成员
4: public Type ServiceType { get; private set; }
5: public MessageEncoderFactory MessageEncoderFactory { get; private set; }
6: public IDictionary<string, MethodInfo> Methods { get; private set; }
7: public IDictionary<string, IDispatchMessageFormatter> MessageFormatters { get; private set; }
8: public IDictionary<string, IOperationInvoker> OperationInvokers { get; private set; }
9:
10: public bool IsReusable
11: {
12: get { return false; }
13: }
14:
15: public WcfHandler(Type serviceType, MessageEncoderFactory messageEncoderFactory)
16: {
17: this.ServiceType = serviceType;
18: this.MessageEncoderFactory = messageEncoderFactory;
19: this.Methods = new Dictionary<string, MethodInfo>();
20: this.MessageFormatters = new Dictionary<string, IDispatchMessageFormatter>();
21: this.OperationInvokers = new Dictionary<string, IOperationInvoker>();
22: }
23: }
如上面代码所示,上述的关于WCF服务端框架所需的组件以只读属性的方式体现在WcfHandler上。ServiceType属性表示服务的类型,基于这个类型通过反射创建服务实例。消息编码器工厂通过MessageEncoderFactory属性表示,两个字典类型的属性MessageFormatters和OperationInvokers代表基于操作的分发消息格式化器和操作调用器列表,字典的Key为操作请求消息的<Action>报头的值。而Methods表示契约接口所有操作方法的MethodInfo集合。
针对WCF服务的请求处理实现在如下的ProcessRequest方法中,执行的逻辑也不算复杂。我们直接通过消息编码器工厂创建的消息编码从当前HTTP请求的输入流中读取出消息。然后根据当前消息的<Action>报头的值从MessageFormatters属性中找到与当前请求操作相匹配的分发消息格式化器对消息进行反序列化。
接着直接通过反射的方式根据服务类型创建服务实例对象。同样根据当前消息的<Action>报头从OperationInvokers属性获取出基于当前请求操作的操作调用器,并将创建的服务实例和反序列化后生成的参数作为输入执行操作方法。
操作的执行结果通过分发消息格式化器进行序列化生成的消息最终通过消息编码器写入当前HTTP回复的输出流中返回给客户端。
1: public class WcfHandler: IHttpHandler
2: {
3: //其他成员
4: public void ProcessRequest(HttpContext context)
5: {
6: //对HttpPRequest进行解码生成请求消息对象
7: Message request = this.MessageEncoderFactory.Encoder.ReadMessage(context.Request.InputStream, int.MaxValue, "application/soap+xml; charset=utf-8");
8:
9: //通过请求消息得到代表服务操作的Action
10: string action = request.Headers.Action;
11:
12: //通过Action从MethodInfo字典中获取服务操作对应的MethodInfo对象
13: MethodInfo method = this.Methods[action];
14:
15: //得到输出参数的数量
16: int outArgsCount = 0;
17: foreach (var parameter in method.GetParameters())
18: {
19: if (parameter.IsOut)
20: {
21: outArgsCount++;
22: }
23: }
24:
25: //创建数组容器,用于保存请求消息反序列后生成的输入参数对象
26: int inputArgsCount = method.GetParameters().Length - outArgsCount;
27: object[] parameters = new object[inputArgsCount];
28: try
29: {
30: this.MessageFormatters[action].DeserializeRequest(request, parameters);
31: }
32: catch
33: {}
34:
35: List<object> inputArgs = new List<object>();
36: object[] outArgs = new object[outArgsCount];
37: //创建服务对象,在WCF中服务对象通过InstanceProvider创建
38:
39: object serviceInstance = Activator.CreateInstance(this.ServiceType);
40:
41: //执行服务操作
42: object result = this.OperationInvokers[action].Invoke(serviceInstance,parameters, out outArgs);
43:
44: //将操作执行的结果(返回值或者输出参数)序列化生成回复消息
45: Message reply = this.MessageFormatters[action].SerializeReply(request.Version, outArgs, result);
46: context.Response.ClearContent();
47: context.Response.ContentEncoding = Encoding.UTF8;
48: context.Response.ContentType = "application/soap+xml; charset=utf-8";
49:
50: //对回复消息进行编码,并将编码后的消息通过HttpResponse返回
51: this.MessageEncoderFactory.Encoder.WriteMessage(reply, context.Response.OutputStream);
52: context.Response.Flush();
53: }
54: }
对于本例来说,客户端和服务端需要的组件主要有四类,即消息编码器工厂、分发消息格式化器、客户端消息格式化器和操作调用器。我们通过具有如下定义的静态的工厂类ComponentBuilder来创建它们。我们调用操作行为DataContractSerializerOperationBehavior的GetFormatter方法来创建基于指定操作的消息格式化器。不过该方法是一个内部方法,所以我们是通过反射的方式来调用的。isProxy参数表示创建的是客户端消息格式化器(True)还是分发消息格式化器(False)。
消息编码器工厂通过基于文本编码方式绑定元素TextMessageEncodingBindingElement的CreateMessageEncoderFactory创建,传入的参数分别表示消息的版本和文本编码类型。我们采用SyncMethodInvoker以同步的方式进行操作的执行。由于SyncMethodInvoker是一个内部类型,所以我们不得不采用反射的方式来创建它。
1: public static class ComponentBuilder
2: {
3: public static object GetFormatter(OperationDescription operation, bool isProxy)
4: {
5: bool formatRequest = false;
6: bool formatReply = false;
7: DataContractSerializerOperationBehavior behavior = new DataContractSerializerOperationBehavior(operation);
8: MethodInfo method = typeof(DataContractSerializerOperationBehavior).GetMethod("GetFormatter", BindingFlags.Instance | BindingFlags.NonPublic);
9: return method.Invoke(behavior, new object[] { operation, formatRequest, formatReply, isProxy });
10: }
11:
12: public static MessageEncoderFactory GetMessageEncoderFactory(MessageVersion messageVersion, Encoding writeEncoding)
13: {
14: TextMessageEncodingBindingElement bindingElement = new TextMessageEncodingBindingElement(messageVersion, writeEncoding);
15: return bindingElement.CreateMessageEncoderFactory();
16: }
17: public static IOperationInvoker GetOperationInvoker(MethodInfo method)
18: {
19: string syncMethodInvokerType = "System.ServiceModel.Dispatcher.SyncMethodInvoker, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
20: Type type = Type.GetType(syncMethodInvokerType);
21: return (IOperationInvoker)Activator.CreateInstance(type, new object[]{method});
22: }
23: }
我们通过HttpModule的方式将用于处理WCF服务请求的映射到相应的WCF服务调用请求,为此我们定义了如下一个实现了System.Web.IHttpModule接口的WcfHttpModule类型。WcfHttpModule通过注册HttpApplication的BeginRequest事件的方式将创建的WcfHandler映射为处理当前HTTP请求的HttpHandler。
1: public class WcfHttpModule: IHttpModule
2: {
3: public void Dispose() {}
4:
5: public void Init(HttpApplication context)
6: {
7: context.BeginRequest += (sender, args) =>
8: {
9: string relativeAddress = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.Remove(0,2);
10: Type serviceType = RouteTable.Routes.Find(relativeAddress);
11: if (null == serviceType)
12: {
13: return;
14: }
15: IHttpHandler handler = this.CreateHttpHandler(serviceType);
16: context.Context.RemapHandler(handler);
17: };
18: }
19: protected IHttpHandler CreateHttpHandler(Type serviceType)
20: {
21: MessageEncoderFactory encoderFactory = ComponentBuilder.GetMessageEncoderFactory(MessageVersion.Default, Encoding.UTF8);
22: WcfHandler handler = new WcfHandler(serviceType, encoderFactory);
23: Type interfaceType = serviceType.GetInterfaces()[0];
24: ContractDescription contract = ContractDescription.GetContract(interfaceType);
25: foreach (OperationDescription operation in contract.Operations)
26: {
27: IDispatchMessageFormatter messageFormatter = (IDispatchMessageFormatter)ComponentBuilder.GetFormatter(operation, false);
28: handler.MessageFormatters.Add(operation.Messages[0].Action, messageFormatter);
29:
30: IOperationInvoker operationInvoker = ComponentBuilder.GetOperationInvoker(operation.SyncMethod);
31: handler.OperationInvokers.Add(operation.Messages[0].Action, operationInvoker);
32:
33: handler.Methods.Add(operation.Messages[0].Action, operation.SyncMethod);
34: }
35: return handler;
36: }
37: }
至于WcfHandler的创建,需要确定服务的类型。而服务的类型只能根据请求的地址来确定,这个IIS寄宿根据.svc文件来创建ServiceHost的原理是一样的。对于本例来说,我们需要对请求的地址和服务类型作一个映射,为此我们定义了如下一个RouteMapping的类型表示这个映射。
1: public class RouteMapping
2: {
3: public string Address { get; private set; }
4: public Type ServiceType { get; private set; }
5: public RouteMapping(string address, Type serviceType)
6: {
7: this.Address = address;
8: this.ServiceType = serviceType;
9: }
10: }
而映射表则通过如下一个继承自Collection<RouteMapping>的RouteTable来定义。泛型的Register<T>方法用于注册地址与服务类型的映射关系,而Find方法则根据地址获取相应的服务类型。静态属性Routes表示当前被使用的映射表,而在WcfHttpModule中正是通过这个静态属性根据解析出来的地址得到用于创建WcfHandler的服务类型的。
1: public class RouteTable: Collection<RouteMapping>
2: {
3: public static RouteTable Routes{get; private set;}
4: static RouteTable()
5: {
6: Routes = new RouteTable();
7: }
8: public Type Find(string address)
9: {
10: RouteMapping routeMapping = (from route in this
11: where string.Compare(route.Address, address,true) == 0
12: select route).FirstOrDefault();
13: return null == routeMapping? null: routeMapping.ServiceType;
14: }
15:
16: public void Register<T>(string address)
17: {
18: this.Add(new RouteMapping(address, typeof(T)));
19: }
20: }
ChannelFactory<TChannel>创建的服务代理仅仅是一个透明代理,而真实实现服务调用的是它的真实代理。为此我们创建了如下一个继承自RealProxy的泛型的ServiceChannelProxy<TChannel>,其中泛型参数为契约接口类型。
1: public class ServiceChannelProxy<TChannel>: RealProxy
2: {
3: //其他成员
4: public Uri Address { get; private set; }
5: public MessageVersion MessageVersion { get; private set; }
6: public IDictionary<string, IClientMessageFormatter> MessageFormatters { get; private set; }
7: public MessageEncoderFactory MessageEncoderFactory { get; private set; }
8:
9: public ServiceChannelProxy(Uri address, MessageVersion messageVersion, MessageEncoderFactory encoderFactory): base(typeof(TChannel))
10: {
11: this.Address = address;
12: this.MessageVersion = messageVersion;
13: this.MessageEncoderFactory = encoderFactory;
14: this.MessageFormatters = new Dictionary<string, IClientMessageFormatter>();
15: }
16: }
和WcfHttpHandler类似,进行服务调用所需的组件通过相应的只读属性表示。属性MessageEncoderFactory表示消息编码器工厂,而字典类型的MessageFormatters表示基于每个操作的客户端消息格式化器列表,其中的Key为操作的名称。属性Address表示被调用服务的地址。
针对透明代理的方法调用最终都会转移到针对真实真实代理的Invoke方法,所以我们将所有的服务调用操作实现在如下的Invoke方法中。我们首先获取代表当前调用方法的MethodBase上应用的OperationContractAttribute特性,并借此获得操作名称。
根据获取的操作名称从属性MessageFormatters属性中获得基于当前操作的客户端消息格式化器,并将方法调用转化消息。接着根据Address属性表示的服务调用地址创建EndpointAddress对象并将其附加到请求消息中。除此之外,还需要为请求消息添加一些必要的报头(比如<MessageId>和<ReplyTo>)。
接下来通过消息编码器工厂创建的消息编码器对消息进行编码,并将得到的字节数据通过创建的HttpWebRequest对象发送出去。对于得到的HttpWebResponse,则通过消息编码器进行解码以生成回复消息。回复消息最终通过客户端消息格式化器进行反序列化,得到的对象映射为方法返回值和输出/引用参数返回。
1: public class ServiceChannelProxy<TChannel>: RealProxy
2: {
3: //其他成员
4: public override IMessage Invoke(IMessage msg)
5: {
6: IMethodCallMessage methodCall = (IMethodCallMessage)msg;
7:
8: //得到操作名称
9: object[] attributes = methodCall.MethodBase.GetCustomAttributes(typeof(OperationContractAttribute), true);
10: OperationContractAttribute attribute = (OperationContractAttribute)attributes[0];
11: string operationName = string.IsNullOrEmpty(attribute.Name) ? methodCall.MethodName : attribute.Name;
12:
13: //序列化请求消息
14: Message requestMessage = this.MessageFormatters[operationName].SerializeRequest(this.MessageVersion, methodCall.InArgs);
15:
16: //添加必要的WS-Address报头
17: EndpointAddress address = new EndpointAddress(this.Address);
18: requestMessage.Headers.MessageId = new UniqueId(Guid.NewGuid());
19: requestMessage.Headers.ReplyTo = new EndpointAddress("http://www.w3.org/2005/08/addressing/anonymous");
20: address.ApplyTo(requestMessage);
21:
22: //对请求消息进行编码,并将编码生成的字节发送通过HttpWebRequest向服务端发送
23: HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(this.Address);
24: webRequest.Method = "Post";
25: webRequest.KeepAlive = true;
26: webRequest.ContentType = "application/soap+xml; charset=utf-8";
27: ArraySegment<byte> bytes = this.MessageEncoderFactory.Encoder.WriteMessage(requestMessage, int.MaxValue, BufferManager.CreateBufferManager(long.MaxValue, int.MaxValue));
28: webRequest.ContentLength = bytes.Array.Length;
29: webRequest.GetRequestStream().Write(bytes.Array, 0, bytes.Array.Length);
30: webRequest.GetRequestStream().Close();
31: WebResponse webResponse = webRequest.GetResponse();
32:
33: //对HttpResponse进行解码生成回复消息.
34: Message responseMessage = this.MessageEncoderFactory.Encoder.ReadMessage(webResponse.GetResponseStream(), int.MaxValue);
35:
36: //回复消息进行反列化生成相应的对象,并映射为方法调用的返回值或者ref/out参数
37: object[] allArgs = (object[])Array.CreateInstance(typeof(object),methodCall.ArgCount);
38: Array.Copy(methodCall.Args, allArgs, methodCall.ArgCount);
39: object[] refOutParameters = new object[GetRefOutParameterCount(methodCall.MethodBase)];
40: object returnValue = this.MessageFormatters[operationName].DeserializeReply(responseMessage, refOutParameters);
41: MapRefOutParameter(methodCall.MethodBase, allArgs, refOutParameters);
42:
43: //通过ReturnMessage的形式将返回值和ref/out参数返回
44: return new ReturnMessage(returnValue, allArgs, allArgs.Length, methodCall.LogicalCallContext, methodCall);
45: }
46:
47: private int GetRefOutParameterCount(MethodBase method)
48: {
49: int count = 0;
50: foreach (ParameterInfo parameter in method.GetParameters())
51: {
52: if (parameter.IsOut || parameter.ParameterType.IsByRef)
53: {
54: count++;
55: }
56: }
57: return count;
58: }
59:
60: private void MapRefOutParameter(MethodBase method, object[] allArgs,object[] refOutArgs)
61: {
62: List<int> refOutParamPositionsList = new List<int>();
63: foreach (ParameterInfo parameter in method.GetParameters())
64: {
65: if (parameter.IsOut || parameter.ParameterType.IsByRef)
66: {
67: refOutParamPositionsList.Add(parameter.Position);
68: }
69: }
70: int[] refOutParamPositionArray = refOutParamPositionsList.ToArray();
71: for (int i = 0; i < refOutArgs.Length; i++)
72: {
73: allArgs[refOutParamPositionArray[i]] = refOutArgs[i];
74: }
75: }
76: }
WCF的服务代理对象是通过ChannelFactory<TChannel>创建的,我们来创建如下一个与之对应的ServiceProxyFactory<TChannel>类,泛型参数依然表示契约接口类型。CreateChannel方法中通过表示服务地址的Uri,契约接口类型和默认消息版本创建上述的真实代理ServiceChannelProxy<TChannel>对象,并返回其透明代理作为进行服务调用的代理对象。
1: public class ServiceProxyFactory<TChannel>
2: {
3: public Uri Address { get; private set; }
4:
5: public ServiceProxyFactory(Uri address)
6: {
7: this.Address = address;
8: }
9: public TChannel CreateChannel()
10: {
11: MessageEncoderFactory encoderFactory = ComponentBuilder.GetMessageEncoderFactory(MessageVersion.Default, Encoding.UTF8);
12: ServiceChannelProxy<TChannel> proxy = new ServiceChannelProxy<TChannel>(this.Address, MessageVersion.Default, encoderFactory);
13: ContractDescription contract = ContractDescription.GetContract(typeof(TChannel));
14: foreach (OperationDescription operation in contract.Operations)
15: {
16: IClientMessageFormatter messageFormatter = (IClientMessageFormatter)ComponentBuilder.GetFormatter(operation, true);
17: proxy.MessageFormatters.Add(operation.Name, messageFormatter);
18: }
19: return (TChannel)proxy.GetTransparentProxy();
20: }
21: }
现在我们创建一个服务寄宿在我们自定义的迷你版本的WCF中。依然采用我们熟悉的计算服务,下面是分别定义的Service.Interface和Service项目中的契约接口定义和服务类型定义。
1: //契约接口
2: [ServiceContract(Namespace = "http://www.artech.com/")]
3: public interface ICalculator
4: {
5: [OperationContract]
6: double Add(double x, double y);
7: [OperationContract]
8: double Subtract(double x, double y);
9: [OperationContract]
10: double Multiply(double x, double y);
11: [OperationContract]
12: double Divide(double x, double y);
13: }
14:
15: //服务类型
16: public class CalculatorService: ICalculator
17: {
18: public double Add(double x, double y)
19: {
20: return x + y;
21: }
22: public double Subtract(double x, double y)
23: {
24: return x - y;
25: }
26: public double Multiply(double x, double y)
27: {
28: return x * y;
29: }
30: public double Divide(double x, double y)
31: {
32: return x / y;
33: }
34: }
然后我们为Web项目Service中添加一个Global.asax文件,并通过如下的定义让Web应用启动的时候注册寄宿的服务类型CalculatorService和地址(calculatorservice)之间的映射关系。然后在IIS中创建一个Web应用(比如起名为WcfServices)并将物理路径映射为Service项目的根目录。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: RouteTable.Routes.Register<CalculatorService>("calculatorservice");
6: }
7: }
由于最终处理服务调用请求的WcfHandler是通过WcfHttpModule进行映射的,所以我们需要将WcfHttpModule类型配置在Service项目的Web.config中。
1: <configuration>
2: <system.webServer>
3: <modules>
4: <add name="WcfHttpModule" type="Artech.WcfServices.Service.WcfHttpModule, Artech.WcfServices.Service"/>
5: </modules>
6: </system.webServer>
7: </configuration>
在客户端我们只需要按照如下的方式通过指定正确的调用地址(Web应用地址+在Global.asax文件中添加的路由映射的地址)创建ServiceProxyFactory<TChannel>对象,并用它来创建用于尽心服务调用的代理对象即可。
1: Uri address = new Uri("http://localhost/WcfServices/CalculatorService");
2: ServiceProxyFactory<ICalculator> factory = new ServiceProxyFactory<ICalculator>(address);
3: ICalculator proxy = factory.CreateChannel();
4: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));
5: Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));
6: Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));
7: Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));
上面的代码执行之后,就像你真正调用WCF服务一样,同样可以得到如下的运算结果。
1: x + y = 3 when x = 1 and y = 2
2: x - y = -1 when x = 1 and y = 2
3: x * y = 2 when x = 1 and y = 2
4: x / y = 0.5 when x = 1 and y = 2