On this page

Skip to content

Calling WebService using HttpClient

TLDR

  • In environments where Web References cannot be added, you can manually call a WebService via HttpClient using the SOAP message format.
  • For .NET Core or environments where System.Web.Services is unavailable, it is recommended to use XmlSerializer for object-to-XML serialization and deserialization to handle complex types.
  • By sending XML content compliant with the SOAP 1.2 specification via HttpClient.PostAsync and parsing the returned XDocument, you can achieve dynamic calls.

Solving the Problem of Being Unable to Add Web References

When does this issue occur: When the development environment cannot directly add Web References (WSDL) via Visual Studio due to network or architectural restrictions.

In the .NET Framework environment, the traditional approach is to use WebClient to read the WSDL and dynamically compile service code via ServiceDescriptionImporter and Reflection. However, since .NET Core, this method is no longer applicable due to the lack of the System.Web.Services library. In this case, the most stable alternative is to treat the WebService as an HTTP endpoint and send a SOAP-compliant XML request directly via HttpClient.

Implementing SOAP Calls using HttpClient

When does this issue occur: When you need to call a WebService but cannot use automatically generated proxy classes, and the transmitted data contains complex objects.

To handle complex object types, we can use XmlSerializer to convert C# objects into XML format and encapsulate them within a SOAP Envelope. The following is the recommended approach for implementing a utility class:

csharp
public static class WebServiceUtils {
    private static readonly HttpClient httpClient = new HttpClient();

    public static async Task<TResponse> ExecuteAsync<TResponse>(string uri, string method, IDictionary<string, string> arguments, string @namespace = "http://tempuri.org/") {
        XmlSerializerNamespaces serializerNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
        XmlWriterSettings settings = new XmlWriterSettings {
            Indent = true,
            OmitXmlDeclaration = true
        };

        string argsXml = string.Join("", arguments.Select(x => {
            Type type = x.Value.GetType();
            XmlSerializer _serializer = new XmlSerializer(type);
            StringBuilder sb = new StringBuilder();
            using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
                _serializer.Serialize(writer, x.Value, serializerNamespaces);
                // Use Regex to ensure XML tag names match the parameter names expected by the WebService
                return Regex.Replace(sb.ToString(), $@"((?<=^<)(\w*)(?=>))|(?<=</)\w*(?=>$)", x.Key);
            }
        }));

        string soapXml = $@"
            <soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
              <soap12:Body>
                <{method} xmlns=""{@namespace}"">
                    {argsXml}
                </{method}>
              </soap12:Body>
            </soap12:Envelope>
        ";

        StringContent content = new StringContent(soapXml, Encoding.UTF8, "text/xml");
        using (HttpResponseMessage message = await httpClient.PostAsync(uri, content).ConfigureAwait(false)) {
            if (!message.IsSuccessStatusCode) {
                throw new HttpRequestException($"HTTP request failed with status code {message.StatusCode}: {message.ReasonPhrase}");
            }

            string result = await message.Content.ReadAsStringAsync().ConfigureAwait(false);

            XDocument xdoc = XDocument.Parse(result);
            XNamespace ns = @namespace;
            string resultTag = method + "Result";

            XElement xelement = xdoc.Descendants(ns + resultTag).Single();

            XmlSerializer serializer = new XmlSerializer(typeof(TResponse), new XmlRootAttribute(resultTag) { Namespace = @namespace });

            using (XmlReader reader = xelement.CreateReader()) {
                return (TResponse)serializer.Deserialize(reader);
            }
        }
    }
}

Verifying Results

Using the method above, even Request and Response objects containing nested structures can be correctly serialized and passed to the WebService. In the debugger, you can confirm:

  • Request parameters are correctly mapped to the parameter names of the WebService method.
  • The returned XML has been successfully deserialized into the specified TResponse type.

![webservice received request](../../../backend/images/使用 HttpClient 呼叫 WebService/webservice-received-request.png) ![client received response](../../../backend/images/使用 HttpClient 呼叫 WebService/client-received-response.png)

Change Log

  • 2023-02-13 Initial version created.