Raggruppamento di messaggi in coda in una sessione
Windows Communication Foundation (WCF) fornisce una sessione che consente di raggruppare un set di messaggi correlati affinché vengano elaborati da un'unica applicazione ricevente. I messaggi appartenenti a una sessione devono appartenere alla stessa transazione. Poiché tutti i messaggi appartengono alla stessa transazione, se l'elaborazione di un messaggio non riesce viene eseguito il rollback dell'intera sessione. Le sessioni presentano comportamenti simili relativamente alle code di messaggi non recapitabili e alle code di messaggi non elaborabili. La proprietà di durata (TTL, Time To Live) impostata in un'associazione in coda configurata per una determinata sessione viene applicata all'intera sessione. Se allo scadere del TTL è stata inviata solo una parte dei messaggi, l'intera sessione viene inserita nella coda di messaggi non recapitabili. Analogamente, quando risulta impossibile inviare a un'applicazione alcuni messaggi di una sessione contenuti nella coda dell'applicazione, l'intera sessione viene inserita nella coda di messaggi non elaborabili (se disponibile).
Esempio di raggruppamento di messaggi
Un caso in cui raggruppare i messaggi risulta utile è l'implementazione come servizio WCF di un'applicazione di elaborazione degli ordini. Si supponga ad esempio che un client invii a questa applicazione un ordine contenente alcuni elementi. Per ogni elemento il client effettua una chiamata al servizio. Ciò comporta l'invio di un messaggio a parte per ogni elemento. È possibile che il server A riceva il primo elemento e il server B riceva il secondo elemento. Ogni volta che viene aggiunto un elemento, il server che lo sta elaborando deve individuare l'ordine associato e quindi aggiungervi tale elemento. Ciò risulta particolarmente inefficiente. Gli stessi problemi di inefficienza si presentano anche utilizzando un unico server che gestisce tutte le richieste. Infatti, tale server deve tenere traccia di tutti gli ordini correntemente in elaborazione e determinare a quale di essi appartiene il nuovo elemento. Il raggruppamento di tutte le richieste relative a un determinato ordine consente di semplificare notevolmente l'implementazione dell'applicazione descritta. L'applicazione client invia in una sessione tutti gli elementi relativi a un determinato ordine. Pertanto, quando il servizio elabora l'ordine, elabora l'intera sessione in un'unica operazione. \
Procedure
Per configurare l'utilizzo di sessioni in un contratto di servizio
Definire un contratto di servizio che richieda una sessione. A tale scopo, utilizzare l'attributo OperationContractAttribute e specificare:
SessionMode=SessionMode.Required
Poiché questi metodi non restituiscono alcun dato, contrassegnare le operazioni del contratto come unidirezionali. A tale scopo, utilizzare l'attributo OperationContractAttribute e specificare:
[OperationContract(IsOneWay = true)]
Implementare il contratto di servizio e specificare la modalità InstanceContextMode PerSession. Ciò comporta la creazione di un'unica istanza del servizio per ogni sessione.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
Per ogni operazione del servizio è necessario specificare una transazione. A tale scopo, utilizzare l'attributo OperationBehaviorAttribute. L'operazione che completa la transazione deve inoltre impostare la proprietà TransactionAutoComplete su true.
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
Configurare un endpoint che utilizza l'associazione NetProfileMsmqBinding fornita dal sistema.
Creare una coda transazionale utilizzando lo spazio dei nomi System.Messaging. Tale coda può anche essere creata tramite il sistema di accodamento dei messaggi (MSMQ) o la console MMC. In tal caso, creare una coda transazionale.
Creare un host del servizio mediante la classe ServiceHost.
Aprire l'host del servizio per rendere disponibile il servizio.
Chiudere l'host del servizio.
Per configurare un client
Creare un ambito di transazione per scrivere nella coda transazionale.
Creare il client WCF tramite lo strumento Strumento ServiceModel Metadata Utility Tool (Svcutil.exe).
Inviare l'ordine.
Chiudere il client WCF.
Esempio
Descrizione
Nell'esempio seguente viene riportato il codice del servizio IProcessOrder e di un client che utilizza tale servizio. In particolare, viene illustrato come WCF utilizza sessioni in coda per fornire il comportamento di raggruppamento.
Codice del servizio
' Service Code:
Imports System
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
// 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 Config File: -->
<appSettings>
<!-- use appSetting to configure MSMQ queue name -->
<add key="queueName" value=".\private$\ServiceModelSamplesSession" />
<add key="baseAddress" value="https://localhost:8000/orderTaker/sessionSample"/>
</appSettings>
<system.serviceModel>
<services>
<service name="Microsoft.ServiceModel.Samples.OrderTakerService"
behaviorConfiguration="MyServiceTypeBehaviors" >
<!-- Define NetMsmqEndpoint -->
<endpoint address="net.msmq://localhost/private/ServiceModelSamplesSession"
binding="netMsmqBinding"
contract="Microsoft.ServiceModel.Samples.IOrderTaker" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceTypeBehaviors" >
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Codice del client
Imports System
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
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();
}
}
}
<system.serviceModel>
<client>
<!-- Define NetMsmqEndpoint -->
<endpoint name="OrderTakerEndpoint"
address="net.msmq://localhost/private/ServiceModelSamplesSession"
binding="netMsmqBinding"
contract="IOrderTaker" />
</client>
</system.serviceModel>