Gruppieren von Nachrichten in der Warteschlange einer Sitzung
Windows Communication Foundation (WCF) bietet eine Sitzung, mit der Sie eine Reihe zusammengehöriger Nachrichten zur Verarbeitung durch eine einzige empfangende Anwendung zusammenfassen können. Die Nachrichten in einer Sitzung müssen Teil der gleichen Transaktion sein. Da alle Nachrichten Teil der gleichen Transaktion sind, wird die gesamte Sitzung zurückgesetzt, wenn eine Nachricht nicht verarbeitet werden kann. Sitzungen weisen ähnliche Verhaltensweisen bezüglich Warteschlangen für unzustellbare Nachrichten und Warteschlangen für potenziell schädliche Nachrichten auf. Die Time to Live (TTL)-Eigenschaft einer Bindung in der Warteschlange, die für Sitzungen konfiguriert wurde, wird auf die gesamte Sitzung angewendet. Wenn nur ein Teil der Nachrichten in der Sitzung vor Ablauf der TTL gesendet wird, wird die gesamte Sitzung in der Warteschlange für unzustellbare Nachrichten abgelegt. Analog wird ggf. die gesamte Sitzung in der Warteschlange für potenziell schädliche Nachrichten abgelegt, wenn Nachrichten in einer Sitzung nicht von der Anwendungswarteschlange an eine Anwendung gesendet werden können.
Beispiel für das Gruppieren von Nachrichten
Das Gruppieren von Nachrichten kann beispielsweise hilfreich sein, wenn Sie eine Anwendung für die Auftragsverarbeitung als WCF-Dienst implementieren. Angenommen, ein Client sendet einen Auftrag an diese Anwendung, die eine Anzahl von Elementen enthält. Der Client ruft für jedes Element den Dienst auf. Dadurch wird jeweils eine separate Nachricht gesendet. Es kann sein, dass Server A das erste Element empfängt und Server B das zweite. Immer, wenn ein Element hinzugefügt wird, muss der Server, der das Element verarbeitet, die entsprechende Reihenfolge ermitteln, und das Element muss hinzugefügt werden. Dieser Vorgang ist nicht sehr effizient. Dieses Problem tritt auch dann auf, wenn alle Anfragen nur von einem Server behandelt werden, da der Server alle Aufträge überwachen muss, die derzeit verarbeitet werden, und das neue Element einem Auftrag zugeordnet werden muss. Durch das Gruppieren aller Anforderungen für einen Auftrag wird die Implementierung einer solchen Anwendung stark vereinfacht. Alle Elemente für einen Auftrag werden von der Clientanwendung in einer Sitzung gesendet, sodass der Auftrag vom Dienst in einer Sitzung verarbeitet werden kann. \
Prozeduren
So richten Sie einen Dienstvertrag für Sitzungen ein
Definieren Sie einen Dienstvertrag, der eine Sitzung erfordert. Tun Sie dies mit dem Attribut ServiceContractAttribute, indem Sie Folgendes angeben:
SessionMode=SessionMode.Required
Markieren Sie die Vorgänge im Vertrag als unidirektional, da von diesen Methoden nichts zurückgegeben wird. Dies geschieht mit dem OperationContractAttribute-Attribut durch die Angabe von:
[OperationContract(IsOneWay = true)]
Implementieren Sie den Dienstvertrag, und geben Sie einen InstanceContextMode von InstanceContextMode.PerSession an. Dadurch wird der Dienst für jede Sitzung nur einmal instanziiert.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
Jeder Dienstvorgang erfordert eine Transaktion. Verwenden Sie das OperationBehaviorAttribute-Attribut für die Angabe. Der Vorgang, mit dem die Transaktion abgeschlossen wird, sollte auch TransactionAutoComplete auf
true
festlegen.[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
Konfigurieren Sie einen Endpunkt, der die vom System bereitgestellte
NetMsmqBinding
-Bindung verwendet.Erstellen Sie eine Transaktionswarteschlange mit System.Messaging. Sie können die Warteschlange auch mit Message Queuing (MSMQ) oder MMC erstellen. Erstellen Sie in diesem Fall eine Transaktionswarteschlange.
Erstellen Sie mit ServiceHost einen Host für den Dienst.
Öffnen Sie den Diensthost, um den Dienst verfügbar zu machen.
Schließen Sie den Diensthost.
So richten Sie einen Client ein
Erstellen Sie einen Transaktionsbereich zum Schreiben in die Transaktionswarteschlange.
Erstellen Sie den WCF-Client mit dem ServiceModel Metadata Utility Tool (Svcutil.exe).
Platzieren Sie den Auftrag.
Schließen Sie den WCF-Client.
Beispiel
Beschreibung
Im folgenden Beispiel wird der Code für den IProcessOrder
-Dienst sowie für einen Client bereitgestellt, der diesen Dienst verwendet. Es wird veranschaulicht, wie WCF die Gruppierungsfunktion mit Warteschlangensitzungen bereitstellt.
Code für den Dienst
// Service Code:
using System;
using System.ServiceModel.Channels;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;
using System.Text;
using System.Collections.Generic;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required)]
public interface IOrderTaker
{
[OperationContract(IsOneWay = true)]
void OpenPurchaseOrder(string customerId);
[OperationContract(IsOneWay = true)]
void AddProductLineItem(string productId, int quantity);
[OperationContract(IsOneWay = true)]
void EndPurchaseOrder();
}
// Define the Purchase Order Line Item
public class PurchaseOrderLineItem
{
static Random r = new Random(137);
string ProductId;
float UnitCost;
int Quantity;
public PurchaseOrderLineItem(string productId, int quantity)
{
this.ProductId = productId;
this.Quantity = quantity;
this.UnitCost = r.Next(10000);
}
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 Purchase Order
public class PurchaseOrder
{
string PONumber;
string CustomerId;
LinkedList<PurchaseOrderLineItem> orderLineItems = new LinkedList<PurchaseOrderLineItem>();
public PurchaseOrder(string customerId)
{
this.CustomerId = customerId;
this.PONumber = Guid.NewGuid().ToString();
}
public void AddProductLineItem(string productId, int quantity)
{
orderLineItems.AddLast(new PurchaseOrderLineItem(productId, quantity));
}
public float TotalCost
{
get
{
float totalCost = 0;
foreach (PurchaseOrderLineItem lineItem in orderLineItems)
totalCost += lineItem.TotalCost;
return totalCost;
}
}
public string Status
{
get
{
return "Pending";
}
}
public override string ToString()
{
StringBuilder strbuf = new 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();
}
}
// Service class which implements the service contract.
// Added code to write output to the console window
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class OrderTakerService : IOrderTaker
{
PurchaseOrder po;
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void OpenPurchaseOrder(string customerId)
{
Console.WriteLine("Creating purchase order");
po = new PurchaseOrder(customerId);
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void AddProductLineItem(string productId, int quantity)
{
po.AddProductLineItem(productId, quantity);
Console.WriteLine("Product " + productId + " quantity " + quantity + " added to purchase order");
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void EndPurchaseOrder()
{
Console.WriteLine("Purchase Order Completed");
Console.WriteLine();
Console.WriteLine(po.ToString());
}
// Host the service within this EXE console application.
public static void Main()
{
// Get MSMQ queue name from app settings in configuration
string queueName = ConfigurationManager.AppSettings["queueName"];
// Create the transacted MSMQ queue if necessary.
if (!MessageQueue.Exists(queueName))
MessageQueue.Create(queueName, true);
// Get the base address that is used to listen for WS-MetaDataExchange requests
string baseAddress = ConfigurationManager.AppSettings["baseAddress"];
// Create a ServiceHost for the OrderTakerService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(OrderTakerService), new Uri(baseAddress)))
{
// Open the ServiceHostBase 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 ServiceHostBase to shutdown the service.
serviceHost.Close();
}
}
}
}
' Service Code:
Imports System.ServiceModel.Channels
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions
Imports System.Text
Imports System.Collections.Generic
Namespace Microsoft.ServiceModel.Samples
' Define a service contract.
<ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples", SessionMode:=SessionMode.Required)> _
Public Interface IOrderTaker
<OperationContract(IsOneWay:=True)> _
Sub OpenPurchaseOrder(ByVal customerId As String)
<OperationContract(IsOneWay:=True)> _
Sub AddProductLineItem(ByVal productId As String, ByVal quantity As Integer)
<OperationContract(IsOneWay:=True)> _
Sub EndPurchaseOrder()
End Interface
' Define the Purchase Order Line Item
Public Class PurchaseOrderLineItem
Private Shared r As New Random(137)
Private ProductId As String
Private UnitCost As Single
Private Quantity As Integer
Public Sub New(ByVal productId As String, ByVal quantity As Integer)
Me.ProductId = productId
Me.Quantity = quantity
Me.UnitCost = r.Next(10000)
End Sub
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 Purchase Order
Public Class PurchaseOrder
Private PONumber As String
Private CustomerId As String
Private orderLineItems As New LinkedList(Of PurchaseOrderLineItem)()
Public Sub New(ByVal customerId As String)
Me.CustomerId = customerId
Me.PONumber = Guid.NewGuid().ToString()
End Sub
Public Sub AddProductLineItem(ByVal productId As String, ByVal quantity As Integer)
orderLineItems.AddLast(New PurchaseOrderLineItem(productId, quantity))
End Sub
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 "Pending"
End Get
End Property
Public Overrides Function ToString() As String
Dim strbuf As New 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
' Service class which implements the service contract.
' Added code to write output to the console window
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)> _
Public Class OrderTakerService
Implements IOrderTaker
Private po As PurchaseOrder
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=False)> _
Public Sub OpenPurchaseOrder(ByVal customerId As String) Implements IOrderTaker.OpenPurchaseOrder
Console.WriteLine("Creating purchase order")
po = New PurchaseOrder(customerId)
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=False)> _
Public Sub AddProductLineItem(ByVal productId As String, ByVal quantity As Integer) Implements IOrderTaker.AddProductLineItem
po.AddProductLineItem(productId, quantity)
Console.WriteLine("Product " & productId & " quantity " & quantity & " added to purchase order")
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
Public Sub EndPurchaseOrder() Implements IOrderTaker.EndPurchaseOrder
Console.WriteLine("Purchase Order Completed")
Console.WriteLine()
Console.WriteLine(po.ToString())
End Sub
' Host the service within this EXE console application.
Public Shared Sub Main()
' Get MSMQ queue name from app settings 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
' Get the base address that is used to listen for WS-MetaDataExchange requests
Dim baseAddress As String = ConfigurationManager.AppSettings("baseAddress")
' Create a ServiceHost for the OrderTakerService type.
Using serviceHost As New ServiceHost(GetType(OrderTakerService), New Uri(baseAddress))
' Open the ServiceHostBase 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 ServiceHostBase to shutdown the service.
serviceHost.Close()
End Using
End Sub
End Class
End Namespace
Code für den Client
using System;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.Transactions;
namespace Microsoft.ServiceModel.Samples
{
//The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.
//Client implementation code.
class Client
{
static void Main()
{
//Create a transaction scope.
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
// Create a proxy with given client endpoint configuration
OrderTakerClient client = new OrderTakerClient("OrderTakerEndpoint");
try
{
// Open a purchase order
client.OpenPurchaseOrder("somecustomer.com");
Console.WriteLine("Purchase Order created");
// Add product line items
Console.WriteLine("Adding 10 quantities of blue widget");
client.AddProductLineItem("Blue Widget", 10);
Console.WriteLine("Adding 23 quantities of red widget");
client.AddProductLineItem("Red Widget", 23);
// Close the purchase order
Console.WriteLine("Closing the purchase order");
client.EndPurchaseOrder();
client.Close();
}
catch (CommunicationException ex)
{
client.Abort();
}
// Complete the transaction.
scope.Complete();
}
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.Transactions
Namespace Microsoft.ServiceModel.Samples
'The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.
'Client implementation code.
Friend Class Client
Shared Sub Main()
'Create a transaction scope.
Using scope As New TransactionScope(TransactionScopeOption.Required)
' Create a proxy with given client endpoint configuration
Dim client As New OrderTakerClient("OrderTakerEndpoint")
Try
' Open a purchase order
client.OpenPurchaseOrder("somecustomer.com")
Console.WriteLine("Purchase Order created")
' Add product line items
Console.WriteLine("Adding 10 quantities of blue widget")
client.AddProductLineItem("Blue Widget", 10)
Console.WriteLine("Adding 23 quantities of red widget")
client.AddProductLineItem("Red Widget", 23)
' Close the purchase order
Console.WriteLine("Closing the purchase order")
client.EndPurchaseOrder()
client.Close()
Catch ex As CommunicationException
client.Abort()
End Try
' Complete the transaction.
scope.Complete()
End Using
Console.WriteLine()
Console.WriteLine("Press <ENTER> to terminate client.")
Console.ReadLine()
End Sub
End Class
End Namespace