且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总

更新时间:2021-11-20 06:48:15


概念汇总

  我希望从本章的这个小节你能对面向服务有个清晰的认识。接下来的一些章节,让我们看看这些概念根本上如何在WCF系统里工作的。在我们的例子里,我讲构建一个简单的接受客户订单的订单处理服务。为了保证概念的简洁明了,这里有2个消息参与者,如图2-3所示。
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总
这个示例代码的作用就是加强你对面向服务的认识和提供WCF的介绍,不会详细描述WCF的方方面面或者建立一个完整功能的订单处理系统。例子里的类型和机制会在这本书将会详细介绍。
契约
  显然地,面向服务系统开发首先应该是创建契约。为了例子简单,一个订单包含一个产品ID(product ID)、数量(quantity)和状态消息(status message)。有了这三个字段,一个订单可以使用下面的伪schema代码表示:
<Order> 
<ProdID>xs:integer</ProdID> 
<Qty>xs:integer</Qty> 
<Status>xs:string</Status> 
</Order>

  从我们消息自治和配置地址的讨论,我们知道消息需要更多的地址结构,如果我们想使用WS-Addressing。在我们的订单处理服务里,消息发送者和接收者统一使用遵守WS-Addressing规范的SOAP消息来限制消息的结构。有了这个规则,下面就是一个结构合理的消息例子:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http:// 
schemas.xmlsoap.org/ws/2004/08/addressing"> 
<s:Header> 
        <wsa:Action s:mustUnderstand="1">urn:SubmitOrder</wsa:Action> 
        <wsa:MessageID>4</wsa:MessageID> 
        <wsa:ReplyTo> 
            <wsa:Address> http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous 
            </wsa:Address> 
        </wsa:ReplyTo> 
        <wsa:To s:mustUnderstand="1">http://localhost:8000/Order</wsa:To> 
</s:Header> 
    <s:Body> 
        <Order> 
            <ProdID>6</ProdID> 
            <Qty>6</Qty> 
            <Status>order placed</Status> 
        </Order> 
</s:Body> 
</s:Envelope>
  在创建完描述我们的消息的schema之后,下一个步骤就是定义接受消息的终结点。为此,我们要先看一下WSDL的概念。你也许会想:“我没心情去处理原始的schema和WSDL。”那好,你不会孤单(MJ的一首歌)。WCF团队已经为我们在Microsoft .NET Framework提供了使用我们选择语言(本书里,使用C#)去表示契约的方法(schema和WSDL)。基本上,C#描述的契约都可以根据需要转化为机遇XSD和基于WSDL的契约。
  当使用C#表示契约的时候,我们可以选择定义一个类或者接口。下面是用C#接口定义一个契约的例子:
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// file: Contracts.cs 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel.Channels; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// define the contract for the service 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总[ServiceContract(Namespace = "http://wintellect.com/ProcessOrder")] 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总public interface IProcessOrder { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        [OperationContract(Action="urn:SubmitOrder")] 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        void SubmitOrder(Message order); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总}
  注意ServiceContractAttribute  OperationContractAttribute属性标记。我们将在第9章:契约(Contracts)里详细讨论这2个属性。现在这个接口与其它.NET Framework的接口不同,因为它们可以定制属性。另外也注意一下SubmitOrder方法的签名。这个方法的唯一参数类型是System.ServiceModel.Message。这个参数代表任何来自初始发送者和中介者的消息。这个消息类型非常有趣,并且优点复杂,我们将会在第五章:消息(Messages)里详细讨论。当前,假设初始发送者发送的消息可以被System.ServiceModel.Message表示。
  无论我们选择什么方式表达契约,它都应该在发送者和接收者程序进一步开发以前确定。实际上,接收者定义需要的消息结构契约,发送者通常尝试按照这个契约构建和发送消息。
  没什么能阻止发送者不按照消息接收者定义的契约来发送消息。因此,接收者第一个任务应该是验证接收到的消息是否符合契约。这个方法保证了接收者的数据结构不会被破坏。这些观点经常在分布式开发社区里讨论。
  这个契约可以被编译为一个程序集。一旦编译完成,程序集可以指派给发送者和接收者。这个程序集代表了发送者和接收者之间的契约。当契约还会变化确定的次数的时候,我们应该认为在共享以后它是不可变的。我们会在第9章讨论契约的版本问题。
  选择我们有了自己的契约,让我们建立一个接收者程序。第一个商业订单应该构建一个实现我们定义好的契约的接口。
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// File: Receiver.cs 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// Implement the interface defined in the contract assembly 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总public sealed class MyService : IProcessOrder { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 public void SubmitOrder(Message order) { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Do work here 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 } 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总}

  因为这是一个简单的例子,所以我们打算在Console里打印消息并且写到文件里。
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// File: Receiver.cs 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.Xml; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.IO; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel.Channels; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// Implement the interface defined in the contract assembly实现程序集里定义的契约接口 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总public sealed class MyService : IProcessOrder { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 public void SubmitOrder(Message order) { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Create a file name from the MessageID根据MessageID创建文件名 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        String fileName = "Order" + order.Headers.MessageId.ToString() + ".xml"
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Signal that a message has arrived提示消息到达 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Console.WriteLine("Message ID {0} received"
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            order.Headers.MessageId.ToString()); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // create an XmlDictionaryWriter to write to a file创建一个XmlDictionaryWriter去写文件 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter( 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            new FileStream(fileName, FileMode.Create)); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // write the message to a file写消息到文件里 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        order.WriteMessage(writer); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        writer.Close(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 } 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总}

  我们下一个任务就是让MyService类型去接受请求消息。为了接受消息:
·         MyService必须加载进AppDomain
·         MyService(或者另外一个类型)必须侦听内部消息。
·         适当的时候,比如创建一个此类型的实例,并且只要需要就要引用它(为了阻止垃圾收集器GC释放对象的内存)。
·         当一个消息到来时,它必须被分发给MyService实例,激活SubmitOrder方法。
  这些任务通常由宿主(Host)来执行。我们将在第10章里详细介绍宿主(Host),但是现在,假设我们的AppDomain托管在一个控制台程序里,管理和分发消息到MyService对象的类型是System.ServiceModel.ServiceHost。我们的控制台应用代码如下:
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// File: ReceiverHost.cs 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.Xml; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总internal static class ReceiverHost { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 public static void Main() { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Define the binding for the service定义服务绑定 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        WSHttpBinding binding = new WSHttpBinding(SecurityMode.None); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Use the text encoder 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        binding.MessageEncoding = WSMessageEncoding.Text; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Define the address for the service定义服务地址 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Uri addressURI = new Uri(@"http://localhost:4000/Order"); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总     // Instantiate a Service host using the MyService type使用MyService实例化服务宿主 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        ServiceHost svc = new ServiceHost(typeof(MyService)); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Add an endpoint to the service with the给服务增加一个终结点 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // contract, binding, and address 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        svc.AddServiceEndpoint(typeof(IProcessOrder), 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总                                                     binding, 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总                                                     addressURI); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总    
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Open the service host to start listening打开服务宿主开始侦听 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        svc.Open(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Console.WriteLine("The receiver is ready"); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Console.ReadLine(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        svc.Close(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 } 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总}

  在我们的控制台程序里,必须在托管之前设置服务的一些属性。正如你将在后续章节里看到的一样,每个服务包含一个地址、一个绑定和一个契约。这些机制常常被成为WCFABC。现在,假设如下:
·         一个地址描述了服务侦听请求消息的地方
·         一个绑定描述了服务如何侦听消息
·         一个契约描述了服务会将会接收什么样的消息
  在我们的例子里,我们将使用WSHttpBinding绑定去定义服务如何侦听请求消息。第8章我们会消息讨论绑定(Binding)。我们的服务也会使用Uri类型来定义服务侦听的地址。我们的服务会创建一个ServiceHost实例去托管MyServiceServiceHosts没有缺省的终结点,所以我们必须通过AddServiceEndpoint方法增加自己的终结点。这样,我们的控制台程序就准备开始在http://localhost:8000/Order地址上侦听请求消息。对实例Open法的调用启动了循环侦听。
  你也许想知道当一个消息到达at http://localhost:8000/Order时会发生什么。答案取决于到达终结点的消息类型。就此来说,我们来做个简单的例子。更高层次上,我们的消息发送者必须要知道下面三点:
·         服务在哪里(地址address
·         服务期望消息如何被传递(绑定binding
·         服务期望什么类型的消息(契约contract
假设这些大家都知道,下面就是一个合格的消息发送应用:
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总// File: Sender.cs 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.Text; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.Xml; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.Runtime.Serialization; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.IO; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总using System.ServiceModel.Channels; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总public static class Sender { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 public static void Main(){ 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Console.WriteLine("Press ENTER when the receiver is ready"); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Console.ReadLine(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // address of the receiving application接受程序的地址 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        EndpointAddress address = 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            new EndpointAddress(@"http://localhost:4000/Order"); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Define how we will communicate with the service定义一个我们通信的服务 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // In this case, use the WS-* compliant HTTP binding这个例子里,我们可以使用WS的HTTP绑定 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        WSHttpBinding binding = new WSHttpBinding(SecurityMode.None); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        binding.MessageEncoding = WSMessageEncoding.Text; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Create a channel创建通道 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        ChannelFactory<IProcessOrder> channel = 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            new ChannelFactory<IProcessOrder>(binding, address); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Use the channel factory to create a proxy使用通道工厂创建代理 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        IProcessOrder proxy = channel.CreateChannel(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Create some messages创建一些消息 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        Message msg = null
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        for (Int32 i = 0; i < 10; i++) { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            // Call our helper method to create the message调用我们的helper方法创建消息 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            // notice the use of the Action defined in注意使用在IProcessOrder契约里定义的Action 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            // the IProcessOrder contract 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            msg = GenerateMessage(i,i); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            // Give the message a MessageID SOAP headerSOAP消息头里加MessageID 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            UniqueId uniqueId = new UniqueId(i.ToString()); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            msg.Headers.MessageId = uniqueId; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            Console.WriteLine("Sending Message # {0}", uniqueId.ToString()); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            // Give the message an Action SOAP headerSOAP消息头里加Action 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            msg.Headers.Action = "urn:SubmitOrder"
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            // Send the message发送消息 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            proxy.SubmitOrder(msg); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        } 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 } 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 // method for creating a Message创建消息的方法 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 private static Message GenerateMessage(Int32 productID, Int32 qty) { 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        MemoryStream stream = new MemoryStream(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter( 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            stream, Encoding.UTF8, false); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        writer.WriteStartElement("Order"); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        writer.WriteElementString("ProdID", productID.ToString()); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        writer.WriteElementString("Qty", qty.ToString()); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        writer.WriteEndElement(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        writer.Flush(); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        stream.Position = 0; 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader( 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总            stream, XmlDictionaryReaderQuotas.Max); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        // Create the message with the Action and the body使用Action和body创建消息 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总        return Message.CreateMessage(MessageVersion.Soap12WSAddressing10, 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总                                                                 String.Empty, 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总                                                                 reader); 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总 } 
《WCF技术内幕》翻译12:第1部分_第2章_面向服务:概念汇总}
  不要被ChannelFactory类型吓到,我们将会在第4章里剖析这个类型。现在,注意for循环里的代码。循环体里代码会生成10个消息,并且每个消息会指定一个假的唯一的ID和一个Action(动作,这里专有名词,不做翻译)。
  这里,我们应该有2个可执行文件(ReceiverHost.exe and Sender.exe)表示最终接收者和初始发送者。如果我们运行2个控制台程序,等待接收者初始化完毕,在发送程序里按回车(ENTER)键,我们应该可以在接受程序看到下面的结果:
The receiver is ready 
Message ID 0 received 
Message ID 1 received 
Message ID 2 received 
Message ID 3 received 
Message ID 4 received 
Message ID 5 received 
Message ID 6 received 
Message ID 7 received 
Message ID 8 received 
Message ID 9 received

  恭喜!你已经使用WCF完成了一个面向服务的程序。记住服务把内部消息写进文件里。如果我们检查服务写过的文件,会看到如下:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"                    xmlns:a="http://www.w3.org/2005/08/addressing"> 
<s:Header> 
        <a:Action s:mustUnderstand="1">urn:SubmitOrder</a:Action> 
        <a:MessageID>1</a:MessageID> 
        <a:ReplyTo> 
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> 
        </a:ReplyTo> 
        <a:To s:mustUnderstand="1">http://localhost:4000/Order</a:To> 
</s:Header> 
<s:Body> 
        <Order> 
            <ProdID>1</ProdID> 
            <Qty>1</Qty> 
        </Order> 
</s:Body> 
</s:Envelope>
  消息头应该和我们在WS-Addressing规范里看到的一样看起来有些奇怪,并且它们的值应该与我们在消息发送应用里设置的属性很一样。事实上,System.ServiceModel.Message类型暴露了一个叫做Headers的属性,它是属于System.ServiceModel.MessageHeaders类型。这个MessageHeaders类型暴露的其它属性可以表示WS-Addressing的消息头。这里的想法就是我们可以使用WCF面向对象的编程模型去影响面向服务的SOAP消息。


 本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/318616,如需转载请自行联系原作者