如何:使用 WCF 终结点交换排队消息

即使在通信时 Windows Communication Foundation (WCF) 服务不可用,队列也能确保客户端和该服务之间的可靠消息传递。 下面的过程演示了如何在实现 WCF 服务时使用标准排队绑定确保客户端和服务间的持久性通信。

本部分介绍如何将 NetMsmqBinding 用于 WCF 客户端和 WCF 服务之间的排队通信。

在 WCF 服务中使用队列

  1. 使用用 ServiceContractAttribute 标记的接口定义服务协定。 用 OperationContractAttribute 来标记属于服务协定一部分的接口中的操作,并将它们指定为单向操作(因为不返回对该方法的任何响应)。 下面的代码提供了示例服务协定及其操作定义。

    [ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
    public interface IOrderProcessor
    {
        [OperationContract(IsOneWay = true)]
        void SubmitPurchaseOrder(PurchaseOrder po);
    }
    
    <ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Interface IOrderProcessor
        <OperationContract(IsOneWay:=True)> _
        Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder)
    End Interface
    
  2. 当服务协定传递用户定义的类型时,您必须为这些类型定义数据协定。 下面的代码演示了两个数据协定,PurchaseOrderPurchaseOrderLineItem。 这两种类型定义了发送给服务的数据。 (请注意,定义该数据协定的类也定义了大量方法。这些方法不被视为数据协定的一部分。只有那些使用 DataMemberAttribute 属性声明的成员才是数据协定的一部分。)

    [DataContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public class PurchaseOrder
    {
        static readonly string[] OrderStates = { "Pending", "Processed", "Shipped" };
        static Random statusIndexer = new Random(137);
    
        [DataMember]
        public string PONumber;
    
        [DataMember]
        public string CustomerId;
    
        [DataMember]
        public PurchaseOrderLineItem[] orderLineItems;
    
        public float TotalCost
        {
            get
            {
                float totalCost = 0;
                foreach (PurchaseOrderLineItem lineItem in orderLineItems)
                    totalCost += lineItem.TotalCost;
                return totalCost;
            }
        }
    
        public string Status
        {
            get
            {
                return OrderStates[statusIndexer.Next(3)];
            }
        }
    
        public override string ToString()
        {
            System.Text.StringBuilder strbuf = new System.Text.StringBuilder("Purchase Order: " + PONumber + "\n");
            strbuf.Append("\tCustomer: " + CustomerId + "\n");
            strbuf.Append("\tOrderDetails\n");
    
            foreach (PurchaseOrderLineItem lineItem in orderLineItems)
            {
                strbuf.Append("\t\t" + lineItem.ToString());
            }
    
            strbuf.Append("\tTotal cost of this order: $" + TotalCost + "\n");
            strbuf.Append("\tOrder status: " + Status + "\n");
            return strbuf.ToString();
        }
    }
    
    <DataContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Class PurchaseOrder
        Private Shared ReadOnly OrderStates() As String = {"Pending", "Processed", "Shipped"}
        Private Shared statusIndexer As New Random(137)
    
        <DataMember> _
        Public PONumber As String
    
        <DataMember> _
        Public CustomerId As String
    
        <DataMember> _
        Public orderLineItems() As PurchaseOrderLineItem
    
        Public ReadOnly Property TotalCost() As Single
            Get
                Dim totalCost_Renamed As Single = 0
                For Each lineItem In orderLineItems
                    totalCost_Renamed += lineItem.TotalCost
                Next lineItem
                Return totalCost_Renamed
            End Get
        End Property
    
        Public ReadOnly Property Status() As String
            Get
                Return OrderStates(statusIndexer.Next(3))
            End Get
        End Property
    
        Public Overrides Function ToString() As String
            Dim strbuf As New System.Text.StringBuilder("Purchase Order: " & PONumber & Constants.vbLf)
            strbuf.Append(Constants.vbTab & "Customer: " & CustomerId & Constants.vbLf)
            strbuf.Append(Constants.vbTab & "OrderDetails" & Constants.vbLf)
    
            For Each lineItem In orderLineItems
                strbuf.Append(Constants.vbTab + Constants.vbTab + lineItem.ToString())
            Next lineItem
    
            strbuf.Append(Constants.vbTab & "Total cost of this order: $" & TotalCost + Constants.vbLf)
            strbuf.Append(Constants.vbTab & "Order status: " & Status + Constants.vbLf)
            Return strbuf.ToString()
        End Function
    End Class
    
  3. 实现在类中接口中定义的服务协定的方法。

    public class OrderProcessorService : IOrderProcessor
    {
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {
            Orders.Add(po);
            Console.WriteLine("Processing {0} ", po);
        }
    }
    
    Public Class OrderProcessorService
        Implements IOrderProcessor
        <OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
        Public Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder) Implements IOrderProcessor.SubmitPurchaseOrder
            Orders.Add(po)
            Console.WriteLine("Processing {0} ", po)
        End Sub
    End Class
    

    请注意放置在 OperationBehaviorAttribute 方法上的 SubmitPurchaseOrder。 它指定了必须在某个事务内调用此操作,且当方法完成后,该事务会自动完成。

  4. 使用 System.Messaging 创建一个事务性队列。 可以选择使用 Microsoft 消息队列 (MSMQ) Microsoft 管理控制台 (MMC) 来创建队列。 如果这样,将确保创建事务性队列。

    // Create the transacted MSMQ queue if necessary.
    if (!MessageQueue.Exists(queueName))
        MessageQueue.Create(queueName, true);
    
    ' Create the transacted MSMQ queue if necessary.
    If (Not MessageQueue.Exists(queueName)) Then
        MessageQueue.Create(queueName, True)
    End If
    
  5. 在指定服务地址并使用标准 ServiceEndpoint 绑定的配置中定义 NetMsmqBinding。 若要详细了解如何使用 WCF 配置,请参阅配置 WCF 服务

  6. 使用 OrderProcessingServiceHost 服务创建一个主机,该主机从队列读取消息并处理这些消息。 打开服务主机使服务处于可用状态。 显示一条消息,告知用户按任何键以终止服务。 调用 ReadLine 等待按键,然后关闭服务。

    // Create a ServiceHost for the OrderProcessorService type.
    using (ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService)))
    {
        // Open the ServiceHost to create listeners and start listening for messages.
        serviceHost.Open();
    
        // The service can now be accessed.
        Console.WriteLine("The service is ready.");
        Console.WriteLine("Press <ENTER> to terminate service.");
        Console.WriteLine();
        Console.ReadLine();
    
        // Close the ServiceHostB to shutdown the service.
        serviceHost.Close();
    }
    
    ' Create a ServiceHost for the OrderProcessorService type.
    Using serviceHost As New ServiceHost(GetType(OrderProcessorService))
        ' Open the ServiceHost to create listeners and start listening for messages.
        serviceHost.Open()
    
        ' The service can now be accessed.
        Console.WriteLine("The service is ready.")
        Console.WriteLine("Press <ENTER> to terminate service.")
        Console.WriteLine()
        Console.ReadLine()
    
        ' Close the ServiceHostB to shutdown the service.
        serviceHost.Close()
    End Using
    

为排队服务创建客户端

  1. 下面的示例演示了如何运行主机应用程序并使用 Svcutil.exe 工具创建 WCF 客户端。

    svcutil http://localhost:8000/ServiceModelSamples/service  
    
  2. 在配置中定义 ServiceEndpoint,指定地址并使用标准 NetMsmqBinding 绑定,如下面的示例所示。

  3. 创建事务范围以写入到事务性队列,调用 SubmitPurchaseOrder 操作并关闭 WCF 客户端,如下面的示例中所示。

    //Create a transaction scope.
    using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
    {
        // Make a queued call to submit the purchase order.
        client.SubmitPurchaseOrder(po);
        // Complete the transaction.
        scope.Complete();
    }
    
    //Closing the client gracefully closes the connection and cleans up resources.
    client.Close();
    
    'Create a transaction scope.
    Using scope As New TransactionScope(TransactionScopeOption.Required)
        ' Make a queued call to submit the purchase order.
        client.SubmitPurchaseOrder(po)
        ' Complete the transaction.
        scope.Complete()
    End Using
    
    'Closing the client gracefully closes the connection and cleans up resources.
    client.Close()
    

示例

下面的示例演示了服务代码、主机应用程序、App.config 文件以及为此示例包含的客户端代码。

// This is the service code
//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.ServiceModel.Channels;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;
using System.Runtime.Serialization;
using System.Collections.Generic;

namespace Microsoft.ServiceModel.Samples
{
    // Define the purchase order line item.
    [DataContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public class PurchaseOrderLineItem
    {
        [DataMember]
        public string ProductId;

        [DataMember]
        public float UnitCost;

        [DataMember]
        public int Quantity;

        public override string ToString()
        {
            String displayString = "Order LineItem: " + Quantity + " of "  + ProductId + " @unit price: $" + UnitCost + "\n";
            return displayString;
        }

        public float TotalCost
        {
            get { return UnitCost * Quantity; }
        }
    }

    // Define the purchase order.
    [DataContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public class PurchaseOrder
    {
        static readonly string[] OrderStates = { "Pending", "Processed", "Shipped" };
        static Random statusIndexer = new Random(137);

        [DataMember]
        public string PONumber;

        [DataMember]
        public string CustomerId;

        [DataMember]
        public PurchaseOrderLineItem[] orderLineItems;

        public float TotalCost
        {
            get
            {
                float totalCost = 0;
                foreach (PurchaseOrderLineItem lineItem in orderLineItems)
                    totalCost += lineItem.TotalCost;
                return totalCost;
            }
        }

        public string Status
        {
            get
            {
                return OrderStates[statusIndexer.Next(3)];
            }
        }

        public override string ToString()
        {
            System.Text.StringBuilder strbuf = new System.Text.StringBuilder("Purchase Order: " + PONumber + "\n");
            strbuf.Append("\tCustomer: " + CustomerId + "\n");
            strbuf.Append("\tOrderDetails\n");

            foreach (PurchaseOrderLineItem lineItem in orderLineItems)
            {
                strbuf.Append("\t\t" + lineItem.ToString());
            }

            strbuf.Append("\tTotal cost of this order: $" + TotalCost + "\n");
            strbuf.Append("\tOrder status: " + Status + "\n");
            return strbuf.ToString();
        }
    }

    // Order Processing Logic
    // Can replace with transaction-aware resource such as SQL or transacted hashtable to hold the purchase orders.
    // This example uses a non-transactional resource.
    public class Orders
    {
        static Dictionary<string, PurchaseOrder> purchaseOrders = new Dictionary<string, PurchaseOrder>();

        public static void Add(PurchaseOrder po)
        {
            purchaseOrders.Add(po.PONumber, po);
        }

        public static string GetOrderStatus(string poNumber)
        {
            PurchaseOrder po;
            if (purchaseOrders.TryGetValue(poNumber, out po))
                return po.Status;
            else
                return null;
        }

        public static void DeleteOrder(string poNumber)
        {
            if(purchaseOrders[poNumber] != null)
                purchaseOrders.Remove(poNumber);
        }
    }

    // Define a service contract.
    [ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
    public interface IOrderProcessor
    {
        [OperationContract(IsOneWay = true)]
        void SubmitPurchaseOrder(PurchaseOrder po);
    }

    // Service class that implements the service contract.
    // Added code to write output to the console window.
    public class OrderProcessorService : IOrderProcessor
    {
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {
            Orders.Add(po);
            Console.WriteLine("Processing {0} ", po);
        }
    }
}
' This is the service code
'  Copyright (c) Microsoft Corporation.  All Rights Reserved.

Imports System.ServiceModel.Channels
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions
Imports System.Runtime.Serialization
Imports System.Collections.Generic

Namespace Microsoft.ServiceModel.Samples
    ' Define the purchase order line item.
    <DataContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Class PurchaseOrderLineItem
        <DataMember> _
        Public ProductId As String

        <DataMember> _
        Public UnitCost As Single

        <DataMember> _
        Public Quantity As Integer

        Public Overrides Function ToString() As String
            Dim displayString As String = "Order LineItem: " & Quantity & " of " & ProductId & " @unit price: $" & UnitCost + Constants.vbLf
            Return displayString
        End Function

        Public ReadOnly Property TotalCost() As Single
            Get
                Return UnitCost * Quantity
            End Get
        End Property
    End Class

    ' Define the purchase order.
    <DataContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Class PurchaseOrder
        Private Shared ReadOnly OrderStates() As String = {"Pending", "Processed", "Shipped"}
        Private Shared statusIndexer As New Random(137)

        <DataMember> _
        Public PONumber As String

        <DataMember> _
        Public CustomerId As String

        <DataMember> _
        Public orderLineItems() As PurchaseOrderLineItem

        Public ReadOnly Property TotalCost() As Single
            Get
                Dim totalCost_Renamed As Single = 0
                For Each lineItem In orderLineItems
                    totalCost_Renamed += lineItem.TotalCost
                Next lineItem
                Return totalCost_Renamed
            End Get
        End Property

        Public ReadOnly Property Status() As String
            Get
                Return OrderStates(statusIndexer.Next(3))
            End Get
        End Property

        Public Overrides Function ToString() As String
            Dim strbuf As New System.Text.StringBuilder("Purchase Order: " & PONumber & Constants.vbLf)
            strbuf.Append(Constants.vbTab & "Customer: " & CustomerId & Constants.vbLf)
            strbuf.Append(Constants.vbTab & "OrderDetails" & Constants.vbLf)

            For Each lineItem In orderLineItems
                strbuf.Append(Constants.vbTab + Constants.vbTab + lineItem.ToString())
            Next lineItem

            strbuf.Append(Constants.vbTab & "Total cost of this order: $" & TotalCost + Constants.vbLf)
            strbuf.Append(Constants.vbTab & "Order status: " & Status + Constants.vbLf)
            Return strbuf.ToString()
        End Function
    End Class

    ' Order Processing Logic
    ' Can replace with transaction-aware resource such as SQL or transacted hashtable to hold the purchase orders.
    ' This example uses a non-transactional resource.
    Public Class Orders
        Private Shared purchaseOrders As New Dictionary(Of String, PurchaseOrder)()

        Public Shared Sub Add(ByVal po As PurchaseOrder)
            purchaseOrders.Add(po.PONumber, po)
        End Sub

        Public Shared Function GetOrderStatus(ByVal poNumber As String) As String
            Dim po As PurchaseOrder = Nothing
            If purchaseOrders.TryGetValue(poNumber, po) Then
                Return po.Status
            Else
                Return Nothing
            End If
        End Function

        Public Shared Sub DeleteOrder(ByVal poNumber As String)
            If purchaseOrders(poNumber) IsNot Nothing Then
                purchaseOrders.Remove(poNumber)
            End If
        End Sub
    End Class

    ' Define a service contract. 
    <ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Interface IOrderProcessor
        <OperationContract(IsOneWay:=True)> _
        Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder)
    End Interface

    ' Service class that implements the service contract.
    ' Added code to write output to the console window.
    Public Class OrderProcessorService
        Implements IOrderProcessor
        <OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
        Public Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder) Implements IOrderProcessor.SubmitPurchaseOrder
            Orders.Add(po)
            Console.WriteLine("Processing {0} ", po)
        End Sub
    End Class
End Namespace
// This is the hosting application.

using System;
using System.ServiceModel.Channels;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;
using System.Runtime.Serialization;
using System.Collections.Generic;

namespace Microsoft.ServiceModel.Samples
{
    class hostApp
    {
        // Host the service within this EXE console application.
        public static void Main()
        {
            // Get MSMQ queue name from appsettings in configuration.
            string queueName = ConfigurationManager.AppSettings["queueName"];

            // Create the transacted MSMQ queue if necessary.
            if (!MessageQueue.Exists(queueName))
                MessageQueue.Create(queueName, true);

            // Create a ServiceHost for the OrderProcessorService type.
            using (ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService)))
            {
                // Open the ServiceHost to create listeners and start listening for messages.
                serviceHost.Open();

                // The service can now be accessed.
                Console.WriteLine("The service is ready.");
                Console.WriteLine("Press <ENTER> to terminate service.");
                Console.WriteLine();
                Console.ReadLine();

                // Close the ServiceHostB to shutdown the service.
                serviceHost.Close();
            }
        }
    }
}
' This is the hosting application.

Imports System.ServiceModel.Channels
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions
Imports System.Runtime.Serialization
Imports System.Collections.Generic

Namespace Microsoft.ServiceModel.Samples
    Friend Class hostApp
        ' Host the service within this EXE console application.
        Public Shared Sub Main()
            ' Get MSMQ queue name from appsettings in configuration.
            Dim queueName As String = ConfigurationManager.AppSettings("queueName")

            ' Create the transacted MSMQ queue if necessary.
            If (Not MessageQueue.Exists(queueName)) Then
                MessageQueue.Create(queueName, True)
            End If

            ' Create a ServiceHost for the OrderProcessorService type.
            Using serviceHost As New ServiceHost(GetType(OrderProcessorService))
                ' Open the ServiceHost to create listeners and start listening for messages.
                serviceHost.Open()

                ' The service can now be accessed.
                Console.WriteLine("The service is ready.")
                Console.WriteLine("Press <ENTER> to terminate service.")
                Console.WriteLine()
                Console.ReadLine()

                ' Close the ServiceHostB to shutdown the service.
                serviceHost.Close()
            End Using
        End Sub
    End Class
End Namespace
// This is the client code.
//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;

namespace Microsoft.ServiceModel.Samples
{
    //The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.

    //Client implementation code.
    class Client
    {
        static void Main()
        {
            // Create a client.
            OrderProcessorClient client = new OrderProcessorClient();

            // Create the purchase order.
            PurchaseOrder po = new PurchaseOrder();
            po.CustomerId = "somecustomer.com";
            po.PONumber = Guid.NewGuid().ToString();

            PurchaseOrderLineItem lineItem1 = new PurchaseOrderLineItem();
            lineItem1.ProductId = "Blue Widget";
            lineItem1.Quantity = 54;
            lineItem1.UnitCost = 29.99F;

            PurchaseOrderLineItem lineItem2 = new PurchaseOrderLineItem();
            lineItem2.ProductId = "Red Widget";
            lineItem2.Quantity = 890;
            lineItem2.UnitCost = 45.89F;

            po.orderLineItems = new PurchaseOrderLineItem[2];
            po.orderLineItems[0] = lineItem1;
            po.orderLineItems[1] = lineItem2;

            //Create a transaction scope.
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                // Make a queued call to submit the purchase order.
                client.SubmitPurchaseOrder(po);
                // Complete the transaction.
                scope.Complete();
            }

            //Closing the client gracefully closes the connection and cleans up resources.
            client.Close();

            Console.WriteLine();
            Console.WriteLine("Press <ENTER> to terminate client.");
            Console.ReadLine();
        }
    }
}
' This is the client code.
'  Copyright (c) Microsoft Corporation.  All Rights Reserved.

Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions

Namespace Microsoft.ServiceModel.Samples
    'The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.

    'Client implementation code.
    Friend Class Client
        Shared Sub Main()
            ' Create a client.
            Dim client As New OrderProcessorClient()

            ' Create the purchase order.
            Dim po As New PurchaseOrder()
            po.CustomerId = "somecustomer.com"
            po.PONumber = Guid.NewGuid().ToString()

            Dim lineItem1 As New PurchaseOrderLineItem()
            lineItem1.ProductId = "Blue Widget"
            lineItem1.Quantity = 54
            lineItem1.UnitCost = 29.99F

            Dim lineItem2 As New PurchaseOrderLineItem()
            lineItem2.ProductId = "Red Widget"
            lineItem2.Quantity = 890
            lineItem2.UnitCost = 45.89F

            po.orderLineItems = New PurchaseOrderLineItem(1) {}
            po.orderLineItems(0) = lineItem1
            po.orderLineItems(1) = lineItem2

            'Create a transaction scope.
            Using scope As New TransactionScope(TransactionScopeOption.Required)
                ' Make a queued call to submit the purchase order.
                client.SubmitPurchaseOrder(po)
                ' Complete the transaction.
                scope.Complete()
            End Using

            'Closing the client gracefully closes the connection and cleans up resources.
            client.Close()

            Console.WriteLine()
            Console.WriteLine("Press <ENTER> to terminate client.")
            Console.ReadLine()
        End Sub
    End Class
End Namespace

另请参阅