Uso di code di messaggi non recapitabili per gestire errori di trasferimento dei messaggi
È possibile che i messaggi in coda non vengano recapitati. Tali messaggi con errori vengono registrati in una coda di messaggi non recapitabili. Il mancato recapito può essere dovuto a errori della rete, all'eliminazione di una coda, a una coda completa, a un errore di autenticazione o alla scadenza del tempo disponibile per il recapito.
I messaggi in coda possono rimanere nella coda per molto tempo se l'applicazione ricevente non li legge tempestivamente. Questo comportamento potrebbe non essere adatto per i messaggi a scadenza. Nell'associazione in coda la proprietà TTL (Time to Live) dei messaggi a scadenza è impostata, a indicare la durata della permanenza dei messaggi nella coda prima che scadano. I messaggi scaduti vengono inviati a una coda speciale denominata coda dei messaggi non recapitabili. I messaggi possono essere inoltre inseriti in una coda di messaggi non recapitabili per altri motivi, ad esempio se viene superata una quota della coda o se si verifica un errore di autenticazione.
In genere le applicazioni scrivono logica di compensazione per leggere i messaggi dalla coda dei messaggi non recapitabili e le motivazioni dell'errore. La logica di compensazione dipende dalla causa dell'errore. Nel caso di un errore di autenticazione, ad esempio, è possibile correggere il certificato allegato al messaggio e inviare di nuovo il messaggio. Se il messaggio non è stato recapitato a causa del raggiungimento della quota della coda di destinazione, è possibile tentare di recapitare di nuovo il messaggio nella speranza che il problema della quota sia stato risolto.
La maggior parte dei sistemi di accodamento dispongono di una coda di messaggi non recapitabili a livello di sistema nella quale vengono archiviati tutti i messaggi con errori provenienti dal sistema. In MSMQ (Accodamento messaggi) sono presenti due code di messaggi non recapitabili a livello di sistema: una coda di messaggi non recapitabili relativi a transazioni in cui sono archiviati messaggi non recapitati alla coda transazionale e una coda di messaggi non recapitabili in cui sono archiviati messaggi non recapitati alla coda non transazionale. Se due client inviano messaggi a due servizi diversi e, di conseguenza, code diverse in WCF condividono lo stesso servizio MSMQ per l'invio, è possibile che nella coda di messaggi non recapitabili del sistema esista una combinazione di messaggi. Non è sempre una situazione ottimale. In molti casi (di sicurezza, ad esempio), non è opportuno che un client legga i messaggi di un altro client da una coda di messaggi non recapitabili. Una coda di messaggi non recapitabili condivisa, inoltre, impone ai client di scorrere la coda per trovare un messaggio inviato, operazione potenzialmente laboriosa in base al numero dei messaggi contenuti nella coda. Pertanto in WCFNetMsmqBinding
, MsmqIntegrationBinding,
e MSMQ in Windows Vista forniscono una coda di messaggi non recapitabili personalizzata (talvolta denominata coda di messaggi non recapitabili specifica dell'applicazione).
La coda di messaggi non recapitabili personalizzata fornisce l'isolamento tra client che condividono lo stesso servizio MSMQ per l'invio dei messaggi.
In Windows Server 2003 e Windows XP, Windows Communication Foundation (WCF) fornisce una coda di messaggi non recapitabili a livello di sistema per tutte le applicazioni client in coda. In Windows Vista WCF fornisce una coda di messaggi non recapitabili per ogni applicazione client in coda.
Specificazione dell'utilizzo della coda dei messaggi non recapitabili
La coda dei messaggi non recapitabili si trova nel gestore delle code dell'applicazione mittente. Archivia messaggi scaduti o che non è stato possibile trasferire o recapitare.
L'associazione dispone delle proprietà della coda di messaggi non recapitabili seguenti:
Lettura dalla coda dei messaggi non recapitabili
Un'applicazione che legge i messaggi da una coda di messaggi non recapitabili è simile a un servizio WCF che legge da una coda dell'applicazione, ad eccezione delle minime differenze seguenti:
Per leggere messaggi da una coda di sistema di messaggi non recapitabili relativi a transazioni, l'URI (Uniform Resource Identifier) deve presentarsi nel formato net.msmq://localhost/system$;DeadXact.
Per leggere messaggi da una coda di sistema di messaggi non recapitabili non relativi a transazioni, l'URI deve presentarsi nel formato net.msmq://localhost/system$;DeadLetter.
Per leggere messaggi da una coda di messaggi non recapitabili personalizzata, l'URI deve presentarsi nel formato net.msmq://localhost/private/<custom-dlq-name> dove custom-dlq-name è il nome della coda di messaggi non recapitabili personalizzata.
Per altre informazioni su come gestire le code, vedere Endpoint di servizio e indirizzamento delle code.
Lo stack WCF sul lato destinatario abbina gli indirizzi sui quali il servizio è in ascolto con l'indirizzo presente sul messaggio. Se gli indirizzi corrispondono, il messaggio viene inviato. In caso contrario, il messaggio non viene inviato. Ciò può provocare problemi in caso di lettura dalla coda di messaggi non recapitabili, perché i messaggi in essa contenuti sono solitamente indirizzati al servizio, non al servizio della coda di messaggi non recapitabili. Pertanto, il servizio che legge dalla coda di messaggi non recapitabili deve installare un ServiceBehavior
di filtro di indirizzi che indichi allo stack di far corrispondere tutti i messaggi contenuti nella coda indipendentemente dal destinatario. In particolare è necessario aggiungere un ServiceBehavior
con il parametro Any al servizio che legge messaggi dalla coda di messaggi non recapitabili.
Gestione dei messaggi non elaborabili dalla coda dei messaggi non recapitabili
La gestione dei messaggi non elaborabili è disponibile nelle code dei messaggi non recapitabili, ad alcune condizioni. Poiché non è possibile creare code secondarie dalle code di sistema, durante la lettura dalla coda di sistema dei messaggi non recapitabili ReceiveErrorHandling
non può essere impostato su Move
. Si noti che se si legge da una coda di messaggi non recapitabili personalizzata, è possibile avere code secondarie e, quindi, Move
è una disposizione valida per il messaggio non elaborabile.
Quando ReceiveErrorHandling
è impostato su Reject
, durante la lettura dalla coda di messaggi non recapitabili personalizzata il messaggio non elaborabile viene inserito nella coda di sistema dei messaggi non recapitabili. Nel caso di lettura dalla coda di sistema dei messaggi non recapitabili, il messaggio viene rilasciato (eliminato). Un rifiuto da una coda di sistema di messaggi non recapitabili in MSMQ comporta il rilascio (eliminazione) del messaggio.
Esempio
Nell'esempio seguente viene descritto come creare una coda di messaggi non recapitabili e come utilizzarla per elaborare messaggi scaduti. L'esempio si basa sull'esempio in Procedura: Scambiare messaggi in coda con endpoint WCF. Nell'esempio seguente viene descritto come scrivere il codice client nel servizio di elaborazione ordini che utilizza una coda di messaggi non recapitabili per ogni applicazione. Nell'esempio viene inoltre mostrato come elaborare messaggi dalla coda di messaggi non recapitabili.
Il codice seguente si riferisce a un client che specifica una coda di messaggi non recapitabili per ogni applicazione.
using System;
using System.ServiceModel.Channels;
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()
{
// Get MSMQ queue name from appsettings in configuration.
string deadLetterQueueName = ConfigurationManager.AppSettings["deadLetterQueueName"];
// Create the transacted MSMQ queue for storing dead message if necessary.
if (!System.Messaging.MessageQueue.Exists(deadLetterQueueName))
System.Messaging.MessageQueue.Create(deadLetterQueueName, true);
OrderProcessorClient client = new OrderProcessorClient("OrderProcessorEndpoint");
try
{
// 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();
}
client.Close();
}
catch(TimeoutException timeout)
{
Console.WriteLine(timeout.Message);
client.Abort();
}
catch(CommunicationException conexcp)
{
Console.WriteLine(conexcp.Message);
client.Abort();
}
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}
Imports System.ServiceModel.Channels
Imports System.Configuration
'using 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()
' Get MSMQ queue name from appsettings in configuration.
Dim deadLetterQueueName As String = ConfigurationManager.AppSettings("deadLetterQueueName")
' Create the transacted MSMQ queue for storing dead message if necessary.
If (Not System.Messaging.MessageQueue.Exists(deadLetterQueueName)) Then
System.Messaging.MessageQueue.Create(deadLetterQueueName, True)
End If
Dim client As New OrderProcessorClient("OrderProcessorEndpoint")
Try
' 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
client.Close()
Catch timeout As TimeoutException
Console.WriteLine(timeout.Message)
client.Abort()
Catch conexcp As CommunicationException
Console.WriteLine(conexcp.Message)
client.Abort()
End Try
Console.WriteLine()
Console.WriteLine("Press <ENTER> to terminate client.")
Console.ReadLine()
End Sub
End Class
End Namespace
Il codice seguente è per un file di configurazione client.
Il codice seguente si riferisce a un servizio che elabora messaggi da una coda di messaggi non recapitabili.
using System;
using System.ServiceModel.Description;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Transactions;
namespace Microsoft.ServiceModel.Samples
{
// 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
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, AddressFilterMode = AddressFilterMode.Any)]
public class PurchaseOrderDLQService : IOrderProcessor
{
OrderProcessorClient orderProcessorService;
public PurchaseOrderDLQService()
{
orderProcessorService = new OrderProcessorClient("OrderProcessorEndpoint");
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SimpleSubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine("Submitting purchase order did not succeed ", po);
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
Console.WriteLine();
}
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SubmitPurchaseOrder(PurchaseOrder po)
{
Console.WriteLine("Submitting purchase order did not succeed ", po);
MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
Console.WriteLine();
// Resend the message if timed out.
if (mqProp.DeliveryFailure == DeliveryFailure.ReachQueueTimeout ||
mqProp.DeliveryFailure == DeliveryFailure.ReceiveTimeout)
{
// Re-send.
Console.WriteLine("Purchase order Time To Live expired");
Console.WriteLine("Trying to resend the message");
// Reuse the same transaction used to read the message from dlq to enqueue the message to the application queue.
orderProcessorService.SubmitPurchaseOrder(po);
Console.WriteLine("Purchase order resent");
}
}
// Host the service within this EXE console application.
public static void Main()
{
// Create a ServiceHost for the PurchaseOrderDLQService type.
using (ServiceHost serviceHost = new ServiceHost(typeof(PurchaseOrderDLQService)))
{
// Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The dead letter service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
serviceHost.Close();
}
}
}
}
Imports System.ServiceModel.Description
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.Transactions
Namespace Microsoft.ServiceModel.Samples
' 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
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Single, AddressFilterMode:=AddressFilterMode.Any)> _
Public Class PurchaseOrderDLQService
Implements IOrderProcessor
Private orderProcessorService As OrderProcessorClient
Public Sub New()
orderProcessorService = New OrderProcessorClient("OrderProcessorEndpoint")
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
Public Sub SimpleSubmitPurchaseOrder(ByVal po As PurchaseOrder)
Console.WriteLine("Submitting purchase order did not succeed ", po)
Dim mqProp As MsmqMessageProperty = TryCast(OperationContext.Current.IncomingMessageProperties(MsmqMessageProperty.Name), MsmqMessageProperty)
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus)
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure)
Console.WriteLine()
End Sub
<OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
Public Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder) Implements IOrderProcessor.SubmitPurchaseOrder
Console.WriteLine("Submitting purchase order did not succeed ", po)
Dim mqProp As MsmqMessageProperty = TryCast(OperationContext.Current.IncomingMessageProperties(MsmqMessageProperty.Name), MsmqMessageProperty)
Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus)
Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure)
Console.WriteLine()
' Resend the message if timed out.
If mqProp.DeliveryFailure = DeliveryFailure.ReachQueueTimeout OrElse mqProp.DeliveryFailure = DeliveryFailure.ReceiveTimeout Then
' Re-send.
Console.WriteLine("Purchase order Time To Live expired")
Console.WriteLine("Trying to resend the message")
' Reuse the same transaction used to read the message from dlq to enqueue the message to the application queue.
orderProcessorService.SubmitPurchaseOrder(po)
Console.WriteLine("Purchase order resent")
End If
End Sub
' Host the service within this EXE console application.
Public Shared Sub Main()
' Create a ServiceHost for the PurchaseOrderDLQService type.
Using serviceHost As New ServiceHost(GetType(PurchaseOrderDLQService))
' Open the ServiceHostBase to create listeners and start listening for messages.
serviceHost.Open()
' The service can now be accessed.
Console.WriteLine("The dead letter 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
Il codice seguente è per il file di configurazione del servizio della coda di messaggi non recapitabili.