Filas de letra morta

O exemplo DeadLetter demonstra como manipular e processar mensagens que falharam na entrega. Baseia-se no exemplo de Vinculação MSMQ Transacionada . Este exemplo usa a netMsmqBinding vinculação. O serviço é um aplicativo de console auto-hospedado para permitir que você observe o serviço recebendo mensagens na fila.

Nota

O procedimento de configuração e as instruções de compilação para este exemplo estão localizados no final deste tópico.

Nota

Este exemplo demonstra cada fila de letra morta de aplicativo que só está disponível no Windows Vista. O exemplo pode ser modificado para usar as filas padrão em todo o sistema para MSMQ 3.0 no Windows Server 2003 e Windows XP.

Na comunicação em fila, o cliente se comunica com o serviço usando uma fila. Mais precisamente, o cliente envia mensagens para uma fila. O serviço recebe mensagens da fila. O serviço e o cliente, portanto, não precisam estar em execução ao mesmo tempo para se comunicar usando uma fila.

Como a comunicação em fila pode envolver uma certa quantidade de dormência, convém associar um valor de tempo de vida na mensagem para garantir que a mensagem não seja entregue ao aplicativo se tiver passado do tempo. Há também casos em que um aplicativo deve ser informado se uma mensagem falhou na entrega. Em todos esses casos, como quando o tempo de vida da mensagem expirou ou a mensagem falhou na entrega, a mensagem é colocada em uma fila de letra morta. O aplicativo de envio pode então ler as mensagens na fila de mensagens mortas e tomar ações corretivas que variam de nenhuma ação para corrigir os motivos da falha na entrega e reenviar a mensagem.

A fila de letras mortas na NetMsmqBinding associação é expressa nas seguintes propriedades:

  • DeadLetterQueue propriedade para expressar o tipo de fila de mensagens mortas exigidas pelo cliente. Esta enumeração tem os seguintes valores:

  • None: Nenhuma fila de mensagens mortas é exigida pelo cliente.

  • System: A fila de mensagens mortas do sistema é usada para armazenar mensagens inativas. A fila de mensagens mortas do sistema é compartilhada por todos os aplicativos em execução no computador.

  • Custom: Uma fila de letras mortas personalizada especificada usando a CustomDeadLetterQueue propriedade é usada para armazenar mensagens mortas. Esta funcionalidade só está disponível no Windows Vista. Isso é usado quando o aplicativo deve usar sua própria fila de letra morta em vez de compartilhá-la com outros aplicativos em execução no mesmo computador.

  • CustomDeadLetterQueue para expressar a fila específica a ser usada como uma fila de letras mortas. Isso está disponível apenas no Windows Vista.

Neste exemplo, o cliente envia um lote de mensagens para o serviço de dentro do escopo de uma transação e especifica um valor arbitrariamente baixo para "time-to-live" para essas mensagens (cerca de 2 segundos). O cliente também especifica uma fila de mensagens mortas personalizada a ser usada para enfileirar as mensagens que expiraram.

O aplicativo cliente pode ler as mensagens na fila de mensagens mortas e tentar enviar novamente a mensagem ou corrigir o erro que fez com que a mensagem original fosse colocada na fila de mensagens mortas e enviasse a mensagem. No exemplo, o cliente exibe uma mensagem de erro.

O contrato de serviço é IOrderProcessor, conforme mostrado no código de exemplo a seguir.

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

O código de serviço no exemplo é o da Ligação MSMQ Transacionada.

A comunicação com o serviço ocorre no âmbito de uma transação. O serviço lê mensagens da fila, executa a operação e, em seguida, exibe os resultados da operação. O aplicativo também cria uma fila de mensagens mortas para mensagens mortas.

//The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.

//Client implementation code.
class Client
{
    static void Main()
    {
        // Get MSMQ queue name from app settings in configuration
        string deadLetterQueueName = ConfigurationManager.AppSettings["deadLetterQueueName"];

        // Create the transacted MSMQ queue for storing dead message if necessary.
        if (!MessageQueue.Exists(deadLetterQueueName))
            MessageQueue.Create(deadLetterQueueName, true);

        // Create a proxy with given client endpoint configuration
        OrderProcessorClient client = new OrderProcessorClient("OrderProcessorEndpoint");

        // 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();

        Console.WriteLine();
        Console.WriteLine("Press <ENTER> to terminate client.");
        Console.ReadLine();
    }
}

A configuração do cliente especifica uma curta duração para que a mensagem chegue ao serviço. Se a mensagem não puder ser transmitida dentro da duração especificada, a mensagem expirará e será movida para a fila de mensagens mortas.

Nota

É possível que o cliente entregue a mensagem para a fila de serviço dentro do tempo especificado. Para garantir que você veja o serviço de carta morta em ação, execute o cliente antes de iniciar o serviço. A mensagem expira e é entregue ao serviço de letra morta.

O aplicativo deve definir qual fila usar como sua fila de letra morta. Se nenhuma fila for especificada, a fila de letras mortas transacional padrão em todo o sistema será usada para enfileirar mensagens inativas. Neste exemplo, o aplicativo cliente especifica sua própria fila de letras mortas do aplicativo.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- use appSetting to configure MSMQ Dead Letter queue name -->
    <add key="deadLetterQueueName" value=".\private$\ServiceModelSamplesOrdersAppDLQ"/>
  </appSettings>

  <system.serviceModel>
    <client>
      <!-- Define NetMsmqEndpoint -->
      <endpoint name="OrderProcessorEndpoint"
                address="net.msmq://localhost/private/ServiceModelSamplesDeadLetter"
                binding="netMsmqBinding"
                bindingConfiguration="PerAppDLQBinding"
                contract="IOrderProcessor" />
    </client>

    <bindings>
      <netMsmqBinding>
        <binding name="PerAppDLQBinding"
                 deadLetterQueue="Custom"
                 customDeadLetterQueue="net.msmq://localhost/private/ServiceModelSamplesOrdersAppDLQ"
                 timeToLive="00:00:02"/>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>

</configuration>

O serviço de mensagens mortas lê mensagens da fila de mensagens mortas. O serviço de mensagens mortas executa o IOrderProcessor contrato. Sua implementação, no entanto, não é para processar pedidos. O serviço de mensagens mortas é um serviço de cliente e não tem a facilidade de processar pedidos.

Nota

A fila de mensagens mortas é uma fila de cliente e é local para o gerenciador de filas do cliente.

A implementação do serviço de mensagens mortas verifica o motivo pelo qual uma mensagem falhou na entrega e toma medidas corretivas. O motivo de uma falha de mensagem é capturado em duas enumerações DeliveryFailure e DeliveryStatus. Você pode recuperar o MsmqMessagePropertyOperationContext do como mostrado no código de exemplo a seguir:

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();
    …
}

As mensagens na fila de mensagens mortas são mensagens endereçadas ao serviço que está processando a mensagem. Portanto, quando o serviço de mensagem morta lê mensagens da fila, a camada de canal do Windows Communication Foundation (WCF) localiza a incompatibilidade nos pontos de extremidade e não envia a mensagem. Nesse caso, a mensagem é endereçada ao serviço de processamento de pedidos, mas é recebida pelo serviço de mensagens mortas. Para receber uma mensagem endereçada a um ponto de extremidade diferente, um filtro de endereço para corresponder a qualquer endereço é especificado no ServiceBehavior. Isso é necessário para processar com êxito as mensagens que são lidas da fila de mensagens mortas.

Neste exemplo, o serviço de mensagem morta reenvia a mensagem se o motivo da falha for que a mensagem expirou. Por todos os outros motivos, ele exibe a falha de entrega, conforme mostrado no código de exemplo a seguir:

// 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 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 app. 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();
        }
    }
}

O exemplo a seguir mostra a configuração de uma mensagem de letra morta:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service
          name="Microsoft.ServiceModel.Samples.PurchaseOrderDLQService">
        <!-- Define NetMsmqEndpoint in this case, DLQ end point to read messages-->
        <endpoint address="net.msmq://localhost/private/ServiceModelSamplesOrdersAppDLQ"
                  binding="netMsmqBinding"
                  bindingConfiguration="DefaultBinding"
                  contract="Microsoft.ServiceModel.Samples.IOrderProcessor" />
      </service>
    </services>

    <client>
      <!-- Define NetMsmqEndpoint -->
      <endpoint name="OrderProcessorEndpoint"
                 address="net.msmq://localhost/private/ServiceModelSamplesDeadLetter"
                 binding="netMsmqBinding"
                 bindingConfiguration="SystemDLQBinding"
                 contract="IOrderProcessor" />
    </client>

    <bindings>
      <netMsmqBinding>
        <binding name="DefaultBinding" />
        <binding name="SystemDLQBinding"
                 deadLetterQueue="System"/>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Ao executar o exemplo, há 3 executáveis para executar para ver como a fila de letras mortas funciona para cada aplicativo; o cliente, o serviço e um serviço de carta morta que lê a fila de mensagens mortas de cada aplicativo e reenvia a mensagem para o serviço. Todos são aplicativos de console com saída em janelas de console.

Nota

Como o enfileiramento está em uso, o cliente e o serviço não precisam estar funcionando ao mesmo tempo. Você pode executar o cliente, desligá-lo e, em seguida, iniciar o serviço e ele ainda recebe suas mensagens. Você deve iniciar o serviço e desligá-lo para que a fila possa ser criada.

Ao executar o cliente, o cliente exibe a mensagem:

Press <ENTER> to terminate client.

O cliente tentou enviar a mensagem, mas com um curto tempo limite, a mensagem expirou e agora está na fila de mensagens mortas para cada aplicativo.

Em seguida, execute o serviço de letra morta, que lê a mensagem e exibe o código de erro e reenvia a mensagem de volta para o serviço.

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

Submitting purchase order did not succeed
Message Delivery Status: InDoubt
Message Delivery Failure: ReachQueueTimeout

Purchase order Time To Live expired
Trying to resend the message
Purchase order resent

O serviço é iniciado e, em seguida, lê a mensagem reenviada e processa-a.

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

Processing Purchase Order: 97897eff-f926-4057-a32b-af8fb11b9bf9
        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

Para configurar, compilar e executar o exemplo

  1. Certifique-se de ter executado o procedimento de instalação única para os exemplos do Windows Communication Foundation.

  2. Se o serviço for executado primeiro, ele verificará se a fila está presente. Se a fila não estiver presente, o serviço criará uma. Você pode executar o serviço primeiro para criar a fila ou pode criar uma por meio do Gerenciador de Filas MSMQ. Siga estas etapas para criar uma fila no Windows 2008.

    1. Abra o Gerenciador do Servidor no Visual Studio 2012.

    2. Expanda a guia Recursos .

    3. Clique com o botão direito do mouse em Filas de Mensagens Privadas e selecione Nova, Fila Privada.

    4. Marque a caixa Transacional .

    5. Digite ServiceModelSamplesTransacted como o nome da nova fila.

  3. Para criar a edição C# ou Visual Basic .NET da solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.

  4. Para executar o exemplo em uma configuração de um único ou entre computadores, altere os nomes da fila apropriadamente, substituindo localhost pelo nome completo do computador e siga as instruções em Executando os exemplos do Windows Communication Foundation.

Para executar o exemplo em um computador associado a um grupo de trabalho

  1. Se o computador não fizer parte de um domínio, desative a segurança de transporte definindo o modo de autenticação e o nível de proteção como None mostrado na seguinte configuração de exemplo:

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

    Verifique se o ponto de extremidade está associado à associação definindo o atributo do ponto de bindingConfiguration extremidade.

  2. Certifique-se de alterar a configuração no DeadLetterService, no servidor e no cliente antes de executar o exemplo.

    Nota

    Definir security mode como None é equivalente a definir MsmqAuthenticationMode, MsmqProtectionLevel e Message segurança como None.

Comentários

Por padrão, com o transporte de ligação, a netMsmqBinding segurança está habilitada. Duas propriedades eMsmqProtectionLevel, juntas, MsmqAuthenticationMode determinam o tipo de segurança de transporte. Por padrão, o modo de autenticação é definido como Windows e o nível de proteção é definido como Sign. Para que o MSMQ forneça o recurso de autenticação e assinatura, ele deve fazer parte de um domínio. Se você executar este exemplo em um computador que não faz parte de um domínio, você receberá o seguinte erro: "O certificado interno de enfileiramento de mensagens do usuário não existe".