Gestione dei messaggi non elaborabili in MSMQ 3.0

Questo esempio dimostra come eseguire la gestione dei messaggi non elaborabili in un servizio. L'esempio è basato sull'esempio Associazioni MSMQ transazionali. In questo esempio viene utilizzato netMsmqBinding. Il servizio è un'applicazione console indipendente che consente di osservare il servizio che riceve messaggi in coda.

Nella comunicazione in coda, il client comunica al servizio utilizzando una coda. Più precisamente, il client invia messaggi a una coda. Il servizio riceve messaggi dalla coda. Di conseguenza, per comunicare mediante una coda il servizio e il client non devono essere in esecuzione contemporaneamente.

Un messaggio non elaborabile è un messaggio che viene letto ripetutamente da una coda quando il servizio che legge il messaggio è in grado di elaborarlo e quindi termina la transazione nella quale viene letto il messaggio. In questi casi, il messaggio viene ritentato. Teoricamente l'operazione può ripetersi all'infinito se c'è un problema con il messaggio. Notare che questo può accadere solo quando si utilizzano transazioni per leggere dalla coda e richiamare il funzionamento del servizio.

In base alla versione di MSMQ, NetMsmqBinding supporta dal rilevamento limitato fino a quello completo dei messaggi non elaborabili. Dopo che il messaggio è stato rilevato come non elaborabile, è possibile gestirlo in alcuni modi.

Questo esempio illustra le funzioni dei messaggi non elaborabili limitate fornite nelle piattaforme Windows Server 2003 e Windows XP e quelle complete fornite in Windows Vista. In entrambi gli esempi, l'obiettivo è di spostare il messaggio non elaborabile in un'altra coda che quindi può essere gestita da un servizio di messaggi non elaborabili.

Esempio di gestione dei messaggi non elaborabili MSMQ: Windows Server 2003 e Windows XP

In MSMQ v3.0 la coda secondaria dei messaggi non elaborabili non è supportata. Pertanto, in questo esempio, viene creata una coda per trattenere i messaggi non elaborabili. Si tratta del metodo migliore di trattare i messaggi non elaborabili in MSMQ v3.0 e per impedire la perdita di dati.

Il rilevamento dei messaggi non elaborabili in Windows Server 2003 e Windows XP è ristretto alla configurazione di una singola proprietà ReceiveRetryCount. La proprietà ReceiveRetryCount è il numero di volte in cui il messaggio può essere letto ripetutamente dalla coda. Windows Communication Foundation (WCF) trattiene questo conteggio in memoria. Quando il conteggio viene raggiunto (ovvero, il messaggio non è elaborabile) passiamo a esaminare il modo in cui gestire tale messaggio. L'enumerazione ReceiveErrorHandling proprietà dell'associazione fornisce valori possibili che può prendere. I valori dell'enumerazione sono:

  • Fault (impostazione predefinita): per determinare l'errore del listener e anche dell'host del servizio.
  • Drop: per rilasciare il messaggio.
  • Move: per spostare il messaggio nella coda secondaria dei messaggi non elaborabili. Questo valore è disponibile solo in Windows Vista.
  • Reject: per rifiutare il messaggio, rispedendolo alla coda dei messaggi non recapitabili del mittente. Questo valore è disponibile solo in Windows Vista.

Di questi, solo Fault e Drop sono solo valori validi, se si utilizza MSMQ v3.0. L'esempio illustra come gestire messaggi non elaborabili utilizzando MSMQ v3.0. Questo esempio può essere eseguito anche in MSMQ v4.0 e funziona esattamente allo stesso modo.

Il contratto di servizio è IOrderProcessorche definisce un servizio unidirezionale adatto per l'utilizzo con le code.

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

L'operazione del servizio visualizza un messaggio indicante che l'ordine è in fase di elaborazione. Per dimostrare la funzionalità dei messaggi non elaborabili l'operazione del servizio SubmitPurchaseOrder genera un'eccezione per eseguire il rollback della transazione in una chiamata casuale del servizio. Questo fa in modo che il messaggio venga rimesso nella coda. Eventualmente il messaggio viene contrassegnato come non elaborabile. La configurazione è impostata per restituire un errore in caso di messaggi non elaborabili. Se il messaggio non è elaborabile, il canale in coda genera una classe MsmqPoisonMessageException che contiene la proprietà MessageLookupId del messaggio non elaborabile e genera errori nell'host del servizio. Un gestore di errori viene allegato per controllare questa eccezione e agire di conseguenza. Per aggiungere il gestore di errori creare un PoisonErrorBehaviorAttribute e annotare OrderProcessorService con questo comportamento.

    // Service class that implements the service contract.
    // Added code to write output to the console window.
    [PoisonErrorBehavior]
    public class OrderProcessorService : IOrderProcessor
    {
        static Random r = new Random(137);
        public static string QueueName;
        public static string PoisonQueueName;

        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {

            int randomNumber = r.Next(10);

            if (randomNumber % 2 == 0)
            {
                Orders.Add(po);
                Console.WriteLine("Processing {0} ", po);
            }
            else
            {
                Console.WriteLine("Aborting transaction, cannot process purchase order: " + po.PONumber);
                Console.WriteLine();
                throw new Exception("Cannot process purchase order: " + po.PONumber);
            }
        }

        public static void StartThreadProc(object stateInfo)
        {
            StartService();
        }

        public static void StartService()
        {
            // Get MSMQ queue name from app settings in configuration
            QueueName = ConfigurationManager.AppSettings["queueName"];

            // Get MSMQ queue name for the final poison queue
            PoisonQueueName = ConfigurationManager.AppSettings["poisonQueueName"];

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

            // Create the transacted poison message MSMQ queue if necessary.
            if (!System.Messaging.MessageQueue.Exists(PoisonQueueName))
                System.Messaging.MessageQueue.Create(PoisonQueueName, true);


            // Get the base address that is used to listen for WS-MetaDataExchange requests
            // This is useful to generate a proxy for the client
            string baseAddress = ConfigurationManager.AppSettings["baseAddress"];

            // Create a ServiceHost for the OrderProcessorService type.
            ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService), new Uri(baseAddress));

            // Hook on to the service host faulted events
            serviceHost.Faulted += new EventHandler(OnServiceFaulted);

            // Open the ServiceHostBase to create listeners and start listening for messages.
            serviceHost.Open();
            
        }


        public static void OnServiceFaulted(object sender, EventArgs e) 
        {
            Console.WriteLine("Service Faulted");
        }
        

        // Host the service within this EXE console application.
        public static void Main()
        {

            OrderProcessorService.StartService();

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

        }
    }

PoisonErrorBehaviorAttribute installa PoisonErrorHandler. PoisonErrorHandler è un'implementazione della classe IErrorHandler. IErrorHandler viene chiamato ogni qualvolta viene generata un'eccezione nel sistema. Nell'implementazione, si cerca MsmqPoisonMessageException che indica che è stato trovato un messaggio non elaborabile. Si recupera l'ID del messaggio MSMQ dall'eccezione e si utilizza l'API System.Messaging per ricevere il messaggio non elaborabile dalla coda e spostarlo in una coda specifica di messaggi non elaborabili da elaborare successivamente. Poiché il messaggio può essere bloccato ancora sotto la transazione ServiceModel, si aspetta di vedere se la lettura è senza successo e si tenta nuovamente. Quando il messaggio è stato spostato in una coda diversa, il resto dei messaggi della coda può essere utilizzato. Viene creato un nuovo ServiceHost che si apre per l'elaborazione. Nell'esempio di codice seguente viene illustrato questo comportamento.

    public class PoisonErrorHandler : IErrorHandler
    {
        static WaitCallback orderProcessingCallback = new WaitCallback(OrderProcessorService.StartThreadProc);

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            // no-op -we are not interested in this.
        }
        
        // Handle poison message exception by moving the offending message out of the way for regular processing to go on.
        public bool HandleError(Exception error)
        {
            MsmqPoisonMessageException poisonException = error as MsmqPoisonMessageException;
            if (null != poisonException)
            {
                long lookupId = poisonException.MessageLookupId;
                Console.WriteLine(" Poisoned message -message look up id = {0}", lookupId);

                // Get MSMQ queue name from app settings in configuration.

                System.Messaging.MessageQueue orderQueue = new System.Messaging.MessageQueue(OrderProcessorService.QueueName);
                System.Messaging.MessageQueue poisonMessageQueue = new System.Messaging.MessageQueue(OrderProcessorService.PoisonQueueName);
                System.Messaging.Message message = null;
                
                // Use a new transaction scope to remove the message from the main application queue and add it to the poison queue.
                // The poison message service processes messages from the poison queue.
                using(TransactionScope txScope= new TransactionScope(TransactionScopeOption.RequiresNew))
                {
                    int retryCount = 0;
                    while(retryCount < 3) 
                    {
                        retryCount++;
                    
                        try 
                        {
                            // Look up the poison message using the look up id.
                            message = orderQueue.ReceiveByLookupId(lookupId);
                            if(message != null) 
                            {
                                // Send the message to the poison message queue.
                                poisonMessageQueue.Send(message, System.Messaging.MessageQueueTransactionType.Automatic);
                                
                                // complete transaction scope
                                txScope.Complete();

                                Console.WriteLine("Moved poisoned message with look up id: {0} to poison queue: {1} ", lookupId, OrderProcessorService.PoisonQueueName);
                                break;
                            }
                        
                        }
                        catch(InvalidOperationException ) 
                        {
                            //Code for the case when the message may still not be available in the queue because of a race condition in transaction or 
                            //another node in the farm may actually have taken the message.
                            if (retryCount < 3) 
                            {
                                Console.WriteLine("Trying to move poison message but message is not available, sleeping for 10 seconds before retrying");
                                Thread.Sleep(TimeSpan.FromSeconds(10));
                            }
                            else 
                            {
                                Console.WriteLine("Giving up on trying to move the message");
                            }
                        }
                    
                    }
                }

                // Restart the service host.
                Console.WriteLine();
                Console.WriteLine("Restarting the service to process rest of the messages in the queue");
                Console.WriteLine("Press <ENTER> to stop the service");
                ThreadPool.QueueUserWorkItem(orderProcessingCallback);
                return true;
            }

            return false;
        }
    }

La configurazione del servizio comprende le seguenti proprietà dei messaggi non elaborabili: receiveRetryCounte receiveErrorHandling come è illustrato nel file di configurazione seguente. Le proprietà maxRetryCycles e retryCycleDelay vengono ignorate in Windows Server 2003 e Windows XP.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Use appSetting to configure the MSMQ queue name. -->
    <add key="queueName" value=".\private$\ServiceModelSamplesPoison" />
    <add key="poisonQueueName" value=".\private$\OrderProcessorPoisonQueue" />
    <add key="baseAddress" value="https://localhost:8000/orderProcessor/poisonSample"/>
  </appSettings>
  <system.serviceModel>
    <services>
      <service 
              name="Microsoft.ServiceModel.Samples.OrderProcessorService">
        <!-- Define NetMsmqEndpoint -->
        <endpoint address="net.msmq://localhost/private/ServiceModelSamplesPoison"
                  binding="netMsmqBinding"
                  bindingConfiguration="PoisonBinding" 
                  contract="Microsoft.ServiceModel.Samples.IOrderProcessor" />
      </service>
    </services>

    <bindings>
      <netMsmqBinding>
        <binding name="PoisonBinding" 
                 receiveRetryCount="0"
                 maxRetryCycles="1"
                 retryCycleDelay="00:00:05"                      
                 receiveErrorHandling="Fault">
        </binding>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Elaborazione dei messaggi dalla coda dei messaggi non elaborabili

Il servizio dei messaggi non elaborabili legge i messaggi dalla coda finale dei messaggi non elaborabili e li elabora.

I messaggi nella coda dei messaggi non elaborabili sono messaggi indirizzati al servizio che sta elaborando il messaggio, il quale può essere dall'endpoint del servizio dei messaggi non elaborabili. Pertanto, quando il servizio dei messaggi non elaborabili legge messaggi dalla coda, il livello del canale di WCF trova la mancata corrispondenza negli endpoint e non invia il messaggio. In questo caso, il messaggio è indirizzato al servizio di elaborazione ordini ma viene ricevuto dal servizio dei messaggi non elaborabili. Per continuare a ricevere se il messaggio anche se è è indirizzato a un endpoint diverso, è necessario aggiungere un ServiceBehavior per filtrare gli indirizzi nei quali il criterio di corrispondenza è qualsiasi endpoint del servizio al quale il messaggio è indirizzato il messaggio. Questo è necessario per elaborare correttamente messaggi che vengono letti dalla coda dei messaggi non elaborabili.

L'implementazione stessa del servizio dei messaggi non elaborabili è molto simile all'implementazione del servizio. Implementa il contratto ed elabora gli ordini. Di seguito è riportato l'esempio di codice completo:

    // Service class that implements the service contract.
    // Added code to write output to the console window.
    [ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
    public class OrderProcessorService : IOrderProcessor
    {
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {
            Orders.Add(po);
            Console.WriteLine("Processing {0} ", po);
        }

        public static void OnServiceFaulted(object sender, EventArgs e) 
        {
            Console.WriteLine("Service Faulted...exiting app");
            Environment.Exit(1);
        }


        // Host the service within this EXE console application.
        public static void Main()
        {
   
            // Create a ServiceHost for the OrderProcessorService type.
            ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService));

            // Hook on to the service host faulted events.
            serviceHost.Faulted += new EventHandler(OnServiceFaulted);
            
            serviceHost.Open();

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

            // Close the ServiceHostBase to shutdown the service.
            if(serviceHost.State != CommunicationState.Faulted)
            {
                serviceHost.Close();
            }
        }

Diversamente dal servizio di elaborazione ordini che legge i messaggi dalla coda degli ordini, il servizio dei messaggi non elaborabili legge i messaggi dalla coda secondaria non elaborabile. La coda non elaborabile è una coda secondaria della coda principale, denominata "non elaborabile" e è generata automaticamente da MSMQ. Per accedervi, fornire il nome della coda principale seguito da un "," e il nome della coda secondaria, in questo caso - "poison", come illustrato nella configurazione di esempio seguente.

Nota

Nell'esempio per MSMQv3.0, il nome della coda non elaborabile non è una coda secondaria, piuttosto la coda nella quale viene spostato il messaggio.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Microsoft.ServiceModel.Samples.OrderProcessorService">
        <!-- Define NetMsmqEndpoint -->
        <endpoint address="net.msmq://localhost/private/ServiceModelSamplesPoison;poison"
                  binding="netMsmqBinding"
                  contract="Microsoft.ServiceModel.Samples.IOrderProcessor" >
        </endpoint>
      </service>
    </services>

  </system.serviceModel>
</configuration> 

Quando si esegue l'esempio, le attività del client, del servizio e del servizio messaggi non elaborabili vengono visualizzate nelle finestre della console. È possibile osservare il servizio che riceve i messaggi dal client. Premere INVIO in ciascuna finestra della console per arrestare i servizi.

Il servizio avvia l'esecuzione, elaborando gli ordini e in modo casuale inizia a terminare l'elaborazione. Se il messaggio indica che ha elaborato l'ordine, è possibile eseguire di nuovo il client per inviare un altro messaggio finché non si nota che il servizio ha effettivamente terminato un messaggio. In base alle impostazioni dei messaggi non elaborabili configurate, l'elaborazione del messaggio viene tentata una volta prima che questo venga rimosso dalla coda finale non elaborabile.

The service is ready.
Press <ENTER> to terminate service.

Processing Purchase Order: 0f063b71-93e0-42a1-aa3b-bca6c7a89546
        Customer: somecustomer.com
        OrderDetails
                Order LineItem: 54 of Blue Widget @unit price: $29.99
                Order LineItem: 890 of Red Widget @unit price: $45.89
        Total cost of this order: $42461.56
        Order status: Pending

Processing Purchase Order: 5ef9a4fa-5a30-4175-b455-2fb1396095fa
        Customer: somecustomer.com
        OrderDetails
                Order LineItem: 54 of Blue Widget @unit price: $29.99
                Order LineItem: 890 of Red Widget @unit price: $45.89
        Total cost of this order: $42461.56
        Order status: Pending

Aborting transaction, cannot process purchase order: 23e0b991-fbf9-4438-a0e2-20adf93a4f89

Avviare il servizio messaggi non elaborabili per leggere il messaggio non elaborabile dalla coda non elaborabile. In questo esempio,il servizio messaggi non elaborabili legge il messaggio e lo elabora. È possibile osservare che l'ordine di acquisto terminato e impostato come non elaborabile viene letto dal servizio messaggi non elaborabili.

The service is ready.
Press <ENTER> to terminate service.

Processing Purchase Order: 23e0b991-fbf9-4438-a0e2-20adf93a4f89
        Customer: somecustomer.com
        OrderDetails
                Order LineItem: 54 of Blue Widget @unit price: $29.99
                Order LineItem: 890 of Red Widget @unit price: $45.89
        Total cost of this order: $42461.56
        Order status: Pending

Per impostare, compilare ed eseguire l'esempio

  1. Assicurarsi di avere eseguito Procedura di installazione singola per gli esempi di Windows Communication Foundation.

  2. Per generare l'edizione in C# o Visual Basic .NET della soluzione, seguire le istruzioni in Generazione degli esempi Windows Communication Foundation.

  3. Per eseguire l'esempio in una configurazione singola o tra più computer, modificare i nomi della coda per riflettere il nome host effettivo anziché localhost e seguire le istruzioni in Esecuzione degli esempi di Windows Communication Foundation.

Per impostazione predefinita con il trasporto dell'associazione netMsmqBinding è attivata la protezione. Il tipo di protezione del trasporto è determinato da due proprietà, MsmqAuthenticationMode e MsmqProtectionLevel. Per impostazione predefinita, la modalità di autenticazione è impostata su Windows e il livello di protezione èimpostato su Sign. Affinché MSMQ fornisca la funzionalità di autenticazione e firma, deve appartenere a un dominio. Se si esegue questo esempio in un computer che non appartiene a un dominio, si riceve l'errore seguente: "Il certificato interno del servizio di accodamento messaggi non esiste".

Per eseguire l'esempio in un computer appartenente a un gruppo di lavoro

  1. Se il computer non appartiene a un dominio, disattivare la protezione del trasporto impostando la modalità di autenticazione e livello di protezione su None come illustrato nella configurazione di esempio seguente:

    <bindings>
        <netMsmqBinding>
            <binding name="TransactedBinding">
                <security mode="None"/>
            </binding>
        </netMsmqBinding>
    </bindings>
    

    Verificare che l'endpoint sia associato all'associazione impostando l'attributo bindingConfiguration.

  2. Assicurarsi di modificare la configurazione in PoisonMessageServer, sul server e sul client prima che di eseguire l'esempio.

    Nota

    L'impostazione di security mode su None è equivalente all'impostazione di MsmqAuthenticationMode, MsmqProtectionLevel e della protezione Message su None.

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.