Last week I needed to make a SOAP service call at work. The customer provided us WSDL definitions. I was supposed to add the service references in Visual Studio and make requests from UWP app. After adding the service references, I noticed that the service proxy didn’t include header objects. So I created custom MessageHeader class and appended it to the SOAP request message. I didn’t have access to the server-side. So this post is only about making SOAP requests on client-side.
In these WSDL’s, body objects were defined. The problem was that I was not able to set header nodes because they were not defined. I added them to request message by extending MessageHeader class. I also needed to add XML namespace to the header tag with a custom prefix. I also covered it in the following example.
To add custom headers, we need to create a class which extends MessageHeader and override some fields in it. Here I will add 2 different header tags. One with custom XML namespace prefix and the other one without. The example will go like following:
public class CustomHeader : MessageHeader
{
private const string HeaderName = "p:someTag";
private const string HeaderNamespace = "";
public override string Name => HeaderName;
public override string Namespace => HeaderNamespace;
public string headerString;
public CustomHeader()
{
this.headerString = @"<innerTag>content</innerTag>";
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteXmlnsAttribute("p", "http://www.name.space.url/");
var r = XmlReader.Create(new StringReader(headerString));
r.MoveToContent();
writer.WriteNode(r, false);
}
}
public class SecurityHeader : MessageHeader
{
private const string HeaderName = "Security";
private const string HeaderNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
public override string Name => HeaderName;
public override string Namespace => HeaderNamespace;
public string headerString;
public SecurityHeader(string username, string password)
{
this.headerString = @"<UsernameToken xmlns=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<Username>" + username + @"</Username>
<Password>" + password + @"</Password> </UsernameToken>";
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
var r = XmlReader.Create(new StringReader(headerString));
r.MoveToContent();
writer.WriteNode(r, false);
}
}
Before calling the web service, we can use our custom header classes like this:
var binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
var svc = new ServiceClient(binding, new EndpointAddress("https://service.endpoint.url"));
try
{
using (var scope = new OperationContextScope(svc.InnerChannel))
{
var cus = new CustomHeader();
OperationContext.Current.OutgoingMessageHeaders.Add(cus);
var sec = new SecurityHeader("USER", "PASS");
OperationContext.Current.OutgoingMessageHeaders.Add(sec);
return await svc.retrieveResponseAsync(req); ;
}
}
catch (Exception ex)
{
// log error
return null;
}
finally
{
await svc.CloseAsync();
}
At the end, the SOAP request we send will look like this:
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<p:someTag xmlns:p="http://www.name.space.url/">
<innerTag>content</innerTag>
</p:someTag>
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<UsernameToken>
<Username>USER</Username>
<Password>PASS</Password>
</UsernameToken>
</Security>
</s:Header>
<s:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- body content -->
</s:Body>
</s:Envelope>