可管理服务的契约模型






4.98/5 (70投票s)
本文描述了用于创建和管理虚拟(可管理)服务的契约的存储库工具的设计和实现。
目录
特点
- 契约模型的元数据存储库
- 端点、绑定、契约、操作、消息、架构、WSDL 的契约模型
- 手动创建和编辑存储库中的元数据
- 从端点导入契约(WSDL、Mex)
- 从程序集导入契约
- 将元数据拖放到存储库
- 将操作、消息等的元数据组合成虚拟契约
- 创建虚拟端点
- 编译架构、消息、操作和契约
- 导出(生成)端点的元数据
- 用于发现元数据(WS-Transfer Get 等)的存储库服务,由 Windows NT 服务托管
- 内置客户端测试器,由 svcutil 程序驱动
- 导出-导入回路测试
- 内置 MMC 框架
- 用于存储的 SQL 架构
- 使用 LINQ-SQL、MMC 3.0 和 .NetFX 3.5 技术实现
引言与概念
最新的 Microsoft .NetFX 3.5 版本发布了一个基于 Windows Communication Foundation (WCF) 和 Windows Workflow foundation (WF) 技术集成的模型。该模型称为 WorkflowService
,其能力基于投影服务连接性和业务活动。在当前的 .NetFx 3.5 中,WorkflowService
投影器基于 WCF 和 WF 元数据,这些元数据通过三个不同的资源进行描述,例如配置文件(system.serviceModel
部分组)、XOML 和规则文件。此外,此元数据还可以通过 XSLT、WSDL 等其他资源进行扩展。我强烈推荐阅读我最近的文章 VirtualService for ESB,其中提供了有关此主题(从服务方角度)的更多信息。
从架构角度来看,由元数据驱动的服务能够将业务逻辑模型映射到物理模型并进行管理。我们可以在 Microsoft Road Map 中看到这种策略,该策略首次在 PDC 2003 上提出,并在即将到来的 PDC 2008 上发布有关 OSLO 项目的更多详细信息。您还可以访问以下 链接,以及 Douglas Purdy(Oslo 产品单元经理)的文章 What is Oslo?。
该策略将元数据封装在实现层之外,并将 AppDomains 托管在一个称为存储库的集中位置。这个知识库可以在设计时创建和建模,然后根据部署架构映射到物理目标。换句话说,.NetFX 3.5 等当前技术已在运行时托管层与 WCF 和 WF 模型集成。即将发布的 .NetFX 版本侧重于元数据的集成。由一个资源(XAML)表示的结构良好且集成的元数据模型将简化建模工具和运行时引导的设计和实现。基于 PDC 2008 会议(OSLO/WCF/WF 主题),我们可以看到 WorkflowService
将在完全可管理的服务中发挥关键作用。
从企业的角度来看,存储库的大小可以轻松增长为一个庞大的知识库,如果没有正确的工具,将很难管理数千个端点、绑定、服务、工作流、XSLT 等资源的业务模型。由元数据驱动的可管理服务是建模、仿真、部署和运行时流程的巨大挑战,当然,对于设计者和开发人员来说,这是一种质量不同的思维方式(声明式编程)。本文展示了这个挑战的一部分,例如可管理服务的契约模型的存储库。请注意,我们仅限于当前技术,如 .NetFX 3.5。
可管理服务和契约模型是什么意思?
好吧,让我们先简要介绍一下“传统”Web 服务,然后过渡到代表完全可管理服务的 VirtualService
。
基本上,如果服务的连接性、行为、业务工作流和部署由元数据描述并位于企业服务存储库中,那么该服务就可以被视为在业务模型中完全可管理。
不可管理的服务
下图显示了 Web 服务与其消费客户端之间的典型连接。
Web 服务是用为特定托管、传输、绑定和消息交换模式硬编码的模型实现的。此外,业务逻辑和活动也在设计时硬编码。此模型具有非常有限的公共元数据,主要由配置文件等本地存储库处理。Web 服务配置元数据的任何更改都将强制回收托管进程。使用消息管道中的 SoapExtension
,我们可以自定义编码、消息契约等,并发布一些元数据以动态管理会话。请注意,扩展代表特定 Web 服务的私有本地功能。
此模型的优点是其简单性(只需提供端口的 URL 地址)以及基于 WSDL 规范(一个用于消费服务的著名契约 - MXL 格式文档)在不同平台之间的互操作性。Web 服务具有由 WSDL 文档资源表示的内置元数据导出器。基于此资源,客户端可以在设计时或动态地在运行时创建代理。这是逻辑连接模型中松散耦合连接的一个很棒的功能。
最后,Web 服务被视为不可管理的业务服务。因此,我们需要另一种通信方式来轻松且可管理地将逻辑连接映射到物理连接。在 WCF 中可以非常轻松地完成此操作。下图显示了一个 WCF 服务而不是 Web 服务。
WCF 模型在连接模型方面有质量上的改变,其中每个层都可以声明式地描述并存储在元数据资源(如配置文件)中。业务逻辑和活动从托管环境中完全封装,基于 ServiceContract
模型。服务托管对 AppDomain 和传输通道是完全透明的。当然,WCF 还支持导出元数据,而不仅仅是用于 HTTP/HTTPS 传输。
看起来 WCF 模型是可管理服务的一个很好的候选者,不是吗?我可以说是接近了。缺失的部分是背后的服务编排。换句话说,服务在设计时仍然是硬编码的,其可管理性基于私有功能。这是个坏消息;好消息是,我们最近发布了 .NetFX 3.5,其中将两个模型集成到一个范式中,称为 WorkflowService
。
可管理服务
下图显示了一个基于 WorkflowService
模型的可管理服务。WCF 功能由工作流模型扩展,并集成到由配置文件、XOML 和规则资源中的元数据驱动的通用模型中。
使用 WorkflowService
将业务逻辑模型映射到物理模型,使我们能够协调连接性和业务活动编排。您可以看到,所有资源都位于服务域中。当前版本仅支持从文件系统读取资源,如 .config 文件以及 XOML/规则文件。因此,配置文件中的任何更改都将强制回收主机进程。关于在隔离的业务域中托管服务的一个解决方案可以在我最近的文章:VirtualService for ESB 中找到。我们可以基于此解决方案将元数据集中到存储库存储中。
下图显示了我们服务修改的下一步
如上图所示,服务的元数据存储在数据库中,有一个通用位置用于其建模和管理。此功能在上图中看起来很不错且可行,但今天,使用当前技术,我们需要在 .NetFX 3.5 版本的基础上构建另一个层(基础设施),用于在特定的 AppDomains 中托管服务、元数据引导、管理托管服务的服务、存储库模型、工具等。
WorkflowService
可以在 Connector
的位置连接到企业服务总线,负责契约中介、服务可访问性等;换句话说,用于服务集成;请看下图
好的,继续,我们还有一个关于可管理服务的要点 - 未类型化消息。
未类型化消息
为了实现服务虚拟化,当然也为了增加其可管理性,服务契约应该被虚拟化。虚拟服务可以以完全透明的方式处理传入的类型化消息,就像类型化契约一样,通过未类型化的操作契约。同样,在相反的方向,当服务与另一个服务通信时,虚拟服务可以驱动类型化的操作契约生成出站的未类型化消息。这是一个很棒的集成功能,虚拟服务可以完全控制消息流、交换模式和消息中介。
这是真的。由元数据驱动的 VirtualService
(可管理 WorkflowService
)可以投影在设计时创建的 **虚拟契约**,该契约基于现有和/或手动导入的契约、操作、消息和数据契约。下图显示了这种组合
在上面的示例中,*Contract A* 代表虚拟契约,是 *Contracts 1 - N* 组合过程的结果,其中特定的操作已导入并组合到一个新的虚拟契约中。组合过程可以使用 XSLT 技术(这是元数据中的一个附加资源)来中介数据契约。
下图显示了具有 XOML 组合过程的 VirtualService 的示例,其中未类型化消息被操作分支分支到单独的活动以进行消息中介。在此示例中,服务中有五个分支,前三个操作用于业务活动,其他操作用于服务的内部功能,如错误处理和发布元数据。请注意,这两个分支应包含在任何虚拟服务中。
在虚拟服务中处理不支持的操作通常是通过抛出特定异常并返回一个故障消息(通过未类型化契约)来完成的。发布元数据(WSDL 或 Mex 端点)等其他功能对于虚拟服务来说更具挑战性。请注意,服务中有一个基于未类型化消息契约的虚拟(通用)契约。以下代码片段显示了虚拟契约的一个示例
[ServiceContract(Namespace = "urn:rkiss.esb/2008/08")]
public interface IGenericContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message message);
}
像上面的虚拟契约不会生成描述复合契约操作和/或数据的元数据。一种解决此挑战的方法是在设计时(建模)创建此元数据并将其导出到虚拟服务,作为另一个资源。在这种情况下,内置的元数据交换服务将被禁用(默认配置),并由一个操作分支(参见上图)替换,以使用 WS-Transfer Get 操作进行模拟。此活动将简单地返回 WSDL 资源给调用者。
正如您所见,在此概念中,我们将服务虚拟化的挑战推到了设计时,而引导基础设施并不关心该资源是由谁以及如何生成并存储在存储库中的。我们可以使用市场上的多种工具(如 Altova 等)来生成此元数据,并通过契约模型手动组合以供运行时投影。另一种方法是创建一个由 MMC 托管的轻量级工具。我将在后面更详细地介绍这个 MMC 工具。
下图显示了由元数据驱动的可管理服务的概念
存储库是此概念的核心组件,其中存储了我们模型(通信、工作流、中介等)的元数据。基本上,有以下主要活动
- 通过工具组合元数据,其中工具可以导入和创建契约、操作、架构、绑定、WSDL、XSLT、端点等。请注意,契约可以从现有端点或程序集中导入。
- 虚拟服务(引导)拉取元数据。
- 从知名端点进行元数据交换,基于资源主题。例如,调用此端点:
net.pipe:///repository/mex?order
,我们可以像从其他端点一样以完全透明的方式获取主题order
的元数据交换资源。请注意,工具还可以导入由建模创建的自己的元数据。
这个模型是如何工作的?
好吧,我可以用以下基本场景来解释,其中我假设工具已经为虚拟服务创建了元数据,并且我们在主机环境中有一个活动的服务。假设某个客户端想要执行预订书籍的业务工作流。客户端不需要了解此业务的连接性,它需要知道谁知道它(就像人工智能一样)。因此,客户端调用此知识库的知名端点来获取它(发现阶段)。基于导入的元数据交换资源,客户端有足够的信息来创建业务端点的代理通道。代理可以根据特定需求(如生命周期过期、错误等)从知识库(存储库)缓存和回收。
上面描述的概念可以在下图进行抽象
正如您所见,有两种模型,第一个是关于契约,任何组件(如服务或客户端)都可以识别它。这是一个逻辑连接模型,描述了 ESB 上的“谁、如何、什么”。请注意,本文重点介绍此模型作为工具的第一部分。
第二个模型是 **应用程序模型**。此模型描述了服务的行为、业务工作流、活动、AppDomain 中的托管、消息和服务中介等。这是服务虚拟化中包含在其业务建模中的另一个挑战。
模型通过 ServiceEndpoint
元数据逻辑连接,这是一个 ESB 上的唯一资源。ServiceEndpoint
由契约模型导出,用于特定的地址、绑定和契约(ABC)。将 ServiceEndpoint
分配给应用程序模型,我们可以得到虚拟服务的通用模型。
好的,最后,我们得到了 ESB 抽象的图示,其中契约模型代表服务总线,逻辑上集中在存储库中,并在物理上通过特定托管计算机中的虚拟服务引导程序分散。
存储库存储通过*连接器服务*插入到 ESB,并提供一个公共端点以根据主题获取元数据资源。此端点代表存储库管理的所有虚拟服务。客户端应在第一次业务调用之前调用此端点,以动态创建代理。当然,虚拟服务还支持元数据交换功能,但在此情况下,客户端完全负责管理此物理业务端点(例如,路由、中介等),就像 ESB 外部的另一个端点一样。
如我早先提到的,存储库服务代表用于发现元数据的企业端点。下图显示了这个场景
正如您所见,元数据服务消费者还可以是*Visual Studio 2008*、控制台实用程序(如*svcutil.exe*)或任何客户端。这是集中管理服务的一个很棒的功能,其中契约、操作、数据等可以在*契约模型*中统一并重用。另一个特性是*模型优先*的概念,其中契约模型在服务背后的业务流程之前创建。虚拟服务可以在开发周期的每个阶段帮助以透明的方式模拟业务流程。
摘要
下图显示了存储库在分布式系统中的策略。在存储库中,业务模型在逻辑上集中用于建模目的,并在物理上分散用于运行时处理。换句话说,通过存储库(数据库)的建模工具可以声明式地编写服务、其连接性、消息中介、业务处理等。
好的,就这些了,让我们开始深入研究契约模型和 MMC 工具的设计和实现。
设计与实现
如我上面提到的,可管理服务由集中在存储库中的元数据(如连接性、业务活动等)驱动。这个逻辑上集中的模型必须准备好由业务 AppDomains 进行引导,并在企业网络上进行物理部署。此解决方案的挑战在于创建逻辑模型,包括其建模、仿真和管理。这个过程从开发环境到生产环境需要很多阶段。运行在虚拟服务器上的 VirtualServices 支持每个开发周期的业务仿真和模拟的建模过程。在此垂直模型的底部是*契约模型*,用于通过*端点*资源逻辑描述连接性。
下图显示了*契约模型*图的一个示例,其中 Endpoint
对象代表了服务互操作性的抽象
契约模型根据架构、消息、操作、契约和端点资源组织成数据层次结构。通过此模型声明的端点可以通过名称将其 XML 格式的文本存储在 WSDL 资源中以供稍后发布。可以通过存储库服务按*名称*查询此 WSDL 资源,并进行主题、版本和契约的子查询。
在存储库存储中创建契约模型架构并加载 WorkflowService
支持的标准绑定元数据,我们就准备好将逻辑连接映射到物理连接了。此存储库的知识库可以通过导入云网络中的元数据或手动使用 MMC 工具来学习。下图显示了这个场景
基于导入的元数据(如架构和/或契约),我们可以增加连接性模型的知识。将操作、消息等组合成新的契约,我们可以虚拟化我们的模型连接性。此外,我们还可以在应用程序行为的基础上动态管理元数据。注意,本文的实现存在一些限制(例如,将只有一个契约分配给端点)在契约模型和 MMC 工具中。
上图显示了由应用程序模型和契约模型等元数据模型管理的虚拟服务的位置。在上图的顶部显示了物理端点之间的直接连接。在它们之间注入一个逻辑点,我们可以使用不同的消息交换模式(如请求/响应、单向等)以组合的方式管理业务契约。通过使用未类型化契约可以实现此功能。当然,在虚拟服务中使用类型化契约没有限制,只要合适。例如,REST/WS-Eventing 连接到企业存储。
要运行上述场景,存储库必须包含业务工作流、契约和托管环境的知识库。所有这些信息都作为关系资源存储在模型中。请注意,企业业务的理智模型可以位于这些模型的顶部。
存储库中的所有资源都由文本和/或 XML 文本格式的元素表示。模型中没有二进制序列化的 CLR 对象;因此,我们的模型(知识库)中必须有足够的信息来提供运行时支持,以创建 CLR 类型、实例、AppDomains、业务活动等。这是将元数据映射到运行时 CLR 对象的一个巨大挑战。使用 System.ServiceModel.Description 和 System.Xml.Schema 命名空间中的“主力”类(如 WsdlExporter
、WsdlImport
和 XmlSchemaSet
)以及 LINQ 技术将简化契约模型的实现。
发布 ServiceEndpoint
下图显示了发布元数据的基本过程
导出元数据的设计理念是从由文本资源表示的元数据创建 System.ServiceModel.Description.ServiceEndpoint
类的 CLR 实例。如上图所示,ABC 服务端点需要一个地址,使用 EndpointAddress
类并通过传递 Uri
地址可以轻松创建。挑战从绑定类型开始,其中 Binding
类的实例是基于 XML 格式资源生成的。
契约模型已将 NetFX 3.5 支持的所有标准绑定预加载到存储库中,用户可以使用嵌入的 XmlNotepad 2007 添加其他绑定(截至此时,我想感谢这个伟大的 CodePlex 项目,允许我将其程序集嵌入到 MMC 框架中)。
好的,回到上图,绑定描述。
数据绑定。
将 CLR 对象与数据进行映射的实现封装在位于单独文件中的两个辅助类中。下图显示了它们的类图
第一个类 ConfigHelper
已实现,用于将 XML 格式的资源反序列化到配置节,例如(在本例中)BindingsSection
。此反序列化过程使我们获得 BindingSection
,就像基于配置文件一样。
以下代码片段显示了一个用于从 XML 文本反序列化配置节的通用方法
public static T DeserializeSection<T>(string config) where T : class
{
T cfgSection = Activator.CreateInstance<T>();
byte[] buffer = new ASCIIEncoding().GetBytes(config);
XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
xmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment;
using (MemoryStream ms = new MemoryStream(buffer))
{
using (XmlReader reader = XmlReader.Create(ms, xmlReaderSettings))
{
try
{
Type cfgType = typeof(ConfigurationSection);
MethodInfo mi = cfgType.GetMethod("DeserializeSection",
BindingFlags.Instance | BindingFlags.NonPublic);
mi.Invoke(cfgSection, new object[] { reader });
}
catch (Exception ex)
{
throw new Exception( ...);
}
}
}
return cfgSection;
}
拥有由元数据创建的 BindingsSection
对象是遍历标准 .NetFx 3.5 绑定或自定义绑定的一个步骤。最后,我们得到了 ServiceEndpoint
的绑定类实例。下图显示了辅助方法的代码片段
public static Binding CreateBinding(
string bindingName, string bindingNamespace, string xmlBinding)
{
xmlBinding = string.Format("<bindings>{0}</bindings>", xmlBinding);
BindingsSection bindingSection =
DeserializeSection<BindingsSection>(xmlBinding);
Binding binding = CreateEndpointBinding(bindingName, bindingSection);
if (!string.IsNullOrEmpty(bindingNamespace))
binding.Namespace = bindingNamespace;
return binding;
}
接下来,ServiceEndpoint
的第三个要求是契约描述。这是实现的关键,其中由关系数据资源描述的元数据(如契约、操作、消息、头、数据类型、架构等)被映射到 ContractDescription
类型。
ContractDescription
将元数据映射到 ContractDescription
类型的过程从为 MessageDescription
和 MessageHeaderDescription
对象定义 DataContract
类型开始。我们需要一个架构来创建 MessageBody
或 HeaderBody
类型。因此,架构是我们契约模型层次结构的瓶颈。架构可以在线导入,从第三方工具(例如 Altova)拖放,或者使用内置的 XmlNotepad2007
用户控件进行交互式创建。存储在存储库和 Notepad 中的架构可以随时通过单击 MMC 操作项进行预编译。
如我之前提到的,契约模型支持导入契约的组合,共享它们的架构、消息、头和操作。导入的架构必须由 XmlSchemaSet
容器正确收集,以避免重复等。契约组合的每个步骤都可以在完成存储库之前进行预编译。
为了进行此元数据映射,创建了 WsdlHelper
静态类以简化实现 - 请参见上方的类图。
有许多有用的方法用于消息传递元数据并将此逻辑封装在用户界面之外。下图是导出服务终结点的代码片段
private void buttonExport_Click(object sender, EventArgs e)
{
// ...
StringBuilder sbLog = new StringBuilder();
LocalRepositoryDataContext repository =
((LocalRepositorySnapIn)this.view.SnapIn).Repository;
#region PART 1: Generate ContractDescription (CLR Types)
var contractTypes = WsdlHelper.GenerateContractDescriptions(
repository, selectedEndpoint.contractId, selectedOperations, sbLog);
ContractDescription contractDescription =
contractTypes.FirstOrDefault(t => t.Name == selectedContract.Name);
#endregion
#region PART 2: Generate ServiceEndpoints
Collection<ServiceEndpoint> endpoints = new Collection<ServiceEndpoint>()
{
new ServiceEndpoint(contractDescription, binding, endpoint)
{ Name=this.selectedEndpoint.name},
};
#endregion
#region PART 3: Generate wsdl
MetadataSet metadataDocs =
WsdlHelper.GenerateMetadata(endpoints, policyVersion);
StringBuilder sb = new StringBuilder();
metadataDocs.WriteTo(XmlWriter.Create(sb));
#endregion
// show result
xmlNotepadPanelControl.XmlNotepadForm.LoadXmlDocument(sb.ToString());
// ...
}
上面的实现使用 WsdlHelper
类看起来非常直接。作为此助手的示例,以下是为操作创建架构集的代码片段
public static XmlSchemaSet CreateOperationXmlSchemaSet(
LocalRepositoryDataContext repository,
Guid? inputMessageId,
Guid? outputMessageId,
XmlSchemaSet set)
{
if (set == null)
{
set = new XmlSchemaSet();
set.ValidationEventHandler += delegate(object sender, ValidationEventArgs args)
{
throw args.Exception;
};
}
var im = repository.Messages.FirstOrDefault(m => m.id == inputMessageId);
if (im != null)
{
var om = repository.Messages.FirstOrDefault(m => m.id == outputMessageId);
if (im.schemaId == null || im.Schema.schema1 == null)
throw new Exception("Missing schema for input message ...");
// message schema
if (set.Schemas().Count == 0 || set.Schemas().Cast<XmlSchema><xmlschema />().
FirstOrDefault(s => s.TargetNamespace == im.Schema.@namespace &&
s.SourceUri == im.Schema.sourceUrl) == null)
set.Add(null, XmlReader.Create(
new StringReader(im.Schema.schema1.ToString()))).SourceUri=
im.Schema.sourceUrl;
if (im.headers != null)
CreateXmlSchemaSetForHeaders(repository, im.headers.ToString(), set);
if (om != null && om.schemaId != null)
{
if (set.Schemas().Count == 0 || set.Schemas().Cast<XmlSchema><xmlschema />().
FirstOrDefault(s => s.TargetNamespace == om.Schema.@namespace &&
s.SourceUri == om.Schema.sourceUrl) == null)
{
set.Add(null, XmlReader.Create(
new StringReader(om.Schema.schema1.ToString()
))).SourceUri=om.Schema.sourceUrl;
}
if (om.headers != null)
CreateXmlSchemaSetForHeaders(repository, om.headers.ToString(), set);
}
}
// imported schemas
ImportSchemas(repository, set);
return set;
}
在处理上述方法时,XmlSchemaSet
将从存储库(由输入/输出消息 ID 定义)累积契约的所有操作架构。架构查询基于 TargetNamespace
和 SourceUrl
,以避免架构重复。一旦我们拥有了契约级别上的完整架构集,我们就可以编译它。这是可能中断我们映射过程的一个步骤。发生这种情况时,将弹出错误消息并显示错误描述。请注意,必须解决问题才能继续下一步。这就是为什么我们有一个操作(用于元数据的每个级别)来预编译元数据,以消除将损坏的元数据资源存储到存储库中的问题。
WsdlExport
类的另一个有趣特性是创建由架构描述的类型的内存程序集。请注意,在此文章版本中,程序集加载到默认的 AppDoman 中。为了将来卸载,它应该加载到单独的 AppDomain 中。
以上是描述内容,让我们继续描述允许我们创建服务终结点元数据的工具。
esbLocalRepository 工具
esbLocalRepository 是一个轻量级工具,用于创建和管理契约模型的元数据。该工具基于 MMC 3.0 框架实现,并使用 LINQ-SQL 技术来处理 SQL 数据库中的资源。
UI 布局设计基于三个面板。左面板用于元数据类型资源,如架构、消息、操作、契约和终结点/WSDL。右面板是所选资源的“操作”面板。例如,操作资源将具有以下操作:所有操作、添加新操作、编译操作、删除、刷新。中央面板用于与元数据资源进行交互式工作。关系资源仅用于读取。用户控件是 DataGridView
导向的,允许我们分页和过滤存储库中的特定资源。
下图是“在存储库中添加新操作”的屏幕截图
用户控件层级基于关系资源垂直增长。例如,架构资源位于此层级的底部,而相反的终结点资源位于顶部。为了显示更友好的用户面板布局,双击行可将客户端面板最小化为三行。相反,通过单击 TabPage,面板将最大化以显示更多行。
对于 XML 格式的资源,该工具嵌入了 XmlNotepad 2007 程序集,允许我们覆盖其公共 XmlNotepad.FormMain
类以满足我们的目的。下图显示了用于*添加绑定*的用户控件,其中 customBinding
可以被拖放或交互式创建
当谈到帮助我构建此工具的第三方用户控件时,还有一个控件。它就是 PopupControl
。感谢这个 CodeProject 程序集;我使用这个弹出控件在组合框上以用户友好的方式选择特定资源(如绑定、架构等)。
Microsoft Management Console 3.0 (MMC 3.0) 是 Windows 环境的一个很棒的托管框架。与 2.0 版本相比 - 请参阅我 2003 年写的旧文章 Remoting Managment Console,用于管理远程对象及其托管,3.0 版本进行了质量改进,易于开发和扩展。当然,对于下一个版本存在一些挑战,例如,拥有一个带有事件驱动架构的发布/订阅通知系统,包括广播场景。无论如何,感谢这个很棒的 MMC 3.0 样板代码,它提高了我的工作效率。
好的,让我们做一个“Hello World”示例来展示该工具的功能。此示例显示了基于从 WS-Eventing 和 Amazon 导入的元数据创建复合(虚拟)契约。
从 URL 导入契约
请遵循以下步骤
- 在左侧面板中选择契约。
- 在右侧面板中选择“导入契约”操作。
- 插入用于导入契约的 URL。例如:http://schemas.xmlsoap.org/ws/2004/08/eventing/eventing.wsdl
- 单击“获取”按钮。请注意,此连接性的绑定是标准的,但可以根据绑定选择进行更改(此版本不支持)。
您应该在中央面板中看到契约、操作和消息/头。
- 选择要导入存储库的契约,例如,
SubscriptionManager
。 - 为选定的契约选择操作。例如,
GetStatusOp
。 - 单击“导入”按钮以处理此选定契约的架构、消息/头、操作的导入。
- 单击“取消”按钮以清理用户控件。
- 键入新 URL,例如:http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl。
- 选择一个契约(只有一个)。
- 选择操作,例如:
CartAdd
、CartClear
、CartCreate
、CartGet
和CartModify
- 单击“导入”按钮。导入元数据后,“存在”列将被标记。
现在,我们应该在存储库中有两个契约。每个资源都可以在其控件上单独检查。更改后务必刷新控件。资源验证通过“操作编译”进行。任何错误都将弹出一个模态对话框并显示错误详细信息。
添加新契约(复合)
复合(或虚拟)契约由工具基于存储在存储库中的元数据进行管理。过程非常简单,例如填充契约描述属性和选择操作。在我们的上述示例之后,单击“编译”按钮后应该会显示以下屏幕截图
如果元数据已编译,“完成”按钮将启用;否则,我们必须解决错误弹出对话框中描述的问题并重复编译过程。
好的,到目前为止一切顺利。我们有了名为 VirtualContract
的新契约。此契约已从两个不同的外部元数据(WS-Eventing 和 Amazon)组合而成。
现在,我们可以将此契约分配给终结点并导出其元数据。以下步骤显示了这个场景。
导出终结点
导出终结点是该工具的最终资源。它将创建元数据并将其存储在存储库中以供将来引用和发布。如我早先提到的,通用契约(action="*") 不会在运行时发布元数据。动态为任何契约这样做会很困难。
回到我们的工具,选择“新建终结点”控件和我们包含在其操作中的演化契约,我们可以看到以下屏幕
“导出”和(当然)“保存”按钮被禁用。缺少地址和绑定值。让我们输入一些唯一的终结点地址,然后单击绑定组合框。弹出控件将显示存储库中注册的所有绑定。下图显示了此操作
之后,“导出”按钮将被启用,并准备好处理此终结点的导出。当编译器成功后,元数据将显示在“元数据预览”选项卡中,并且“保存”按钮将启用以将其存储在存储库中。
以下屏幕截图部分显示了这个场景。您可以在此控件中看到更多标签页;例如,“生成的契约类型”将显示所有类型和服务契约的源代码。请注意,本篇文章未实现“策略”选项卡。
现在,我们可以将终结点元数据存储在存储库中,并使用唯一的键。键是 Name
和 Topic
属性。通过单击“保存”按钮,WSDL 资源将被存储在存储库中,并在“元数据测试”选项卡上获得以下反馈屏幕。
如果我们安装并打开了存储库服务(托管在 Windows NT 服务中,名称为 LocalRepositoryService),我们可以通过单击“运行”按钮来运行 svcutil.exe 测试。
请注意,可以为 svcutil.exe 添加其他可选命令行参数,或者您只需键入 /? 获取更多帮助。
关于序列化的另一件事。默认序列化器使用 DataContractSerializer
。在某些情况下,我们需要使用 XmlSerializer
,这就是为什么我们有一个组合框选择。编译过程将自动选择最佳选项,方法是选择*自动序列化器*选项。为了处理契约操作上的 XML,可以选中*XML 格式*复选框。
以下代码片段显示了选择序列化器类型的逻辑。请注意,XmlSerializerImporter
类是在解决方案中实现的
XsdDataContractImporter importer = new XsdDataContractImporter()
{
Options = new ImportOptions()
{ GenerateSerializable = true,
EnableDataBinding = false,
GenerateInternal = false,
ImportXmlType = false
}
};
if (serializerOption != null &&
serializerOption.Serializer == SerializerTypes.DataContractSerializer)
{
// explicitly DataContractSerializer
importer.Options.ImportXmlType = true;
importer.Import(set);
code = importer.CodeCompileUnit;
}
else if (serializerOption != null &&
serializerOption.Serializer == SerializerTypes.XmlSerializer)
{
// explicitly XmlSerializerImport
XmlSerializerImport importer2 = new XmlSerializerImport();
importer2.Import(set);
code = importer2.CodeCompileUnit;
}
else if (importer.CanImport(set)) // auto case
{
// auto serializer, from DataContract to Xml
importer.Import(set);
code = importer.CodeCompileUnit;
}
else
{
XmlSerializerImport importer2 = new XmlSerializerImport();
importer2.Import(set);
code = importer2.CodeCompileUnit;
}
修改导入的契约
导入的契约可以通过添加更多操作或更改其属性来修改。下图显示了“修改契约”操作,其中导入的契约 Test2 版本 1.0.0.0 通过从另一个导入的契约添加一个 CartAdd
操作来进行修改
编译这个新版本并单击“完成”按钮后,契约 Test2 版本 1.0.0.1 将被存储在存储库中。我们可以为该契约导出终结点并将其存储以供客户端或虚拟服务(引导过程)将来发现。
由元数据驱动的虚拟服务将此契约投影到工作流服务中,如下图所示
这是一个简化的 XOML 虚拟服务,用于描述四个操作分支。第一个分支用于处理我们的附加操作 CartAdd
,它不是物理 Test2 契约的一部分。第二个分支基本上是将原始契约路由到物理终结点,下一个分支用于通过 WS-Transfer 发布元数据,最后一个分支用于处理不支持的操作。
基本上,上面的例子展示了在真实物理终结点之前的服务中介。我们通过在虚拟服务中基于元数据扩展了一个真实的不可管理服务,以实现额外的业务。请注意,客户端看不到这个真实终结点,实际上客户端并不关心它的物理位置和存在。
再举一个例子,展示如何将新消息添加到存储库。
添加新消息
如您所知,导入的契约将导入架构、消息和操作。这是从导入的元数据中提取所有资源并放入存储库的隐式过程。推荐使用此功能,这样我们可以从程序集中导入契约。契约可以在 Visual Studio 的契约程序集中创建,然后导入到存储库中。
但是,该工具具有通过*添加新消息*操作显式创建新消息(正文和头)的功能。下图显示了这个操作
为了创建新消息,必须选择架构和特定类型(请参见上图的消息正文和头)。在此实现中,无法根据部分键入消息正文。在所有情况下,我们需要一个消息契约,如请求/响应。该元素由架构类型选择。对于头,除了从架构导入之外,还有一些灵活性可以手动键入头。一旦准备就绪,就可以通过单击“完成”按钮编译消息,该消息会将元数据存储在存储库中以供将来组合。
摘要
上述部分描述了*esbLocalRepository*的一些功能。该工具让你对如何创建虚拟契约有了一些概述。这个契约是存储在存储库中的操作、消息、架构、类型等资源的组合结果。因此,存储库也可以称为逻辑连接性和业务处理的知识库。存储库必须通过现有契约隐式地学习,或者显式地以契约优先的方式学习。
下图显示了契约模型在可管理服务中的位置
正如您所见,契约模型后面是 XOML 驱动的应用程序模型。这是将虚拟契约投影到业务模型中的地方,包括服务和消息中介,例如更改消息交换模式等。
以上是描述内容,让我们描述一些存储库元数据的用法。
用法
契约模型的主要目标是以模型优先的方式为虚拟服务创建元数据。如我早先提到的,具有通用契约(Action="*")的服务不发布元数据;因此,我们需要提前提供给服务。契约模型的最佳用途是与应用程序模型一起用于虚拟服务。不幸的是,这不在本文的讨论范围内。
然而,我们可以使用 Visual Studio 2008 作为客户端和服务器端微型自托管通用服务来证明这个概念。以下代码片段显示了它的契约
[ServiceContract(Namespace = "urn:rkiss.esb/2008/04")]
public interface IGenericContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message message);
}
[ServiceContract(Namespace = "urn:rkiss.esb/2008/04")]
public interface IGenericOneWayContract
{
[OperationContract(Action = "*", IsOneWay=true)]
void ProcessMessage(Message message);
}
该服务只有一个业务逻辑:将完整消息转储到控制台。
客户端
存储库中表示的契约模型可以通过一个知名服务终结点进行消费。在本文中,该终结点已在配置文件中配置为 net.pipe:///repository/mex?,并带有一个简单的 name
元数据查询。
让我们创建一个使用 WCF 的控制台应用程序客户端。此客户端需要消费一个服务,因此必须创建代理。VS2008 具有内置的*添加服务引用*功能,可以以透明的方式创建代理。
以下屏幕截图显示了从服务终结点导入元数的实用对话框
客户端方面没有什么特别的,开发人员知道一个可以获取元数据vi 的服务终结点,VS2008 将源代码和所有类型嵌入到项目中,以便通过代理消费服务。请注意,代理拥有足够的信息用于服务和客户端之间的消息交换模式,包括实际的目标终结点地址。
一旦我们在源代码中有了代理,业务层就可以像使用 AppDomain 中的任何其他对象一样,以完全透明的方式通过此代理开始消费服务。
客户端方面就这些了。为了运行我们的客户端,我们需要启动目标服务。在此示例中,我们有一个空服务来显示接收到的 SOAP 消息。请注意,该服务非常简单,并且仅适用于单向操作,没有响应消息返回给调用者。
以下屏幕截图显示了从 WS-Eventing 操作 RenewOp
的客户端调用的示例。
安装
LocalRepositoryConsole 解决方案是基于三个层构建的,包含以下五个项目
解决方案的核心是 LocalRepositoryConsole 项目,其中包含 MMC 工具对契约模型的实现。该解决方案使用 Visual Studio 2008/SP1 - 目标框架 3.5/SP1 版本开发,并使用了几种技术。有一个第三方文件夹用于存放程序集副本,如 XmlNotepad 2007 和 PopupControl。该解决方案(用于测试目的)使用 MS svcutil.exe 程序,该程序位于标准安装路径中。如果需要,请修改路径以匹配您的设置。
由于解决方案需要 SQL、MMC 等的安装过程,以下步骤将帮助您在您的计算机上部署解决方案。
- 下载并解压缩包含的源文件到您的文件夹。
- 在 SQLEXPRESS 中创建一个本地数据库
- 将外部工具 InstallUtil.exe 添加到您的 VS2008 中,用于安装/卸载程序集。以下是命令和参数
- 使用 VS2008 打开解决方案。
- 在服务器资源管理器中,添加一个到 LocalRepository 数据库的数据库连接。
- 编译解决方案。
- 在 LocalRepositoryConsole 中,打开文件 CreateLocalRepository.sql 并单击“执行 SQL”。此脚本将创建我们存储库的所有表和架构。
- 打开文件 LoadingLocalRepository.sql 并执行脚本。此操作将处理并将元数据预加载到存储库中,例如,标准绑定。
- 将 LocalRepositoryWindowsService 设置为启动项目。现在,当您单击(外部的)installutil 时,我们可以安装这个 Windows NT 服务。请注意,服务必须手动启动,因此打开计算机管理服务,找到名为 LocalRepositoryService 的服务,然后单击“启动”。
- 选择 LocalRepositoryConsole 并设置为启动项目。转到外部工具,然后单击 installutil。
- 好的,现在是 MMC 时间了。转到 Vista 开始菜单并键入 mmc。您应该会在 MMC 3.0 中看到一个空的控制台根目录。
- 选择文件/添加或删除管理单元。
- 添加 esbLocalRepository 管理单元。
- 现在,契约模型工具已准备好使用。关闭之前请务必保存它。
- 您可以了解您的存储库并使用架构、消息等。在此评估期之后,可以使用脚本 ResetLocalRepository 来清除存储库。
connectionString = "Data Source=localhost\sqlexpress;
Initial Catalog=LocalRepository;Integrated Security=True"
C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe
$(BinDir)$(TargetName)$(TargetExt)
请注意,如我早先提到的,此实现不是生产版本。它是用于投影虚拟服务契约模型概念的初始版本。许多功能缺失,例如删除受限资源、改进同一服务内的架构合并、比较架构、策略、复合终结点等。
结论
逻辑模型连接性基于契约模型元数据,该元数据是服务终结点描述。从建模的角度来看,此模型在逻辑上集中在存储库中,并在物理上分散在主机进程中。当前的硬件技术使我们能够在物理托管系统的来宾计算机上以虚拟方式部署我们的应用程序。例如,我们可以创建前端、中间层和后端数据库的虚拟服务器集群。所有这些虚拟服务器都可以托管在一台具有强大硬件资源的物理服务器上。正如您所见,这些服务器的安装也可以是建模的一部分。业务流程在逻辑上集中,并通过契约模型在物理上分散。本文描述了契约模型,它在建模挑战中只占很小一部分。下一个挑战是创建描述 VirtualService
托管、激活、中介和业务处理(使用 XAML 激活的 WorkflowService
)的应用程序模型。
我们可以在 Microsoft PDC 2008 会议上听到更多关于建模策略的细节,届时将介绍 Oslo 项目及其技术。到时见。