Compartilhar via


Expedição por elemento Body

O exemplo AdvancedDispatchByBody demonstra como implementar um algoritmo alternativo para atribuir mensagens de entrada às operações.

Por padrão, o dispatcher de 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 HTTP SOAP.

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 desses stacks pode enviar mensagens com um cabeçalho HTTP do SoapAction que pode ser vazio ou arbitrário, o que foi permitido pela especificação SOAP 1.1.

Para alterar a maneira como as mensagens são enviadas para métodos, o exemplo implementa a IDispatchOperationSelector interface de extensibilidade 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. O defaultOperationName é o nome da operação que recebe todas as mensagens que não podem ser correspondidas a este 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;
    }
}

IDispatchOperationSelector as implementações 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 que seja igual ao nome de um método no contrato de serviço para o 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 à qual as propriedades da mensagem e os cabeçalhos da 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;
}

Adicionando 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 Attribute classe derivada, 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 fins de 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 neste exemplo. Os outros métodos não são mostrados porque retornam ao chamador sem realizar nenhuma ação.

[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 de operação é inspecionada quanto à presença do comportamento DispatchBodyElementAttribute, uma implementação de IOperationBehavior que também está definida neste 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 com base no nome qualificado XML (QName propriedade) e o nome da operação (Name propriedade) 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);
    }
}

Implementando o serviço

O comportamento implementado neste exemplo afeta diretamente como as mensagens do fio são interpretadas e expedidas, 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 de 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 expedidas 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 é simples. Cada método encapsula a mensagem recebida em uma mensagem de resposta e a ecoa de volta para o 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 corporal é nomeado bodyA, bodyBe bodyX, respectivamente. Como pode ser deduzido da descrição anterior e do contrato de serviço mostrado, a mensagem de entrada com o elemento bodyA é expedida para o método OperationForBodyA(). Como não há um destino de expedição explícito para a mensagem com o bodyX elemento body, 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 mensagens de entrada e saída claramente 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 avulsa dos exemplos do Windows Communication Foundation.

  2. Para criar a solução, siga as instruções na criação dos exemplos do Windows Communication Foundation.

  3. Para executar o exemplo em uma configuração única ou entre máquinas, siga as instruções em Executando os exemplos do Windows Communication Foundation.