首页 »博文摘选 » WCF技术剖析的 2十 7: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序) »正文
WCF技术剖析的 2十 7: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)
来源: 发布时间:星期六, 2009年12月26日 浏览:0次 评论:0
通过 ![](/icons/93644smhl.gif) 如何将 ![](/icons/93644yi.gif) 个服务发布成WSDL[编程篇] ![](/icons/93644smhr.gif) ![](/icons/93644de.gif) 介绍我们知道了如何可以通过编程或者配置 ![](/icons/93644de.gif) 方式将ServiceMetadataBehavior这样 ![](/icons/93644yi.gif) 个服务形式应用到相应 ![](/icons/93644de.gif) 服务上面 ![](/icons/93644dou.gif) 从而实现基于 HTTP-GET或者 WS-MEX![](/icons/93644de.gif) 元数据发布机制 ![](/icons/93644dou2.gif) 那么在WCF内部具体 ![](/icons/93644de.gif) 实现原理又是怎样 ![](/icons/93644de.gif) 呢?相信很多人对此都心存好奇 ![](/icons/93644dou.gif) 本篇文章 ![](/icons/93644de.gif) 内容将围绕着这个主题展开 、 从WCF分发体系谈起 如果读者想对WCF内部 元数据发布机制 实现原理有 个全面而深入 了解 必须对WCF服务端 分发体系有 个清晰 认识 在这里我们先对该分发体系作 个概括性 介绍 WCF整个分发体系在进行服务寄宿(Hosting)时被构建 该体系 基本结构基本上可以通过图1体现
图1 WCF服务端分发体系
当我们创建ServiceHost对象成功寄宿某个服务后 WCF会根据监听地址 区别为该ServiceHost对象创建 到多个ChannelDispatcher对象 每个ChannelDispatcher都拥有各自 ChannelListener 这些ChannelListener绑定到相应 监听地址监听来自外界 请求 对于每 个ChannelListener对象 有个自己具有 到多个Endpo Dispatcher对象和的匹配 每 个Endpo Dispatcher对应着某个终结点
而针对每 个Endpo Dispatcher 在其 化 时候会为的创建 个运行时 即DispatchRuntime DispatchRuntime拥有 系列处理请求、激活对象和执行思路方法等操作 运行时对象 在这里我们主要关注 个称为InstanceContextProvider 对象 InstanceContextProvider用于提供封装有相应服务例子 InstanceContext对象
2、基于WS-MEX模式下 元数据发布是如何实现 ? 现在我们再把话题移到元数据发布上来 先来谈谈基于WS-MEX协议 元数据发布方式 在这种元数据发布模式下 服务端通过MEX终结点发布元数据 客户端创建相应 MEX终结点获取元数据 这和 般意义上 服务 并没有本质 区别 你完全可以将元数据 获取当成是 个某个服务 而该服务就是提供元数据
如果我们通过编程或者配置 方式为某个服务添加了 个MEX终结点后 当服务被成功寄宿后 WCF会为的创建 个ChannelDispatcher 该ChannelDispatcher拥有 个用于监听元数据请求 ChannelListener 监听 地址及元数据发布 地址 基于该MEX终结点 Endpo Dispatcher对象也会被创建 并和该ChannelDispatcher关联在 起 在Endpo Dispatcher 化 时候 关联DispatchRuntime也随的被创建 和普通终结点关联 DispatchRuntime 样 基于MEX终结点 DispatchRuntime同样拥有相同 运行时对象集合 但是 由于并没有 个真正用于提供元数据 服务被寄宿 DispatchRuntime InstanceContextProvider(默认是PerSessionInstanceContextProvider)是获取不到包含有真正服务例子 InstanceContext对象![](/icons/93644de.gif)
那么 如果能够定制DispatchRuntime InstanceContextProvider 使它能够正常提供 个InstanceContext 而该InstanceContext包含真正能够提供元数据 服务例子 并且服务类类实现MEX终结点 契约接口IMetadataExchange 那么 切问题都迎刃而解 实际上 ServiceMetadataBehavior内部就是这么做![](/icons/93644de.gif) 而这个用于提供元数据 服务类型是定义在WCF内部![](/icons/93644de.gif) 个 ernal类型:WSMexImpl![](/icons/93644dou2.gif)
1: ernal WSMexImpl : IMetadataExchange
2: {
3: //其他成员
4: public IAsyncResult BeginGet(Message request, AsyncCallback callback, object state);
5: public Message EndGet(IAsyncResult result);
6: private MetadataSet GatherMetadata( dialect, ident ier);
7: public Message Get(Message request);
8: }
当ServiceMetadataBehavior ApplyDispatchBehavior思路方法被执行 时候 ServiceMetadataBehavior会创建WSMexImpl对象 据此创建InstanceContext对象 并将其作为MEX终结点DispatchRuntime SingletonInstanceContext 然后创建 个SingletonInstanceContextProvider作为该DispatchRuntime InstanceContextProvider 那么 MEX终结点 DispatchRuntime就能使用其InstanceContextProvider提供封装有WSMexImpl例子 InstanceContext了
上诉 这些内容虽然不算负责 但是要求读者对WCF 例子上下文机制有清晰 认识 对此不太熟悉 读者 可以参数 WCF技术剖析(卷1) 第9章 为了加深读者对基于WS-MEX元数据发布机制 理解 接下来我会作 个简单 例子演示
3、 例子演示:模拟ServiceMetadataBehavior实现基于WS-MEX元数据发布
接下来 我会完全基于ServiceMetadataBehavior 实现原理 即在上面介绍 原理 创建 个自定义服务行为用于基于WS-MEX 元数据发布 Source Code从这里下载 首先我们先来编写 些辅助性质 代码 由于在本例中我需要创建 些和DispatchRuntime相关 运行时对象 而且很多对象并没有被公开出来(很多是 ernal类型 比如SingletonInstanceContextProvider) 我需要通过反射 机制来创建它们 此外 我们需要为某些对象![](/icons/93644de.gif) 些私有或者内部属性赋值 同样需要利用反射 所以我写了下面两个辅助思路方法:
1: using ;
2: using .Globalization;
3: using .Reflection;
4: ServiceMetadataBehaviorSimulator
5: {
6: public Utility
7: {
8: public T CreateInstance<T>( typeQname, Type parameterTypes, object parameters) where T:![](/icons/93644class.gif)
9: {
10: Type type = Type.GetType(typeQname);
11: BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
12: ConstructorInfo constructorInfo = type.GetConstructor(bindingFlags, Type.DefaultBinder, parameterTypes, null);
13: Activator.CreateInstance(type, bindingFlags, Type.DefaultBinder, parameters, CultureInfo.InvariantCulture) as T;
14: }
15:
16: public void SetPropertyValue(object target, propertyName, object propertyValue)
17: {
18: BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance;
19: PropertyInfo propertyInfo = target.GetType .GetProperty(propertyName, bindingFlags);
20: propertyInfo.SetValue(target, propertyValue, null);
21: }
22: }
23: }
接下来 我仿照IMetadataExchange接口定义了如下 个接口:IMetadataProvisionService 为了简化 我省略了异步模式定义 Get操作(BeginGet/EndGet) Get操作 Action和ReplyAction同样基于WS-Transfer规范标准定义 通过ServiceContractAttribute特性将契约 Name和ConfigurationName设定成IMetadataProvisionService![](/icons/93644dou2.gif)
1: using .ServiceModel;
2: using .ServiceModel.Channels;
3: ServiceMetadataBehaviorSimulator
4: {
5: [ServiceContract(ConfigurationName = "IMetadataProvisionService", Name = "IMetadataProvisionService", Namespace = "http://schemas.microsoft.com/2006/04/mex")]
6: public erface IMetadataProvisionService
7: {
8: [OperationContract(Action = "http://schemas.xmlsoap.org/ws/2004/09/transfer/Get", ReplyAction = "http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse")]
9: Message Get(Message request);
10: }
11: }
由于Get操作返回 是封装有元数据(以MetadataSet 形式) 消息对象 为此我定义了如下 个以消息契约(Message Contract)形式定义 类型:MetadataMessage MetadataMessage通过MessageBodyMemberAttribute特性直接将类型为MetadataSet 属性定义成消息主体成员 并按照WS-MEX规范标准设置该成员 名称和命名空间![](/icons/93644dou2.gif)
1: using .ServiceModel;
2: using .ServiceModel.Description;
3: ServiceMetadataBehaviorSimulator
4: {
5: [MessageContract(IsWrapped = false)]
6: public MetadataMessage
7: {
8: public MetadataMessage(MetadataSet metadata)
9: {
10: this.Metadata = metadata;
11: }
12:
13: [MessageBodyMember(Name = "Metadata", Namespace = "http://schemas.xmlsoap.org/ws/2004/09/mex")]
14: public MetadataSet Metadata { get; ; }
15: }
16: }
接下来我们来创建真正用于提供元数据 服务类:MetadataProvisionService MetadataProvisionService实现了上面定义 服务契约接口IMetadataProvisionService 具有 个MetadataSet类型 属性成员Metadata 在Get思路方法中 通过Metadata属性表述 MetadataSet创建MetadataMessage对象 并将其转化成Message对象返回 最终返回 消息具有WS-Transfer规定 Action:http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse![](/icons/93644dou2.gif)
1: using ;
2: using .ServiceModel.Channels;
3: using .ServiceModel.Description;
4: ServiceMetadataBehaviorSimulator
5: {
6: public MetadataProvisionService : IMetadataProvisionService,
7: {
8: public MetadataSet Metadata
9: { get; private ; }
10:
11: public MetadataProvisionService(MetadataSet metadata)
12: {
13: (null metadata)
14: {
15: throw ArgumentNullException("metadata");
16: }
17: this.Metadata = metadata;
18: }
19:
20: public Message Get( .ServiceModel.Channels.Message request)
21: {
22: MetadataMessage message = MetadataMessage(this.Metadata);
23: TypedMessageConverter converter = TypedMessageConverter.Create(typeof(MetadataMessage), "http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse");
24: converter.ToMessage(message, request.Version);
25: }
26: }
27: }
最后我们就可以创建用于实现元数据发布 服务行为了 在这里使用了和ServiceMetadataBehavior相同 名字 并将其定义成特性 那么我们就可以直接通过特性 方式应用到服务类型上 所有 实现体现在ApplyDispatchBehavior思路方法中 该思路方法先后执行以下两组操作:
- 导出元数据:直接通过WsdlExporter将服务相关
所有终结点导出生成MetadataSet 需要注意 是 在进行终结点收集 时候 需要过滤到MEX终结点;元数据导出 所有操作实现在GetExportedMetadata思路方法中;
- 定制MEX终结点
DispatchRuntime:在ServiceHost Endpo Dispatcher列表中 筛选出基于MEX终结点 Endpo Dispatcher 然后定制它们 DispatchRuntime:创建SingletonInstanceContextProvider作为IntanceContextProvider;根据导出 MetadataSet创建MetadataProvisionService对象 并将其封装在InstanceContext中 而该InstanceContext直接设定为DispatchRuntime SingletonInstanceContext
1: using ;
2: using .Collections.ObjectModel;
3: using .Reflection;
4: using .ServiceModel;
5: using .ServiceModel.Channels;
6: using .ServiceModel.Description;
7: using .ServiceModel.Dispatcher;
8: using .Xml;
9: ServiceMetadataBehaviorSimulator
10: {
11: [AttributeUsage(AttributeTargets.Class)]
12: public ServiceMetadataBehaviorAttribute:Attribute, IServiceBehavior
13: {
14: private const MexContractName = "IMetadataProvisionService";
15: private const MexContractNamespace = "http://schemas.microsoft.com/2006/04/mex";
16: private const SingletonInstanceContextProviderType = " .ServiceModel.Dispatcher.SingletonInstanceContextProvider, .ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
17: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpo > endpo s, BindingParameterCollection bindingParameters){}
18:
19: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
20: {
21: MetadataSet metadata = GetExportedMetadata(serviceDescription);
22: CustomizeMexEndpo s(serviceDescription, serviceHostBase, metadata);
23: }
24:
25: private MetadataSet GetExportedMetadata(ServiceDescription serviceDescription)
26: {
27: Collection<ServiceEndpo > endpo s = Collection<ServiceEndpo > ;
28: foreach (var endpo in serviceDescription.Endpo s)
29: {
30: (endpo .Contract.ContractType typeof(IMetadataProvisionService))
31: {
32: continue;
33: }
34: ServiceEndpo Endpo = ServiceEndpo (endpo .Contract, endpo .Binding, endpo .Address);
35: Endpo .Name = endpo .Name;
36: foreach (var behavior in endpo .Behaviors)
37: {
38: Endpo .Behaviors.Add(behavior);
39: }
40: endpo s.Add( Endpo );
41: }
42: WsdlExporter exporter = WsdlExporter ;
43: XmlQual iedName wsdlServiceQName = XmlQual iedName(serviceDescription.Name, serviceDescription.Namespace);
44: exporter.ExportEndpo s(endpo s, wsdlServiceQName);
45: MetadataSet metadata = exporter.GetGeneratedMetadata ;
46: metadata;
47: }
48:
49: private void CustomizeMexEndpo s(ServiceDescription description, ServiceHostBase host,MetadataSet metadata)
50: {
51: foreach(ChannelDispatcher channelDispatcher in host.ChannelDispatchers)
52: {
53: foreach(Endpo Dispatcher endpo in channelDispatcher.Endpo s)
54: {
55: (endpo .ContractName MexContractName && endpo .ContractNamespace MexContractNamespace)
56: {
57: DispatchRuntime dispatchRuntime = endpo .DispatchRuntime;
58: dispatchRuntime.InstanceContextProvider = Utility.CreateInstance<IInstanceContextProvider>(SingletonInstanceContextProviderType, Type { typeof(DispatchRuntime) }, object { dispatchRuntime });
59: MetadataProvisionService serviceInstance = MetadataProvisionService(metadata);
60: dispatchRuntime.SingletonInstanceContext = InstanceContext(host, serviceInstance);
61: }
62: }
63: }
64: }
65:
66: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
67: }
68: }
以我们熟悉 计算服务 为例 我们将ServiceMetadataBehaviorAttribute直接应用CalculatorService上 下面是CalculatorService 定义 的所以让它实现我们定义 IMetadataProvisionService接口 是为了在进行服务寄宿是满足服务类型比如实现终结点契约接口 约束 如果直接使用WCF提供IMetadataExchange 由于其内部进行了相应 处理 服务类型和MEX终结点契约接口无关时允许![](/icons/93644de.gif) ![](/icons/93644dou2.gif)
1: using ;
2: using .ServiceModel.Channels;
3: using .ServiceModel.Description;
4: using Artech.Contracts;
5: using ServiceMetadataBehaviorSimulator;
6: Artech.Services
7: {
8: [ServiceMetadataBehavior ]
9: public CalculatorService : ICalculator, IMetadataProvisionService
10: {
11: public double Add(double x, double y)
12: {
13: x + y;
14: }
15:
16: public Message Get(Message request)
17: {
18: throw NotImplementedException ;
19: }
20: }
21: }
那么在进行服务寄宿 时候 我们就可以采用WCF如下 方式添加MEX终结点了 可以看到这和WCF本身支持 MEX终结点 配置 本上是 样![](/icons/93644de.gif) 唯 区别 是这里 契约是我们自定义IMetadataProvisionService 而不是IMetadataExchange![](/icons/93644dou2.gif)
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.Services.CalculatorService">
6: <endpo address="http://127.0.0.1:3721/calculatorservice" binding="ws2007HttpBinding" contract="Artech.Contracts.ICalculator" />
7: <endpo address="http://127.0.0.1:9999/calculatorservice/mex"
8: binding="mexHttpBinding" contract="IMetadataProvisionService" />
9: </service>
10: </services>
11: </system.serviceModel>
12: </configuration>
在客户端就可以采用和 般服务 完全 样 方式获取服务 元数据了 下面是客户端 配置 注意 这里配置 终结点并不是 Calculatorservice 终结点 而是为了获取元数据 MEX终结点 地址是服务端MEX终结点 地址 契约是IMetadataProvisionService 采用 绑定是标准 基于HTTP MEX绑定![](/icons/93644dou2.gif)
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <client>
5: <endpo address="http://127.0.0.1:9999/calculatorservice/mex"
6: binding="mexHttpBinding" contract="IMetadataProvisionService"
7: name="mex" />
8: </client>
9: </system.serviceModel>
10: </configuration>
下面是基于ChannelFactory<TChannel>创建服务代理 客户端代码 可以看到和 般 服务 并无 2致 获取 元数据最终被写入 个XML文件并被打开![](/icons/93644dou2.gif)
1: using .Diagnostics;
2: using .ServiceModel;
3: using .ServiceModel.Channels;
4: using .ServiceModel.Description;
5: using .Text;
6: using .Xml;
7: using ServiceMetadataBehaviorSimulator;
8: Artech.Client
9: {
10: Program
11: {
12: void Main(![](/icons/93644string.gif) args)
13: {
14: using (ChannelFactory<IMetadataProvisionService> channelFactory = ChannelFactory<IMetadataProvisionService>("mex"))
15: {
16: IMetadataProvisionService proxy = channelFactory.CreateChannel ;
17: Message request = Message.CreateMessage(MessageVersion.Default, "http://schemas.xmlsoap.org/ws/2004/09/transfer/Get");
18: Message reply = proxy.Get(request);
19: MetadataSet metadata = reply.GetBody<MetadataSet> ;
20: using (XmlWriter writer = XmlTextWriter("metadata.xml", Encoding.UTF8))
21: {
22: metadata.WriteTo(writer);
23: }
24: Process.Start("metadata.xml");
25: }
26: }
27: }
28: }
上面 应用如果正常执行 包含所有元数据信息 XML文件将会通过IE(假设使用IE作为开启XML文件 默认应用 )开启 图2是运行后 截图:
图2 获取 元数据在IE中 显示
下 篇中我们将采用同样 方式来模拟基于HTTP-GET 元数据发布时如何实现![](/icons/93644de.gif) ![](/icons/93644dou2.gif)
作者:Artech 出处:http://artech.cnblogs.com 本文版权归作者和博客园共有 欢迎转载 但未经作者同意必须保留此段声明 且在文章页面明显位置给出原文连接 否则保留追究法律责任 权利 Tag标签: WCF,Metadata,WS-MEX,HTTP-GET,ServiceMetadataBehavior
标签:
相关文章
读者评论
发表评论
|
|