Partilhar via


Expedição por elemento de corpo

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

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

Algumas pilhas de serviços Web SOAP 1.1 que não seguem as diretrizes do WS-I Basic Profile 1.1 não enviam mensagens com base no URI de ação, mas sim com base no nome qualificado XML do primeiro elemento dentro do corpo SOAP. Da mesma forma, o lado do cliente dessas pilhas pode enviar mensagens com um cabeçalho HTTP SoapAction vazio ou arbitrário, o que era 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 strings, em que os nomes qualificados indicam o nome do primeiro filho do corpo 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 comparadas com 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 construir, 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 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 obter o nome do elemento atual e o URI do namespace e combiná-los em um XmlQualifiedName que é usado para procurar 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 posterior. 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 para a 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

Os seletores de operação de despacho de serviço são extensões para o dispatcher do Windows Communication Foundation (WCF). Para selecionar métodos no canal de retorno de chamada de contratos duplex, também há seletores de operação de cliente, que funcionam muito como os seletores de operação de despacho 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 despacho são adicionados ao dispatcher usando comportamentos. Um comportamento é um objeto de configuração, que adiciona uma ou mais extensões ao tempo de execução do dispatch (ou ao tempo de execução 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 tempo de execução de despacho é 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, solicitados a contribuir com extensões ou modificar a configuração padrão.

Para maior brevidade, o trecho de código a seguir mostra apenas a implementação do método ApplyDispatchBehavior, que afeta as alterações de configuração para o dispatcher neste 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 ApplyDispatchBehavior implementação configura o dicionário de pesquisa para o seletor de operação iterando sobre os OperationDescription elementos no ponto de ContractDescriptionextremidade do serviço. Em seguida, cada descrição da operação é inspecionada quanto à presença do DispatchBodyElementAttribute comportamento, uma implementação que IOperationBehavior também é 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 tempo de execução do dispatch. Todos os seus métodos retornam ao chamador sem tomar nenhuma 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 do corpo em cuja ocorrência uma operação é selecionada, possam ser associados às respetivas operações.

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

Uma vez que o dicionário é preenchido, um novo DispatchByBodyElementOperationSelector é construído com essas informações e definido como o seletor de operação do tempo de execução do despacho:

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 do fio são interpretadas e despachadas, 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 serviço de projeto de exemplo aplica o comportamento do DispatchByBodyElementBehaviorAttribute contrato ao IDispatchedByBody contrato de serviço e rotula cada uma das duas operações OperationForBodyA() e OperationForBodyB() com um DispatchBodyElementAttribute comportamento de operação. 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 despacha apenas com base no elemento corpo da mensagem e ignora a "Ação", é necessário dizer ao tempo de execução para não verificar o cabeçalho "Ação" nas respostas retornadas atribuindo o curinga "*" à ReplyAction propriedade de OperationContractAttribute. Além disso, é necessário ter uma operação padrão que tenha a propriedade "Action" definida como o curinga "*". A operação padrão recebe todas as mensagens que não podem ser enviadas e não tem 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 construindo o exemplo

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

O cliente envia três mensagens para o serviço cujo elemento de conteúdo do corpo é chamado bodyA, bodyB, e bodyX, respectivamente. Como pode ser adiado da descrição anterior e do contrato de serviço mostrado, a mensagem de entrada com o bodyA elemento é enviada para o OperationForBodyA() método. Como não há um destino de envio 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 para o 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. Certifique-se de ter executado o procedimento de instalação única para os exemplos do Windows Communication Foundation.

  2. Para criar a solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.

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