Como adicionar um cabeçalho HTTP personalizado a cada chamada do WCF?

162

Eu tenho um serviço WCF hospedado em um serviço do Windows. Os clientes que usam esse serviço devem passar um identificador toda vez que estão chamando métodos de serviço (porque esse identificador é importante para o que o método chamado deve fazer). Eu pensei que era uma boa idéia colocar esse identificador de alguma forma nas informações do cabeçalho do WCF.

Se for uma boa ideia, como posso adicionar o identificador automaticamente às informações do cabeçalho. Em outras palavras, sempre que o usuário chama o método WCF, o identificador deve ser adicionado automaticamente ao cabeçalho.

ATUALIZAÇÃO: os clientes que estão usando o serviço WCF são aplicativos Windows e Windows Mobile (usando o Compact Framework).

mrtaikandi
fonte
1
Você conseguiu resolver o seu problema?
Mark Boa
Você acabou fazendo isso funcionar no Compact Framework?
Vaccano 13/01/10

Respostas:

185

A vantagem disso é que ele é aplicado a todas as chamadas.

Crie uma classe que implemente IClientMessageInspector . No método BeforeSendRequest, adicione seu cabeçalho personalizado à mensagem de saída. Pode ser algo como isto:

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,  System.ServiceModel.IClientChannel channel)
{
    HttpRequestMessageProperty httpRequestMessage;
    object httpRequestMessageObject;
    if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
    {
        httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
        if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
        {
            httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
        }
    }
    else
    {
        httpRequestMessage = new HttpRequestMessageProperty();
        httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
        request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
    }
    return null;
}

Em seguida, crie um comportamento do nó de extremidade que aplique o inspetor de mensagens ao tempo de execução do cliente. Você pode aplicar o comportamento por meio de um atributo ou via configuração usando um elemento de extensão de comportamento.

Aqui está um ótimo exemplo de como adicionar um cabeçalho de agente do usuário HTTP a todas as mensagens de solicitação. Estou usando isso em alguns de meus clientes. Você também pode fazer o mesmo no lado do serviço implementando o IDispatchMessageInspector .

É isso que você tinha em mente?

Atualização: Encontrei esta lista de recursos do WCF suportados pela estrutura compacta. Acredito que os inspetores de mensagens classificados como 'Extensibilidade do canal', que, de acordo com este post, sejam suportados pela estrutura compacta.

Mark Good
fonte
2
@ Mark, esta é realmente uma ótima resposta. Obrigado. Eu tentei isso através do net.tcp, mas estou usando a coleção Headers diretamente (os cabeçalhos Http não funcionaram). Eu recebo um cabeçalho com meu token (Nome) no evento ServiceHost AfterReceiveRequest, mas não o valor (nem parece haver uma propriedade para um valor?). Há algo que estou perdendo? Eu esperaria um par de nome / valor como quando crio o cabeçalho que ele me pede: request.Headers.Add (MessageHeader.CreateHeader (name, ns, value));
Program.X
13
+1 OutgoingMessagePropertiesé o que você precisa para acessar os cabeçalhos HTTP - e não os OutgoingMessageHeaderscabeçalhos SOAP.
SliverNinja - MSFT
1
Simplesmente, código impressionante! :)
abhilashca
3
Isso permite apenas um agente de usuário codificado, que - de acordo com o exemplo fornecido - é codificado no web.config!
KristianB
1
Esta é uma excelente resposta. Ele também lida com o caso quando o HttpRequestMessageProperty.Name ainda não está disponível nas propriedades da mensagem. Por alguma razão, depurando meu código, percebi que, dependendo de alguns problemas de tempo, esse valor nem sempre estava lá. Obrigado Mark!
precisa saber é o seguinte
80

Você o adiciona à chamada usando:

using (OperationContextScope scope = new OperationContextScope((IContextChannel)channel))
{
    MessageHeader<string> header = new MessageHeader<string>("secret message");
    var untyped = header.GetUntypedHeader("Identity", "http://www.my-website.com");
    OperationContext.Current.OutgoingMessageHeaders.Add(untyped);

    // now make the WCF call within this using block
}

E então, no lado do servidor, você o pega usando:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
fonte
5
Obrigado pelo seu snippet de código. Mas com isso eu tenho que adicionar o cabeçalho toda vez que quiser chamar um método. Eu queria tornar esse processo transparente. Quero dizer, com a implementação uma vez, sempre que o usuário cria um cliente de serviço e usa um método, o cabeçalho do cliente é adicionado automaticamente à mensagem.
8119 mrtaikandi
Este é um bom link do MSDN com um exemplo para expandir a sugestão fornecida nesta resposta: msdn.microsoft.com/en-us/library/…
atconway
1
Obrigado, este é um ótimo código se você estiver usando uma biblioteca cliente personalizada. Dessa forma, você não precisa implementar o inspetor de mensagens. Basta criar um método de invólucro comum que agrupe todas as chamadas de clientes no OperationContextScope.
23813 Justamartin
3
Como nota, isso é problemático se você estiver fazendo algum tipo de assíncrono com suas chamadas, porque OperationContextScope(e OperationContext) sãoThreadStatic - a resposta de Mark Good funcionará sem depender de ThreadStaticitens.
Zimdanen
2
Isso não adiciona um cabeçalho HTTP! Ele adiciona cabeçalhos ao envelope SOAP.
br3nt 16/03
32

Se você quiser adicionar o mesmo cabeçalho a todas as solicitações do serviço, poderá fazê-lo sem qualquer codificação!
Basta adicionar o nó de cabeçalhos com os cabeçalhos necessários no nó do nó de extremidade no arquivo de configuração do cliente

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nimesh Madhavan
fonte
18
Estes são cabeçalhos SOAP ( alaMessageHeader ) - não cabeçalhos HTTP.
SliverNinja - MSFT
18

Aqui está outra solução útil para adicionar manualmente cabeçalhos HTTP personalizados à sua solicitação WCF do cliente usando o ChannelFactoryproxy. Isso teria que ser feito para cada solicitação, mas é suficiente como uma demonstração simples se você só precisar testar seu proxy de unidade em preparação para plataformas que não sejam do .NET.

// create channel factory / proxy ...
using (OperationContextScope scope = new OperationContextScope(proxy))
{
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
    {
        Headers = 
        { 
            { "MyCustomHeader", Environment.UserName },
            { HttpRequestHeader.UserAgent, "My Custom Agent"}
        }
    };    
    // perform proxy operations... 
}
SliverNinja - MSFT
fonte
1
Tentei outras 4 sugestões de aparência semelhante e essa é a única que funcionou para mim.
JohnOpincar
Na verdade, isso adiciona cabeçalhos HTTP, obrigado! :) Mas caramba, é um código feio.
br3nt 16/03
11

Isso é semelhante à resposta do NimsDotNet, mas mostra como fazê-lo programaticamente.

Basta adicionar o cabeçalho à encadernação

var cl = new MyServiceClient();

var eab = new EndpointAddressBuilder(cl.Endpoint.Address);

eab.Headers.Add( 
      AddressHeader.CreateAddressHeader("ClientIdentification",  // Header Name
                                         string.Empty,           // Namespace
                                         "JabberwockyClient"));  // Header Value

cl.Endpoint.Address = eab.ToEndpointAddress();
ΩmegaMan
fonte
Eu recebi esse código adicionado à minha chamada atual (lado do cliente). Como obter esse valor principal no System.ServiceModel.OperationContext? (lado do servidor) (Eu estou cruzando meus dedos para que isso vai me ajudar)
granadaCoder
1
Entendi ! System.ServiceModel.Channels.MessageHeaders cabeçalhos = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "DESCONHECIDO": headers.GetHeader <string> (headerIndex);
granadaCoder
1
@granadaCoder Adoro esse site! ;-)
egamegaMan
Isso adiciona um cabeçalho ao envelope SOAP, não um cabeçalho HTTP
br3nt 16/03
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
shepcom
fonte
12
Este é um cabeçalho de mensagem SOAP, não um cabeçalho HTTP.
René
3

Isso é o que funcionou para mim, adaptado de Adicionando cabeçalhos HTTP a chamadas WCF

// Message inspector used to add the User-Agent HTTP Header to the WCF calls for Server
public class AddUserAgentClientMessageInspector : IClientMessageInspector
{
    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
    {
        HttpRequestMessageProperty property = new HttpRequestMessageProperty();

        var userAgent = "MyUserAgent/1.0.0.0";

        if (request.Properties.Count == 0 || request.Properties[HttpRequestMessageProperty.Name] == null)
        {
            var property = new HttpRequestMessageProperty();
            property.Headers["User-Agent"] = userAgent;
            request.Properties.Add(HttpRequestMessageProperty.Name, property);
        }
        else
        {
            ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers["User-Agent"] = userAgent;
        }
        return null;
    }

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
    }
}

// Endpoint behavior used to add the User-Agent HTTP Header to WCF calls for Server
public class AddUserAgentEndpointBehavior : IEndpointBehavior
{
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new AddUserAgentClientMessageInspector());
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

Depois de declarar essas classes, você pode adicionar o novo comportamento ao seu cliente WCF assim:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
paulwhit
fonte
Isso não será compilado: Erro CS0136 Um local ou parâmetro chamado 'property' não pode ser declarado neste escopo porque esse nome é usado em um escopo local que o rodeia para definir um local ou parâmetro.
precisa
basta remover o não usado
kosnkov
3

Isso funciona para mim

TestService.ReconstitutionClient _serv = new TestService.TestClient();

using (OperationContextScope contextScope = new OperationContextScope(_serv.InnerChannel))
{
   HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();

   requestMessage.Headers["apiKey"] = ConfigurationManager.AppSettings["apikey"]; 
   OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = 
      requestMessage;
   _serv.Method(Testarg);
}
Taran
fonte
2

As ligações de contexto no .NET 3.5 podem ser exatamente o que você está procurando. Existem três prontos para uso: BasicHttpContextBinding, NetTcpContextBinding e WSHttpContextBinding. O protocolo de contexto basicamente passa pares de valores-chave no cabeçalho da mensagem. Confira o artigo Gerenciando Estado com Serviços Duráveis na revista MSDN.

Mehmet Aras
fonte
Observe também que você define o contexto apenas uma vez antes de estabelecer uma sessão com o servidor. Então o contexto se torna somente leitura. Se você deseja que a configuração do contexto seja transparente no lado do cliente, você pode derivar da classe proxt do cliente e, no contratante, pode adicionar as informações que compõem o seu contexto. Cada vez que o cliente cria uma instância do proxy do cliente, o contexto será automaticamente criado e adicionado à instância do proxy do cliente.
Mehmet Aras
2

Se entendi corretamente sua exigência, a resposta simples é: você não pode.

Isso ocorre porque o cliente do serviço WCF pode ser gerado por terceiros que usam seu serviço.

Se você tiver controle dos clientes do seu serviço, poderá criar uma classe de cliente base que adicione o cabeçalho desejado e herda o comportamento nas classes de trabalho.

Paulo Santos
fonte
1
concordado, se você está realmente criando SOA, não pode assumir que todos os clientes são baseados em .NET. Aguarde até que sua empresa seja adquirida.
SliverNinja - MSFT
2
Isso é mesmo verdade? Os clientes de serviço da web Java não têm a capacidade de adicionar nomes / valores aos cabeçalhos SOAP? Acho isso difícil de acreditar. Claro que seria uma implementação diferente, mas esta é uma solução interoperável
Adam
2

Você pode especificar cabeçalhos personalizados no MessageContract .

Você também pode usar os cabeçalhos <endpoint> armazenados no arquivo de configuração e serão copiados o tempo todo no cabeçalho de todas as mensagens enviadas pelo cliente / serviço. Isso é útil para adicionar algum cabeçalho estático facilmente.

Philippe
fonte
3
Estes são cabeçalhos SOAP ( alaMessageHeader ) - não cabeçalhos HTTP.
SliverNinja - MSFT
0

Se você deseja adicionar cabeçalhos HTTP personalizados a todas as chamadas WCF de maneira orientada a objetos, não procure mais.

Assim como na resposta de Mark Good e paulwhit, precisamos subclasses IClientMessageInspectorpara injetar os cabeçalhos HTTP personalizados na solicitação do WCF. No entanto, vamos tornar o inspetor mais genérico, aceitando um dicionário contendo os cabeçalhos que queremos adicionar:

public class HttpHeaderMessageInspector : IClientMessageInspector
{
    private Dictionary<string, string> Headers;

    public HttpHeaderMessageInspector(Dictionary<string, string> headers)
    {
        Headers = headers;
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // ensure the request header collection exists
        if (request.Properties.Count == 0 || request.Properties[HttpRequestMessageProperty.Name] == null)
        {
            request.Properties.Add(HttpRequestMessageProperty.Name, new HttpRequestMessageProperty());
        }

        // get the request header collection from the request
        var HeadersCollection = ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers;

        // add our headers
        foreach (var header in Headers) HeadersCollection[header.Key] = header.Value;

        return null;
    }

    // ... other unused interface methods removed for brevity ...
}

Assim como na resposta de Mark Good e paulwhit, precisamos subclasses IEndpointBehaviorpara injetar nossa HttpHeaderMessageInspectorno nosso cliente WCF.

public class AddHttpHeaderMessageEndpointBehavior : IEndpointBehavior
{
    private IClientMessageInspector HttpHeaderMessageInspector;

    public AddHttpHeaderMessageEndpointBehavior(Dictionary<string, string> headers)
    {
        HttpHeaderMessageInspector = new HttpHeaderMessageInspector(headers);
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(HttpHeaderMessageInspector);
    }

    // ... other unused interface methods removed for brevity ...
}

A última parte necessária para concluir nossa abordagem orientada a objetos é criar uma subclasse de nosso cliente gerado automaticamente pelo WCF (usei o Guia de Referência de Serviços da Web WCF da Microsoft para gerar um cliente WCF).

No meu caso, preciso anexar uma chave de API ao diretório x-api-key cabeçalho HTML.

A subclasse faz o seguinte:

  • chama o construtor da classe base com os parâmetros necessários (no meu caso, um EndpointConfiguration enum foi gerado para passar para o construtor - talvez sua implementação não tenha isso)
  • Define os cabeçalhos que devem ser anexados a todas as solicitações
  • Anexa-se AddHttpHeaderMessageEndpointBehavioraos Endpointcomportamentos do cliente
public class Client : MySoapClient
{
    public Client(string apiKey) : base(EndpointConfiguration.SomeConfiguration)
    {
        var headers = new Dictionary<string, string>
        {
            ["x-api-key"] = apiKey
        };

        var behaviour = new AddHttpHeaderMessageEndpointBehavior(headers);
        Endpoint.EndpointBehaviors.Add(behaviour);
    }
}

Por fim, use seu cliente!

var apiKey = 'XXXXXXXXXXXXXXXXXXXXXXXXX';
var client = new Client (apiKey);
var result = client.SomeRequest()

A solicitação HTTP resultante deve conter seus cabeçalhos HTTP e ter a seguinte aparência:

POST http://localhost:8888/api/soap HTTP/1.1
Cache-Control: no-cache, max-age=0
Connection: Keep-Alive
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip, deflate
x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXX
SOAPAction: "http://localhost:8888/api/ISoapService/SomeRequest"
Content-Length: 144
Host: localhost:8888

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <SomeRequestxmlns="http://localhost:8888/api/"/>
  </s:Body>
</s:Envelope>
br3nt
fonte
-1

Um pouco atrasado para a festa, mas Juval Lowy aborda esse cenário exato em seu livro e na biblioteca ServiceModelEx associada .

Basicamente, ele define as especializações ClientBase e ChannelFactory que permitem especificar valores de cabeçalho seguros para tipos. Sugiro baixar o código-fonte e examinar as classes HeaderClientBase e HeaderChannelFactory.

John

BrizzleOwl
fonte
1
Isso nada mais é do que promover o trabalho de alguém. Você poderia adicionar um trecho / algoritmo relevante - por exemplo, responder à pergunta - ou divulgar qualquer afiliação que possua? Caso contrário, isso é apenas spam fantasiado.
Fund Monica's Lawsuit
Eu diria que está dando a alguém uma resposta por meio de um ponteiro para uma abordagem da qual eles podem não estar cientes. Forneci o link relevante. Por que devo adicionar mais? está tudo nas referências. E tenho certeza que Juval Lowy poderia descrevê-lo melhor do que jamais poderia fazer :-) Quanto à minha afiliação - comprei o livro! É isso aí. Eu nunca conheci o Sr. Lowy, mas tenho certeza que ele é um ótimo sujeito. Sabe muito sobre WCF aparentemente ;-)
BrizzleOwl
Você deve adicionar mais porque, presumivelmente, você leu Como responder antes de responder e observou a seção "Sempre cite a parte mais relevante de um link importante, caso o site de destino esteja inacessível ou fique permanentemente offline". Sua afiliação não é importante. Somente a qualidade da resposta é.
Fund Monica's Lawsuit
Bem. Eu não gosto disso - como você provavelmente pode ver pela minha pontuação! Apenas pensei que poderia ser um ponteiro útil.
BrizzleOwl
1
Não estou dizendo que é um mau indicador. Estou dizendo que, por si só, não é uma boa resposta. Pode muito bem ajudar as pessoas, e isso é uma coisa boa, mas a resposta será melhor se você puder descrever o método que ele usa, em vez de fornecer uma descrição muito breve das classes envolvidas. Dessa forma, na ocasião em que o site não puder ser acessado - por qualquer motivo -, sua resposta ainda ajudará.
Fund Monica's Lawsuit