Expedição por elemento Body

O exemplo AdvancedDispatchByBody demonstra como implementar um algoritmo alternativo para atribuição de mensagens de entrada a operações.

Por padrão, o dispatcher do modelo de serviço seleciona o método de tratamento apropriado para uma mensagem de entrada com base no cabeçalho "Ação" WS-Addressing da mensagem ou nas informações equivalentes na solicitação SOAP HTTP.

Algumas pilhas de serviços Web do SOAP 1.1 que não seguem as diretrizes do WS-I Basic Profile 1.1 não despacham mensagens com base no URI de Ação, mas sim com base no nome XML qualificado do primeiro elemento dentro do corpo de SOAP. Da mesma forma, o lado do cliente dessas pilhas pode enviar mensagens com um cabeçalho SoapAction HTTP vazio ou arbitrário, o que era permitido pela especificação SOAP 1.1.

Para alterar a forma como as mensagens são enviadas para métodos, o exemplo implementa a interface de extensibilidade IDispatchOperationSelector no DispatchByBodyElementOperationSelector. Essa classe seleciona operações com base no primeiro elemento do corpo da mensagem.

O construtor de classe espera um dicionário preenchido com pares de XmlQualifiedName e cadeias de caracteres, em que os nomes qualificados indicam o nome do primeiro filho do corpo de SOAP, e as cadeias de caracteres indicam o nome da operação correspondente. defaultOperationName é o nome da operação que recebe todas as mensagens que não podem ser correspondidas a esse dicionário:

class DispatchByBodyElementOperationSelector : IDispatchOperationSelector
{
    Dictionary<XmlQualifiedName, string> dispatchDictionary;
    string defaultOperationName;

    public DispatchByBodyElementOperationSelector(Dictionary<XmlQualifiedName,string> dispatchDictionary, string defaultOperationName)
    {
        this.dispatchDictionary = dispatchDictionary;
        this.defaultOperationName = defaultOperationName;
    }
}

Implementações de IDispatchOperationSelector são muito simples de criar, pois há apenas um método na interface: SelectOperation. O trabalho desse método é inspecionar uma mensagem de entrada e retornar uma cadeia de caracteres igual ao nome de um método no contrato de serviço do ponto de extremidade atual.

Neste exemplo, o seletor de operação adquire um XmlDictionaryReader para o corpo da mensagem de entrada usando GetReaderAtBodyContents. Esse método já posiciona o leitor no primeiro filho do corpo da mensagem para que seja suficiente para obter o nome e o URI do namespace do elemento atual e combiná-los em um XmlQualifiedName, que então é usado para pesquisar a operação correspondente no dicionário mantido pelo seletor de operação.

public string SelectOperation(ref System.ServiceModel.Channels.Message message)
{
    XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
    XmlQualifiedName lookupQName = new
       XmlQualifiedName(bodyReader.LocalName, bodyReader.NamespaceURI);
    message = CreateMessageCopy(message,bodyReader);
    if (dispatchDictionary.ContainsKey(lookupQName))
    {
         return dispatchDictionary[lookupQName];
    }
    else
    {
        return defaultOperationName;
    }
}

Acessar o corpo da mensagem com GetReaderAtBodyContents ou qualquer um dos outros métodos que fornecem acesso ao conteúdo do corpo da mensagem faz com que a mensagem seja marcada como "lida", o que significa que a mensagem é inválida para qualquer processamento adicional. Portanto, o seletor de operação cria uma cópia da mensagem de entrada com o método mostrado no código a seguir. Como a posição do leitor não foi alterada durante a inspeção, ela pode ser referenciada pela mensagem recém-criada, na qual as propriedades da mensagem e os cabeçalhos de mensagem também são copiados, o que resulta em um clone exato da mensagem original:

private Message CreateMessageCopy(Message message,
                                     XmlDictionaryReader body)
{
    Message copy = Message.CreateMessage(message.Version,message.Headers.Action,body);
    copy.Headers.CopyHeaderFrom(message,0);
    copy.Properties.CopyProperties(message.Properties);
    return copy;
}

Adição de um seletor de operação a um serviço

Seletores de operação de expedição de serviço são extensões para o dispatcher do WCF (Windows Communication Foundation). Para selecionar métodos no canal de retorno de chamada de contratos duplex, também há seletores de operação do cliente, que funcionam muito como os seletores de operação de expedição descritos aqui, mas que não são explicitamente abordados neste exemplo.

Como a maioria das extensões de modelo de serviço, os seletores de operação de expedição são adicionados ao dispatcher usando comportamentos. Um comportamento é um objeto de configuração, que adiciona uma ou mais extensões ao runtime de expedição (ou ao runtime do cliente) ou altera suas configurações.

Como os seletores de operação têm escopo de contrato, o comportamento apropriado a ser implementado aqui é o IContractBehavior. Como a interface é implementada em uma classe derivada Attribute, conforme mostrado no código a seguir, o comportamento pode ser adicionado declarativamente a qualquer contrato de serviço. Sempre que um ServiceHost é aberto e o runtime de expedição é criado, todos os comportamentos encontrados como atributos em contratos, operações e implementações de serviço ou como elemento na configuração de serviço são adicionados automaticamente e, posteriormente, recebem uma solicitação de contribuição com extensões ou modificação da configuração padrão.

Para obter brevidade, o trecho de código a seguir mostra apenas a implementação do método ApplyDispatchBehavior, o que afeta as alterações de configuração do dispatcher no exemplo. Os outros métodos não são mostrados porque retornam ao chamador sem fazer qualquer trabalho.

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
class DispatchByBodyElementBehaviorAttribute : Attribute, IContractBehavior
{
    // public void AddBindingParameters(...)
    // public void ApplyClientBehavior(...)
    // public void Validate(...)

Primeiro, a implementação ApplyDispatchBehavior configura o dicionário de pesquisa para o seletor de operação iterando sobre os elementos OperationDescription no ContractDescription do ponto de extremidade de serviço. Em seguida, cada descrição da operação é inspecionada quanto à presença do comportamento DispatchBodyElementAttribute, uma implementação de IOperationBehavior que também é definida nesse exemplo. Embora essa classe também seja um comportamento, ela é passiva e não contribui ativamente com nenhuma alteração de configuração para o runtime de expedição. Todos os seus métodos retornam ao chamador sem a execução de qualquer ação. O comportamento da operação só existe para que os metadados necessários para o novo mecanismo de expedição, ou seja, o nome qualificado do elemento body no qual uma operação está selecionada, possam ser associados às respectivas operações.

Se esse comportamento for encontrado, um par de valores criado a partir do nome XML qualificado (propriedade QName) e o nome da operação (propriedade Name) será adicionado ao dicionário.

Após o preenchimento do dicionário, um novo DispatchByBodyElementOperationSelector é construído com essas informações e definido como o seletor de operação do runtime de expedição:

public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
    Dictionary<XmlQualifiedName,string> dispatchDictionary =
                     new Dictionary<XmlQualifiedName,string>();
    foreach( OperationDescription operationDescription in
                              contractDescription.Operations )
    {
        DispatchBodyElementAttribute dispatchBodyElement =
   operationDescription.Behaviors.Find<DispatchBodyElementAttribute>();
        if ( dispatchBodyElement != null )
        {
             dispatchDictionary.Add(dispatchBodyElement.QName,
                              operationDescription.Name);
        }
    }
    dispatchRuntime.OperationSelector =
            new DispatchByBodyElementOperationSelector(
               dispatchDictionary,
               dispatchRuntime.UnhandledDispatchOperation.Name);
    }
}

Implementação do serviço

O comportamento implementado neste exemplo afeta diretamente como as mensagens são interpretadas e enviadas, que é uma função do contrato de serviço. Consequentemente, o comportamento deve ser declarado no nível do contrato de serviço em qualquer implementação do serviço que opte por usá-lo.

O exemplo de serviço de projeto aplica o comportamento do contrato DispatchByBodyElementBehaviorAttribute ao contrato de serviço IDispatchedByBody e rotula cada uma das duas operações OperationForBodyA() e OperationForBodyB() com um comportamento de operação DispatchBodyElementAttribute. Quando um host de serviço para um serviço que implementa esse contrato é aberto, esses metadados são coletados pelo construtor do dispatcher, conforme descrito anteriormente.

Como o seletor de operação é enviado exclusivamente com base no elemento body da mensagem, e ignora a "Ação", é necessário informar ao runtime para não verificar o cabeçalho "Ação" nas respostas retornadas atribuindo o curinga "*" à propriedade ReplyAction de OperationContractAttribute. Além disso, é necessário ter uma operação padrão que tenha a propriedade "Ação" definida como o curinga "*". A operação padrão recebe todas as mensagens que não podem ser enviadas e não têm um DispatchBodyElementAttribute:

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples"),
                            DispatchByBodyElementBehavior]
public interface IDispatchedByBody
{
    [OperationContract(ReplyAction="*"),
     DispatchBodyElement("bodyA","http://tempuri.org")]
    Message OperationForBodyA(Message msg);
    [OperationContract(ReplyAction = "*"),
     DispatchBodyElement("bodyB", "http://tempuri.org")]
    Message OperationForBodyB(Message msg);
    [OperationContract(Action="*", ReplyAction="*")]
    Message DefaultOperation(Message msg);
}

A implementação do serviço de exemplo é bem simples. Cada método encapsula a mensagem recebida em uma mensagem de resposta e a ecoa de volta ao cliente.

Executando e criando o exemplo

Quando você executa o exemplo, o conteúdo do corpo das respostas da operação é exibido na janela do console do cliente de uma maneira semelhante à saída a seguir (formatada).

O cliente envia três mensagens para o serviço cujo elemento de conteúdo de corpo é nomeado bodyA, bodyB e bodyX, respectivamente. Segundo a descrição anterior e o contrato de serviço mostrado, a mensagem de entrada com o elemento bodyA é enviada ao método OperationForBodyA(). Como não há um destino de expedição explícito para a mensagem com o elemento de corpo bodyX, a mensagem é enviada para o DefaultOperation(). Cada uma das operações de serviço encapsula o corpo da mensagem recebida em um elemento específico ao método e o retorna, o que é feito para correlacionar claramente as mensagens de entrada e saída para este exemplo:

<?xml version="1.0" encoding="IBM437"?>
<replyBodyA xmlns="http://tempuri.org">
   <q:bodyA xmlns:q="http://tempuri.org">test</q:bodyA>
</replyBodyA>
<?xml version="1.0" encoding="IBM437"?>
<replyBodyB xmlns="http://tempuri.org">
  <q:bodyB xmlns:q="http://tempuri.org">test</q:bodyB>
</replyBodyB>
<?xml version="1.0" encoding="IBM437"?>
<replyDefault xmlns="http://tempuri.org">
   <q:bodyX xmlns:q="http://tempuri.org">test</q:bodyX>
</replyDefault>

Para configurar, compilar, e executar o exemplo

  1. Verifique se você executou o Procedimento de instalação única para os exemplos do Windows Communication Foundation.

  2. Para compilar a solução, siga as instruções contidas em Como compilar as amostras do Windows Communication Foundation.

  3. Para executar a amostra em uma configuração de computador único ou entre computadores, siga as instruções contidas em Como executar as amostras do Windows Communication Foundation.