Consuming a SOAP service using HttpClient

Home / Consuming a SOAP service using HttpClient

Some of Microsoft’s built-in code generation/tooling is really janky. One such example is the code generator that will produce service references and proxy classes from a SOAP WSDL definition. I’ve never liked this particular feature of Visual Studio. The service classes themselves don’t play nicely with injection, behave strangely with instantiation, scoping, singleton patterns, and are generally so .NET 1.1…

How does one call a SOAP service with the HttpClient in newer versions of .NET, then?

Typically, a SOAP POST request posts “text/xml” with Envelope and Body nodes. Namespace attributes are also specified. It’s actually quite easy to build up the Xml with LinqToXml. In my case, I’m calling a specific endpoint to get a purchase order. The method takes some credentials and a few other identifiers. We can create an XDocument directly with something like this:

XNamespace ns = "";
XNamespace myns = "";

XNamespace xsi = "";
XNamespace xsd = "";

XDocument soapRequest = new XDocument(
    new XDeclaration("1.0", "UTF-8", "no"),
    new XElement(ns + "Envelope",
        new XAttribute(XNamespace.Xmlns + "xsi", xsi),
        new XAttribute(XNamespace.Xmlns + "xsd", xsd),
        new XAttribute(XNamespace.Xmlns + "soap", ns),
        new XElement(ns + "Body",
            new XElement(myns + "GetStuff",
                new XElement(myns + "client",
                    new XElement(myns + "Username", _apiUsername),
                    new XElement(myns + "Password", _apiPassword)),
                new XElement(myns + "ReferenceNumber", referenceNumber)

The only other thing specific to a SOAP service is that the SOAP endpoint generally expects a “SOAPAction” header indicating the action that you’re calling. Wrapping the request content with HttpClient request looks like this:

    using (var client = new HttpClient(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip }) { Timeout = _timeout })
        var request = new HttpRequestMessage()
            RequestUri = new Uri(_apiUrl),
            Method = HttpMethod.Post

        request.Content = new StringContent(soapRequest.ToString(), Encoding.UTF8, "text/xml");

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
        request.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
        request.Headers.Add("SOAPAction", "");

        HttpResponseMessage response = client.SendAsync(request).Result;

        if (!response.IsSuccessStatusCode)
            throw new Exception();

        Task<Stream> streamTask = response.Content.ReadAsStreamAsync();
        Stream stream = streamTask.Result;
        var sr = new StreamReader(stream);
        var soapResponse = XDocument.Load(sr);

        var xml = soapResponse.Descendants(myns + "GetStuffResult").FirstOrDefault().ToString();
        var purchaseOrderResult = StaticMethods.Deserialize<GetStuffResult>(xml);
catch (AggregateException ex)
    if (ex.InnerException is TaskCanceledException)
        throw ex.InnerException;
        throw ex;
catch (Exception ex)
    throw ex;

You’ll notice that we use Link to Xml to grab the element “GetStuff” that is within the soap body. This allows us to pass the Xml from that specific element to the static method that deserializes the response to my own object. This method uses the XmlSerializer:

public static T Deserialize<T>(string xmlStr)
    var serializer = new XmlSerializer(typeof(T));
    T result;
    using (TextReader reader = new StringReader(xmlStr))
        result = (T)serializer.Deserialize(reader);
    return result;

For the XmlSerializer to function properly, we only need to then create objects using Xml attributes indicating how elements or attributes are mapped to properties. My particular response looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="" xmlns:xsi="" xmlns:xsd="">
	   <GetStuffResult xmlns="">
				<ProductInformation />
				<ProductInformation />

What we really want to do is parse the “GetStuffResult” element into an object. That object will have a PurchaseOrder member and a member that is a list of ProductInformation. Our object then, to properly deserialize only needs to be properly decorated:

[XmlRoot(ElementName = "GetStuffResult", Namespace = "")]
public class GetPurchaseOrderResult
    public PurchaseOrder PurchaseOrder { get; set; }

    public List<ProductInformation> Products { get; set; }

The PurchaseOrder and ProductInformation classes will also have Xml attribute decorations to properly map element/attribute values to properties. I won’t bore you with that, though. I like this approach because now I have full control over the Http requests/flow and none of the rigidity and strange behavior I have witnessed in the past with Visual Studio’s service reference proxy generator. I’m also not dealing with a set of proxy classes. All of the models are my own. Another consideration is that the older service proxy generators may not even work with .NET Core. This approach allows me to follow the other patterns I have mentioned for consuming other API’s.

Leave a Reply