Search code examples
c#wcfsoapheadermessage

How to programmatically add soap header when calling a WCF Service from .NET?


The service configuration in the app.config contains this setting:

<client>
  <endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_GenerateAndConvert" contract="GenerateAndConvert.MyPortType" name="MyGenerateAndConvert" >
    <headers>
      <AuthHeader>
        <username>abc</username>
        <password>xyz</password>
      </AuthHeader>
    </headers>
  </endpoint>
</client>

which is serialized as:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:MyServer">
   <soapenv:Header>
       <AuthHeader>
            <username>abc</username>
            <password>xyz</password>
          </AuthHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:Convert>
      </urn:Convert>
   </soapenv:Body>
</soapenv:Envelope>

My question is how to programmatically add the SOAP Header (instead defining the username and password in the config file) when using a Service Reference in a .NET application.

I tried a solution like https://stackoverflow.com/a/53208601/255966, but I got exceptions that scope was disposed on another thread, probably because I call the WCF service Async.


Solution

  • I got it working thanks to the comments/answers on this question and these additional resources:

    My solution is as follows:

    AuthHeader

    Create AuthHeader class which extends MessageHeader to create the AuthHeader with username and password:

    public class AuthHeader : MessageHeader
    {
        private readonly string _username;
        private readonly string _password;
    
        public AuthHeader(string username, string password)
        {
            _username = username ?? throw new ArgumentNullException(nameof(username));
            _password = password ?? throw new ArgumentNullException(nameof(password));
        }
    
        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteStartElement("username");
            writer.WriteString(_username);
            writer.WriteEndElement();
    
            writer.WriteStartElement("password");
            writer.WriteString(_password);
            writer.WriteEndElement();
        }
    
        public override string Name => "AuthHeader";
    
        public override string Namespace => string.Empty;
    }
    

    HttpHeaderMessageInspector

    Create a HttpHeaderMessageInspector which can be used to add a MessageHeader to the request message.

    public class HttpHeaderMessageInspector : IClientMessageInspector
    {
        private readonly MessageHeader[] _headers;
    
        public HttpHeaderMessageInspector(params MessageHeader[] headers)
        {
            _headers = headers;
        }
    
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            foreach (var header in _headers)
            {
                request.Headers.Add(header);
            }
    
            return null;
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
        }
    }
    

    AddHttpHeaderMessageEndpointBehavior

    Create a specific EndpointBehavior which uses the HttpHeaderMessageInspector.

    public class AddHttpHeaderMessageEndpointBehavior : IEndpointBehavior
    {
        private readonly IClientMessageInspector _httpHeaderMessageInspector;
    
        public AddHttpHeaderMessageEndpointBehavior(params MessageHeader[] headers)
        {
            _httpHeaderMessageInspector = new HttpHeaderMessageInspector(headers);
        }
    
        public void Validate(ServiceEndpoint endpoint)
        {
        }
    
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(_httpHeaderMessageInspector);
        }
    }
    

    Client

    Create a Client which adds a custom EndpointBehavior to insert the MessageHeader in the Soap message.

    private MyTestPortTypeClient CreateAuthenticatedClient()
    {
        var client = new MyTestPortTypeClient(_settings.EndpointConfigurationName, _settings.EndpointAddress);
        client.Endpoint.EndpointBehaviors.Clear();
    
        var authHeader = new AuthHeader(_settings.UserName, _settings.Password);
        client.Endpoint.EndpointBehaviors.Add(new AddHttpHeaderMessageEndpointBehavior(authHeader));
    
        return client;
    }