¿Cómo agregar un encabezado HTTP personalizado a cada llamada WCF?

162

Tengo un servicio WCF que está alojado en un servicio de Windows. Los clientes que usan este servicio deben pasar un identificador cada vez que llaman a métodos de servicio (porque ese identificador es importante para lo que debe hacer el método llamado). Pensé que es una buena idea poner este identificador de alguna manera en la información del encabezado WCF.

Si es una buena idea, ¿cómo puedo agregar el identificador automáticamente a la información del encabezado? En otras palabras, cada vez que el usuario llama al método WCF, el identificador debe agregarse automáticamente al encabezado.

ACTUALIZACIÓN: Los clientes que usan el servicio WCF son aplicaciones de Windows y aplicaciones de Windows Mobile (usando Compact Framework).

mrtaikandi
fuente
1
¿Pudiste resolver tu problema?
Mark Good
¿Terminó haciendo que esto funcione en el Marco Compacto?
Vaccano

Respuestas:

185

La ventaja de esto es que se aplica a todas las llamadas.

Cree una clase que implemente IClientMessageInspector . En el método BeforeSendRequest, agregue su encabezado personalizado al mensaje saliente. Podría verse más o menos así:

    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;
}

Luego, cree un comportamiento de punto final que aplique el inspector de mensajes al tiempo de ejecución del cliente. Puede aplicar el comportamiento mediante un atributo o mediante la configuración utilizando un elemento de extensión de comportamiento.

Aquí hay un gran ejemplo de cómo agregar un encabezado de agente de usuario HTTP a todos los mensajes de solicitud. Estoy usando esto en algunos de mis clientes. También puede hacer lo mismo en el lado del servicio implementando IDispatchMessageInspector .

¿Es esto lo que tenías en mente?

Actualización: encontré esta lista de características de WCF que son compatibles con el marco compacto. Creo que los inspectores de mensajes clasificados como 'Extensibilidad de canal' que, según esta publicación, son compatibles con el marco compacto.

Mark Good
fuente
2
@ Mark, esta es una gran respuesta. Gracias. He intentado esto en net.tcp pero estoy usando la colección Headers directamente (los Http Headers no funcionaron). Recibo un encabezado con mi token (nombre) en el evento ServiceHost AfterReceiveRequest, pero no el valor (¿ni siquiera parece haber una propiedad para un valor?). ¿Hay algo que este olvidando? Hubiera esperado un par de nombre / valor como cuando creo el encabezado que me pide: request.Headers.Add (MessageHeader.CreateHeader (name, ns, value));
Program.X
13
+1 OutgoingMessagePropertieses lo que necesita para acceder a los encabezados HTTP, no los OutgoingMessageHeadersencabezados SOAP.
SliverNinja - MSFT
1
Simplemente, código impresionante! :)
abhilashca
3
Esto solo permite un agente de usuario codificado, que, según el ejemplo dado, está codificado en web.config!
KristianB
1
Esta es una excelente respuesta. También maneja el caso cuando HttpRequestMessageProperty.Name aún no está disponible en las propiedades del mensaje. Por alguna razón, al depurar mi código, me di cuenta de que, dependiendo de algunos problemas de tiempo, este valor no siempre estaba allí. Gracias Mark!
carlos357
80

Lo agrega a la llamada 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
}

Y luego, del lado del servidor, lo agarras usando:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
fuente
55
Gracias por tu fragmento de código. Pero con esto tengo que agregar el encabezado cada vez que quiero llamar a un método. Quería hacer este proceso transparente. Quiero decir, con la implementación una vez, cada vez que el usuario crea un cliente de servicio y utiliza un método, el encabezado del cliente se agrega automáticamente al mensaje.
mrtaikandi
Este es un buen enlace de MSDN con un ejemplo para ampliar la sugerencia proporcionada en esta respuesta: msdn.microsoft.com/en-us/library/…
atconway el
1
Gracias, esta es una gran pieza de código si está utilizando una biblioteca cliente personalizada. De esta manera, no necesita implementar el inspector de mensajes. Simplemente cree un método de envoltura común que envuelva cada llamada del cliente en trhe OperationContextScope.
JustAMartin
3
Como nota, esto es problemático si está haciendo algún tipo de asíncrono con sus llamadas, porque OperationContextScope(y OperationContext) son ThreadStatic: la respuesta de Mark Good funcionará sin depender de ThreadStaticelementos.
zimdanen
2
¡Esto no agrega un encabezado HTTP! Agrega encabezados al sobre SOAP.
Br3nt
32

Si solo desea agregar el mismo encabezado a todas las solicitudes al servicio, ¡puede hacerlo sin ninguna codificación!
Simplemente agregue el nodo de encabezados con los encabezados requeridos debajo del nodo de punto final en el archivo de configuración de su cliente

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nimesh Madhavan
fuente
18
Estos son encabezados SOAP ( alaMessageHeader ), no encabezados HTTP.
SliverNinja - MSFT
18

Aquí hay otra solución útil para agregar manualmente encabezados HTTP personalizados a la solicitud WCF de su cliente utilizando ChannelFactorycomo proxy. Esto tendría que hacerse para cada solicitud, pero es suficiente como una demostración simple si solo necesita probar unitariamente su proxy en preparación para plataformas que no sean .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
fuente
1
Intenté otras 4 sugerencias similares y esta es la única que funcionó para mí.
JohnOpincar
Esto realmente agrega encabezados HTTP, ¡gracias! :) Pero, por Dios, es un código feo.
Br3nt
11

Esto es similar a la respuesta de NimsDotNet, pero muestra cómo hacerlo mediante programación.

Simplemente agregue el encabezado al enlace

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
fuente
Tengo este código agregado a mi llamada actual (lado del cliente). ¿Cómo obtengo este valor de cabecera en System.ServiceModel.OperationContext? (lado del servidor) (estoy cruzando los dedos para que esto me ayude)
granadaCoder
1
Entendido ! System.ServiceModel.Channels.MessageHeaders headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "DESCONOCIDO": encabezados. GetHeader <string> (headerIndex);
granadaCoder
1
@granadaCoder ¡Me encanta ese sitio! ;-)
ΩmegaMan
Esto agrega un encabezado al sobre SOAP, no un encabezado HTTP
br3nt
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
shepcom
fuente
12
Este es un encabezado de mensaje SOAP, no un encabezado HTTP.
René
3

Esto es lo que funcionó para mí, adaptado de Agregar encabezados HTTP a llamadas 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)
    {
    }
}

Después de declarar estas clases, puede agregar el nuevo comportamiento a su cliente WCF de esta manera:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
Paulwhit
fuente
Esto no se compilará: Error CS0136 Un local o parámetro llamado 'propiedad' no se puede declarar en este ámbito porque ese nombre se utiliza en un ámbito local adjunto para definir un local o parámetro.
Leszek P
simplemente quite el que no se usa
kosnkov
3

Esto funciona para mi

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
fuente
2

Los enlaces de contexto en .NET 3.5 pueden ser justo lo que está buscando. Hay tres listos para usar: BasicHttpContextBinding, NetTcpContextBinding y WSHttpContextBinding. El protocolo de contexto básicamente pasa pares clave-valor en el encabezado del mensaje. Consulte el artículo Gestión del estado con servicios duraderos en la revista MSDN.

Mehmet Aras
fuente
También tenga en cuenta que solo establece el contexto una vez antes de establecer una sesión con el servidor. Entonces el contexto se vuelve de solo lectura. Si desea que la configuración de contexto sea transparente en el lado del cliente, puede derivar de la clase proxt del cliente y en el constructor puede agregar la información que conforma su contexto. Luego, cada vez que el cliente crea una instancia del proxt del cliente, el contexto se creará automáticamente y se agregará a la instancia del proxy del cliente.
Mehmet Aras
2

Si entiendo su requisito correctamente, la respuesta simple es: no puede.

Esto se debe a que el cliente del servicio WCF puede ser generado por cualquier tercero que use su servicio.

SI tiene el control de los clientes de su servicio, puede crear una clase de cliente base que agregue el encabezado deseado y herede el comportamiento en las clases de trabajo.

Paulo Santos
fuente
1
De acuerdo, si realmente está construyendo SOA, no puede asumir que todos los clientes están basados ​​en .NET. Espere hasta que su negocio sea adquirido.
SliverNinja - MSFT
2
¿Es esto realmente cierto? ¿Los clientes del servicio web Java no tienen la capacidad de agregar nombres / valores a los encabezados SOAP? Me parece difícil de creer. Claro que sería una implementación diferente, pero esta es una solución interoperable
Adam
2

Puede especificar encabezados personalizados en MessageContract .

También puede usar encabezados <endpoint> que se almacenan en el archivo de configuración y se copiarán en el encabezado de todos los mensajes enviados por el cliente / servicio. Esto es útil para agregar un encabezado estático fácilmente.

Philippe
fuente
3
Estos son encabezados SOAP ( alaMessageHeader ), no encabezados HTTP.
SliverNinja - MSFT
0

Si desea agregar encabezados HTTP personalizados a cada llamada WCF de una manera orientada a objetos, no busque más.

Al igual que en la respuesta de Mark Good y paulwhit, necesitamos una subclase IClientMessageInspectorpara inyectar los encabezados HTTP personalizados en la solicitud WCF. Sin embargo, hagamos que el inspector sea más genérico al aceptar un diccionario que contiene los encabezados que queremos agregar:

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 ...
}

Al igual que en la respuesta de Mark Good y Paulwhit, necesitamos subclase IEndpointBehaviorpara inyectar nuestro HttpHeaderMessageInspectoren nuestro 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 ...
}

La última parte necesaria para finalizar nuestro enfoque orientado a objetos es crear una subclase de nuestro cliente generado automáticamente por WCF (utilicé la Guía de referencia del servicio web WCF de Microsoft para generar un cliente WCF).

En mi caso, necesito adjuntar una clave API al x-api-keyencabezado HTML.

La subclase hace lo siguiente:

  • llama al constructor de la clase base con los parámetros requeridos (en mi caso, EndpointConfigurationse generó una enumeración para pasar al constructor; tal vez su implementación no tendrá esto)
  • Define los encabezados que se deben adjuntar a cada solicitud
  • Se adhiere AddHttpHeaderMessageEndpointBehaviora los Endpointcomportamientos del 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);
    }
}

¡Finalmente, usa tu cliente!

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

La solicitud HTTP resultante debe contener sus encabezados HTTP y tener un aspecto similar a este:

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
fuente
-1

Un poco tarde para la fiesta, pero Juval Lowy aborda este escenario exacto en su libro y la biblioteca ServiceModelEx asociada .

Básicamente, define las especializaciones de ClientBase y ChannelFactory que permiten especificar valores de encabezado de tipo seguro. Sugiero descargar la fuente y mirar las clases HeaderClientBase y HeaderChannelFactory.

Juan

BrizzleOwl
fuente
1
Esto no es más que promover el trabajo de alguien. ¿Podría agregar un extracto / algoritmo relevante, es decir, responder la pregunta, o revelar cualquier afiliación que tenga? De lo contrario, esto es solo spam imaginado.
Financia la demanda de Mónica el
Diría que le está dando a alguien una respuesta a través de un puntero a un enfoque que quizás desconozcan. He dado el enlace correspondiente, ¿por qué debería agregar más? Todo está en las referencias. Y estoy seguro de que Juval Lowy podría describirlo mejor de lo que podría hacerlo :-) En cuanto a mi afiliación, ¡compré el libro! Eso es. Nunca he conocido al señor Lowy, pero estoy seguro de que es un gran tipo. Al parecer, sabe mucho sobre WCF ;-)
BrizzleOwl
Debería agregar más porque, presumiblemente, leyó Cómo responder antes de responder, y anotó la sección que dice "Siempre cite la parte más relevante de un enlace importante, en caso de que el sitio de destino no sea accesible o se desconecte permanentemente". Tu afiliación no es importante. Solo la calidad de la respuesta es.
Fondo de la demanda de Mónica
Multa. No estoy en esto por los puntos, ¡como probablemente puedas ver por mi puntaje! Solo pensé que podría ser un puntero útil.
BrizzleOwl
1
No digo que sea un mal puntero. Estoy diciendo que, por sí solo, no es una buena respuesta. Puede muy bien ayudar a las personas, y eso es algo bueno, pero la respuesta será mejor si puede describir el método que utiliza, en lugar de dar una breve descripción de las clases involucradas. De esa manera, en la ocasión en que no se puede acceder al sitio, por alguna razón, su respuesta sigue siendo útil.
Fondo de la demanda de Mónica