175

I have a WCF service that is hosted in a Windows Service. Clients that using this service must pass an identifier every time they're calling service methods (because that identifier is important for what the called method should do). I thought it is a good idea to somehow put this identifier to the WCF header information.

If it is a good idea, how can I add the identifier automatically to the header information. In other words, whenever the user calls the WCF method, the identifier must be automatically added to the header.

UPDATE: Clients that are using the WCF service are both Windows applications and Windows Mobile application (using Compact Framework).

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
mrtaikandi
  • 6,753
  • 16
  • 62
  • 93
  • Did you end up getting this to work on the Compact Framework? – Vaccano Jan 13 '10 at 17:49
  • 1
    See: https://blogs.msdn.microsoft.com/wsdevsol/2014/02/07/adding-custom-messageheader-and-http-header-to-a-wcf-method-call-in-a-windows-store-app-or-windows-phone-app/ – Ian Kemp Apr 19 '17 at 12:12

14 Answers14

200

The advantage to this is that it is applied to every call.

Create a class that implements IClientMessageInspector. In the BeforeSendRequest method, add your custom header to the outgoing message. It might look something like this:

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

Then create an endpoint behavior that applies the message inspector to the client runtime. You can apply the behavior via an attribute or via configuration using a behavior extension element.

Here is a great example of how to add an HTTP user-agent header to all request messages. I am using this in a few of my clients. You can also do the same on the service side by implementing the IDispatchMessageInspector.

Is this what you had in mind?

Update: I found this list of WCF features that are supported by the compact framework. I believe message inspectors classified as 'Channel Extensibility' which, according to this post, are supported by the compact framework.

Andreas
  • 705
  • 10
  • 22
Mark Good
  • 4,271
  • 2
  • 31
  • 43
  • Thanks. This is exactly what i wanted but does it work in compact framework? – mrtaikandi Sep 12 '09 at 08:25
  • Mohammadreza, I'm not sure. The list that I found suggested that they might be, but I haven't been able to confirm. – Mark Good Sep 12 '09 at 15:01
  • 2
    @Mark, This is a really great answer. Thanks. I've tried this over net.tcp but am using the Headers collection directly (the Http Headers didn't work). I get a Header with my token (Name) in at ServiceHost AfterReceiveRequest event, but not the value (there doesn't even seem to be a property for a value?). Is there something I am missing? I would have expected a name/value pair as when I create the header it asks me for: request.Headers.Add(MessageHeader.CreateHeader(name, ns, value)); – Program.X Dec 16 '09 at 15:13
  • 17
    +1 `OutgoingMessageProperties` are what you need to access HTTP Headers - not `OutgoingMessageHeaders` which are SOAP headers. – SliverNinja - MSFT Jan 25 '12 at 16:30
  • 3
    This only allows a hardcoded user agent, which - according to the given example - is hardcoded in the web.config! – KristianB Oct 10 '12 at 07:47
  • @KristianB, you're free to use whatever user agent you like. Feel free to add logic to change it based on some condition. – Mark Good Nov 20 '12 at 00:37
  • 1
    This is an excellent answer. It also handles the case when the HttpRequestMessageProperty.Name is not yet available in the message properties. For some reason, debugging my code, I realized that depending on some timing issues this value was not always there. Thanks Mark! – carlos357 Apr 13 '18 at 07:42
  • @MarkGood, if I need to pass winform authentication(fetched from custom db), how can I pass these info from winform to the clientInspector? I was using a global variable but I do not think this is the correct way to handle this. – Spencer Mar 03 '19 at 15:02
  • But I'm using Asp.net core 2.2 and doesn't have "BehaviorExtensionElement" class to implement. what should I do? – AmirReza-Farahlagha Jun 16 '19 at 07:28
80

You add it to the call using:

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
}

And then, server-side you grab it using:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
  • 53,070
  • 5
  • 41
  • 38
  • 5
    Thanks for you code-snippet. But with this I have to add the header every time I want to call a method. I wanted to make this process transparent. I mean with implementing once, every time the user creates a service client and used a method, the customer header automatically added to the message. – mrtaikandi Jun 08 '09 at 11:36
  • This is a good MSDN link with an example to expand on the suggestion provided in this answer: http://msdn.microsoft.com/en-us/library/system.servicemodel.operationcontext.outgoingmessageheaders(v=vs.90).aspx – atconway Nov 02 '12 at 20:22
  • 1
    Thanks, this is a great piece of code if you are using a custom client library. This way you don't need to implement the messageinspector. Just create a common wrapper method which wraps every client call in trhe OperationContextScope. – JustAMartin Feb 18 '13 at 17:27
  • 4
    As a note, this is problematic if you're doing any sort of async stuff with your calls, because `OperationContextScope` (and `OperationContext`) are `ThreadStatic` - **Mark Good**'s answer will work without relying on `ThreadStatic` items. – zimdanen Apr 11 '14 at 01:27
  • Thank you for the `now make the WCF call within this using block` – AndreFeijo Apr 09 '18 at 05:16
  • caveat: you can't call await inside the using block. (you can if you love memory leaks) – Chazt3n Jun 13 '19 at 17:43
  • 7
    This doesn't add an HTTP header! It adds headers to the SOAP envelope. – br3nt Mar 16 '20 at 22:33
  • @zimdanen - except Mark Good's answer doesn't allow you to set a request-specific value. Is there a best of both worlds? – xr280xr Apr 10 '20 at 22:23
  • 1
    This will set SOAP-headers, not HTTP-headers. – Martin Mulder Jun 17 '20 at 16:13
31

If you just want to add the same header to all the requests to the service, you can do it with out any coding!
Just add the headers node with required headers under the endpoint node in your client config file

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nick
  • 13,238
  • 17
  • 64
  • 100
Nimesh Madhavan
  • 6,290
  • 6
  • 44
  • 55
  • 22
    These are SOAP Headers (*ala [`MessageHeader`](http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.messageheader.aspx)*) - not HTTP Headers. – SliverNinja - MSFT Jan 25 '12 at 16:28
24

Here is another helpful solution for manually adding custom HTTP Headers to your client WCF request using the ChannelFactory as a proxy. This would have to be done for each request, but suffices as a simple demo if you just need to unit test your proxy in preparation for non-.NET platforms.

// 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
  • 31,051
  • 11
  • 110
  • 173
17

If you want to add custom HTTP headers to every WCF call in an object oriented way, look no further.

Just as in Mark Good's and paulwhit's answer, we need to subclass IClientMessageInspector to inject the custom HTTP headers into the WCF request. However, lets make the inspector more generic by accepting an dictionary containing the headers we want to add:

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

Just as in Mark Good's and paulwhit's answer, we need to subclass IEndpointBehavior to inject our HttpHeaderMessageInspector into our WCF client.

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

The last part needed to finish our object oriented approach, is to create a subclass of our WCF auto-generated client (I used Microsoft's WCF Web Service Reference Guide to generate a WCF client).

In my case, I need to attach an API key to the x-api-key HTML header.

The subclass does the following:

  • calls the constructor of the base class with the required parameters (in my case a EndpointConfiguration enum was generated to pass into the constructor - maybe your implementation won't have this)
  • Defines the headers that should be attached to every requests
  • Attaches AddHttpHeaderMessageEndpointBehavior to the client's Endpoint behaviors
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);
    }
}

Finally, use your client!

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

The resulting HTTP request should contain your HTTP headers and look something like this:

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
  • 9,017
  • 3
  • 42
  • 63
  • I am using dotnet-svcutil. There is no `Endpoint` class. Replace with actual client reference `_client.Endpoint.EndpointBehaviors.Add(behaviour);` – unsafePtr Apr 30 '21 at 12:41
11

This is similar to NimsDotNet answer but shows how to do it programmatically.

Simply add the header to the binding

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
  • 29,542
  • 12
  • 100
  • 122
  • I got this code added to my current call (client side) .. How to I get this head value in the System.ServiceModel.OperationContext ? (server side) (I'm crossing my fingers that this will help me) – granadaCoder Jul 18 '16 at 20:30
  • 2
    Got it ! System.ServiceModel.Channels.MessageHeaders headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader("ClientIdentification", string.Empty); var requestName = (headerIndex < 0) ? "UNKNOWN" : headers.GetHeader(headerIndex); – granadaCoder Jul 18 '16 at 21:45
  • 3
    This adds a header to the SOAP envelope, not an HTTP header – br3nt Mar 16 '20 at 22:37
7

This works for me

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);
}
SteveC
  • 15,808
  • 23
  • 102
  • 173
Taran
  • 2,895
  • 25
  • 22
6

This is what worked for me, adapted from Adding HTTP Headers to WCF Calls

// 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)
    {
    }
}

After declaring these classes you can add the new behavior to your WCF client like this:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
paulwhit
  • 8,719
  • 4
  • 29
  • 30
  • This won't compile: Error CS0136 A local or parameter named 'property' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter. – Leszek P Apr 25 '17 at 15:29
  • just remove the one not used – kosnkov Dec 02 '19 at 07:22
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
SteveC
  • 15,808
  • 23
  • 102
  • 173
shepcom
  • 67
  • 1
  • 1
2

Context bindings in .NET 3.5 might be just what you're looking for. There are three out of the box: BasicHttpContextBinding, NetTcpContextBinding, and WSHttpContextBinding. Context protocol basically passes key-value pairs in the message header. Check out Managing State With Durable Services article on MSDN magazine.

Mehmet Aras
  • 5,284
  • 1
  • 25
  • 32
  • Also note that you only set the context once before establishing a session with the server. Then the context becomes readonly. If you want the context setup to be transparent on the client side, you can derive from the client proxt class and in the contructor you can add the information that make up your context. Then each time the client creates an instance of the client proxt, the context will be automatically created and added to the client proxy instance. – Mehmet Aras Jun 08 '09 at 11:45
2

If I understand your requirement correctly, the simple answer is: you can't.

That's because the client of the WCF service may be generated by any third party that uses your service.

IF you have control of the clients of your service, you can create a base client class that add the desired header and inherit the behavior on the worker classes.

Paulo Santos
  • 11,285
  • 4
  • 39
  • 65
  • 1
    agreed, if you are truly building SOA, you can't assume that all clients are .NET-based. Wait until your business gets acquired. – SliverNinja - MSFT Jan 25 '12 at 16:27
  • 2
    Is this really true? Java web service clients dont have the ability to add name/values to SOAP headers? I find that hard to believe. Sure it would be a different implementation, but this is an interoperable solution – Adam Aug 09 '12 at 20:33
2

You can specify custom headers in the MessageContract.

You can also use < endpoint> headers that are stored in the configuration file and will be copied allong in the header of all the messages sent by the client/service. This is usefull to add some static header easily.

Philippe
  • 3,945
  • 3
  • 38
  • 56
  • 3
    These are SOAP Headers (*ala [`MessageHeader`](http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.messageheader.aspx)*) - not HTTP Headers. – SliverNinja - MSFT Jan 25 '12 at 16:28
0

Found another approach here:

SoapServiceClient client = new SoapServiceClient();
using(new OperationContextScope(client.InnerChannel)) 
{
    HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
    requestMessage.Headers["MyHttpHeader"] = "MyHttpHeaderValue";
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;    
    var result = client.MyClientMethod();
}
fubo
  • 44,811
  • 17
  • 103
  • 137
-1

A bit late to the party but Juval Lowy addresses this exact scenario in his book and the associated ServiceModelEx library.

Basically he defines ClientBase and ChannelFactory specialisations that allow specifying type-safe header values. I suggesst downloading the source and looking at the HeaderClientBase and HeaderChannelFactory classes.

John

  • 1
    This is pretty much nothing but promoting someone's work. Could you add a relevant excerpt/algorithm -- i.e. answer the question -- or disclose any affiliation you have? Otherwise this is just fancied-up spam. – Nic Apr 28 '15 at 12:30
  • I would say that it's giving someone an answer by way of a pointer to an approach they may not be aware of. I've given the relevant link why should I need to add more? it's all in the references. And I'm sure Juval Lowy could describe it better than I could ever do :-) As for my affiliation - I bought the book! That's it. I've never met Mr Lowy but I'm sure he's a great chap. Knows a lot about WCF apparently ;-) – BrizzleOwl Apr 29 '15 at 14:37
  • You should add more because presumably you read [How to Answer](http://stackoverflow.com/help/how-to-answer) before answering, and you noted the section that says "Always quote the most relevant part of an important link, in case the target site is unreachable or goes permanently offline." Your affiliation isn't important. Only the quality of the answer is. – Nic Apr 29 '15 at 14:39
  • Fine. I'm not in it for the points - as you can probably tell from my score! Just thought it might be a useful pointer. – BrizzleOwl Apr 29 '15 at 14:42
  • 1
    I'm not saying it's a bad pointer. I'm saying that, on its own, it's not a good answer. It may very well help people, and that's a good thing, but the answer will be better if you can describe the method he uses, rather than giving a very brief description of the classes involved. That way, on the occasion that the site can't be accessed -- for whatever reason -- your answer still helps. – Nic Apr 29 '15 at 14:44