Raw XML Web Service Calls in C#

This is a companion article to Reading XML with Namespaces using LINQ, which explains how to parse XML with namespaces. Both articles came about because I had to use a web service that had non-standard envelope headers, and after struggling with the configuration settings and the sparse Windows Communication Foundation (WCF) documentation for creating custom headers, I finally threw it in and called the service by brute force – assembling the HTTP and XML requests, calling the service, and parsing the returned XML response.

I want to stress that WCF is excellent for communicating with a web service that more or less uses SOAP standards. If that’s your situation, stop reading right now and use WCF. But if you run into trouble with non-standard services I think it’s easier to put WCF aside and work directly with the service. At the very least, it’s a lot more straightforward.

The WebServiceX Global Weather Web Service

This post will use the WebServiceX Global Weather Web Service at http://www.webservicex.com/globalweather.asmx. Append ?wsdl to the end of the address to see the WSDL. If you like doing things the hard way, you can interpret the WSDL by yourself to figure out the request and response XML. An easier way would be to use a utility such as soapUI. Even easier: this web service will show you the request and response XML.

Navigate to http://www.webservicex.com/globalweather.asmx. You’ll see the web service’s home page, with links to its two methods:

WebServiceX Global Weather Service Home

WebServiceX Global Weather Service Home

For this example, we’ll be calling the GetWeather method, so click on that method name. This will show the Request and Response XML for the service method.

The SOAP 1.1 Request

This service happens to support calls using SOAP 1.1, SOAP 1.2, HTTP GET, and HTTP POST. For this post I’ll focus on the SOAP 1.1 call only. Here’s the SOAP 1.1 Request XML (I’ve added a couple line breaks so it will display better):

POST /globalweather.asmx HTTP/1.1
Host: www.webservicex.com
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://www.webserviceX.NET/GetWeather"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetWeather xmlns="http://www.webserviceX.NET">
      <CityName>string</CityName>
      <CountryName>string</CountryName>
    </GetWeather>
  </soap:Body>
</soap:Envelope>

 
We’ll build this request in code, using Boston as the CityName and United States as the CountryName.

Building the SOAP 1.1 Request in Code

The request XML will be a string the same as the XML shown above, with three differences:

  1. For Content-Length: length, substitute the byte length of the XML for the word length.
  2. For <CityName>string</CityName>, substitute Boston for string.
  3. For <CountryName>string</CountryName>, substitute United States for string.

This program will call the web service and display the response XML to the console. It’s fully self-contained, so feel free to copy it and try it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

// Custom includes:
using System.Net;     // for HttpWebRequest, WebRequest
using System.IO;      // for Stream, StreamReader

namespace RawXMLExample {
    class WebServiceXGetWeather {

        static void Main(string[] args) {
            // Here are the HTTP headers we're trying to get.
            //
            // POST /globalweather.asmx HTTP/1.1
            //   ==> handled by setting the request.Method to "POST"
            // Host: www.webservicex.com
            //   ==> supplied by default
            // Content-Type: text/xml; charset=utf-8
            //   ==> handled by setting request.ContentType
            // Content-Length: length
            //   ==> handled by setting request.ContentLength
            // SOAPAction: "http://www.webserviceX.NET/GetWeather"
            //   ==> handled by adding a generic header

            // Create the request and set all headers except for
            // the content length.
            string pageName = "http://www.webservicex.com/globalweather.asmx";
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(pageName);
            req.Method = "POST";
            req.ContentType = "text/xml;charset=UTF-8";
            req.Headers.Add("SOAPAction", "\"http://www.webserviceX.NET/GetWeather\"");

            // The parameter values. In a real-world app these would of
            // course be gotten from user input or some other input.
            string cityName = "Boston";
            string countryName = "United States";

            // Now for the XML. Just build it by brute force.
            string xmlRequest = String.Concat(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>",
                "<soap:Envelope",
                "      xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"",
                "      xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"",
                "      xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">",
                "  <soap:Body>",
                "    <GetWeather xmlns=\"http://www.webserviceX.NET\">",
                "      <CityName>", cityName, "</CityName>",
                "      <CountryName>", countryName, "</CountryName>",
                "    </GetWeather>",
                "  </soap:Body>",
                "</soap:Envelope>");

            // Pull the XML request into a UTF-8 byte array for two
            // reasons:
            // 1. We need to set the content length to the byte length.
            // 2. The XML will be pushed into the request stream, which
            //    handles bytes, not characters.
            byte[] reqBytes = new UTF8Encoding().GetBytes(xmlRequest);

            // Now that the request is encoded to a byte array, we can
            // get its byte length. Set the remaining HTTP header value,
            // which is the content-length:
            req.ContentLength = reqBytes.Length;

            // Write the XML to the request stream.
            // Write the request content (the XML) to the request stream.
            try {
                using (Stream reqStream = req.GetRequestStream()) {
                    reqStream.Write(reqBytes, 0, reqBytes.Length);
                }
            } catch (Exception ex) {
                // GetRequestStream and Write can throw a variety of
                // exceptions; handling them is outside the scope of
                // this article. A "real" application should log and
                // (when possible) handle these exceptions.
                Console.WriteLine("Exception of type " + ex.GetType().Name);
                throw;
            }

            // At this point, the HTTP headers are set and the XML
            // content is set. It's time to call the service.
            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

            // We won't parse the response XML quite yet. Instead, just
            // log the raw XML received to show success.
            string xmlResponse = null;
            using (StreamReader sr = new StreamReader(resp.GetResponseStream())) {
                xmlResponse = sr.ReadToEnd();
            }
            Console.WriteLine(xmlResponse);

        }
    }
}

The SOAP Response

In the example above, all I do with the XML is display it to the console. Here’s the result I got, prettied up with line breaks:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <soap:Body>
  <GetWeatherResponse xmlns="http://www.webserviceX.NET">
   <GetWeatherResult>
    &lt;?xml version="1.0" encoding="utf-16"?&gt;
    &lt;CurrentWeather&gt;
    &lt;Location&gt;BOSTON LOGAN INTERNATIONAL, MA, United States (KBOS) 42-22N 071-01W 54M&lt;/Location&gt;
    &lt;Time&gt;Nov 30, 2013 - 03:54 PM EST / 2013.11.30 2054 UTC&lt;/Time&gt;
    &lt;Wind&gt; from the E (100 degrees) at 5 MPH (4 KT):0&lt;/Wind&gt;
    &lt;Visibility&gt; 10 mile(s):0&lt;/Visibility&gt;
    &lt;SkyConditions&gt; mostly cloudy&lt;/SkyConditions&gt;
    &lt;Temperature&gt; 30.9 F (-0.6 C)&lt;/Temperature&gt;
    &lt;Wind&gt;Windchill: 26 F (-3 C):1&lt;/Wind&gt;
    &lt;DewPoint&gt; 18.0 F (-7.8 C)&lt;/DewPoint&gt;
    &lt;RelativeHumidity&gt; 58%&lt;/RelativeHumidity&gt;
    &lt;Pressure&gt; 30.63 in. Hg (1037 hPa)&lt;/Pressure&gt;
    &lt;PressureTendency&gt; 0.07 inches (2.3 hPa) lower than three hours ago&lt;/PressureTendency&gt;
    &lt;Status&gt;Success&lt;/Status&gt;
    &lt;/CurrentWeather&gt;
   </GetWeatherResult>
  </GetWeatherResponse>
 </soap:Body>
</soap:Envelope>

 
Note that the meat of the response, in the GetWeatherResult element, is even more XML with the < and > tag delimiters encoded. To get the GetWeatherResult element you can pull the response into either an XDocument or XElement and parse. My blog post Reading XML with Namespaces using LINQ shows how to do this.

To parse out the XML within the GetWeatherResult element, I’d suggest either HTML Decoding it or just calling String.Replace to replace &lt; with < and &gt; with >. Then you can load the string into an XDocument or XElement and parse for the temperature, humidity, and other values.

2 thoughts on “Raw XML Web Service Calls in C#

  1. Zuhdi

    Hi Gibbs
    I use your code as a guideline for me to write a code for my web service (Thank you).
    But the error occur when try to request the web service.
    The error : “System.Net.WebException: The underlying connection was closed: The connection was closed unexpectedly.”
    Can you help me or give some idea how to solve the error ?
    Thank you.

    This is my code:
    public string TestWebServiceRawSOAPNik()
    {
    // Create the request and set all headers except for
    // the content length.

    System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(“http://gatewayx.com:1505”);

    request.Method = “POST”;
    request.ContentType = “application/soap+xml; charset=utf-8”;
    request.Headers.Add(“SOAPAction”, “\”http://gatewayx.com/aSub\””);

    string strSOAPRequestBody = String.Concat(
    “”,
    “”,
    “”,
    “”,
    “”,
    “abc”,
    “abc123”,
    “”,
    “”,
    “”,
    “”,
    “”,
    “”,
    “eD”,
    “DPT”,
    “Cvcf4+Pf6xtnrj7GCeDOdVC04+Tyc75hxxx”,
    “77777”,
    “1”,
    “MOD20130123163038567002”,
    “”,
    “0~xxx~yyy~zzz~MSL~MOD”,
    “”,
    “”,
    “”);

    WriteToLogFile(“SOAP Request Body ” + strSOAPRequestBody);

    // Pull the XML request into a UTF-8 byte array for two
    // reasons:
    // 1. We need to set the content length to the byte length.
    // 2. The XML will be pushed into the request stream, which
    // handles bytes, not characters.

    byte[] reqBytes = new UTF8Encoding().GetBytes(strSOAPRequestBody);

    // Now that the request is encoded to a byte array, we can
    // get its byte length. Set the remaining HTTP header value,
    // which is the content-length:

    request.ContentLength = reqBytes.Length;

    // Write the XML to the request stream.
    // Write the request content (the XML) to the request stream.
    try
    {
    using (Stream reqStream = request.GetRequestStream())
    {
    reqStream.Write(reqBytes, 0, reqBytes.Length);
    }
    }
    catch (Exception ex)
    {
    // GetRequestStream and Write can throw a variety of
    // exceptions; handling them is outside the scope of
    // this article. A “real” application should log and
    // (when possible) handle these exceptions.
    Console.WriteLine(“Exception of type ” + ex.GetType().Name);
    //WriteToLogFile(“Exception of type ” + ex.GetType().Name);
    throw;
    }

    // At this point, the HTTP headers are set and the XML
    // content is set. It’s time to call the service.
    HttpWebResponse resp = (HttpWebResponse)request.GetResponse();

    // We won’t parse the response XML quite yet. Instead, just
    // log the raw XML received to show success.
    string xmlResponse = null;
    using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
    {
    xmlResponse = sr.ReadToEnd();
    }
    Console.WriteLine(xmlResponse);
    //WriteToLogFile(“Response” + xmlResponse);
    return xmlResponse;

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *