Raw HTTP Interactions from C#

30. August 2010

I had a conversation with a friend last week who was messing around with a non-SOAP based HTTP service and was fighting with the C# necessary for rudimentary interactions. The problem was compounded by the fact that he needed to associate a certificate with the request to authenticate properly to the server.

I had recently been doing this exact thing based on some work with the Azure management API so I promised him some samples. As I was assembling them this morning, I decided to drop them here in case they could be beneficial to others.

The first sample shows a simple call wherein I build some XML and send it along with the request. In this case we are creating a deployment in Windows Azure. We then grab a value from the response header collection for use in the second call that has a simple request format but returns an XML blob in the body that I then parse to get the results I need.

Both requests are signed with an X509 client certificate – you’ll notice it referred to as “managementCertificate” – this is a variable passed in that was generated using the following code:

var managementCertificate = new X509Certificate2(manifest.CertificateFile);

Where manifest.CertificateFile is the path to the pem file on my local machine.

In the sample below, you’ll see the target URL built, some base64 encoding of some of the parameters (included just for completeness but just a requirement of the service I was calling). I then use a StringBuilder to build up an XML block and then setup the request with the certificate, xml blob, and other properties set. Finally, you’ll see the submission and then pulling a value from the headers collection to be sent back to the caller.

// Build uri string
// format:https://management.core.windows.net/<subscription-id>/services/
//                hostedservices/<service-name>/deploymentslots/
//                <deployment-slot-name>
var url = string.Format(
    "{0}{1}/services/hostedservices/{2}/deploymentslots/{3}",
    Constants.AzureManagementUrlBase,
    subscriptionId,
    serviceName,
    deploymentSlot);

// Base64 encode configuration label and file
var base64label = EncodeAsciiStringTo64(configurationLabel);
var base64config = GetSettings(
    instanceCount,
    accountName,
    accountKey,
    queueSleepTime,
    maxJobLength,
    container,
    queueName);

// build request body
StringBuilder blob = new StringBuilder();
blob.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
blob.Append("<CreateDeployment " +
    "xmlns=\"http://schemas.microsoft.com/windowsazure\">\n");
blob.AppendFormat("\t<Name>{0}</Name>\n", deploymentName);
blob.AppendFormat("\t<PackageUrl>{0}</PackageUrl>\n", packageUrl);
blob.AppendFormat("\t<Label>{0}</Label>\n", base64label);
blob.AppendFormat("\t<Configuration>{0}</Configuration>\n", base64config);
blob.Append("</CreateDeployment>\n");

// encode request body then put it in a byte array
byte[] byteArray = Encoding.UTF8.GetBytes(blob.ToString());

// make request
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);

// header info
request.Method = "POST";
request.ClientCertificates.Add(managementCertificate);
request.Headers.Add(Constants.VersionHeader, Constants.VersionTarget);
request.ContentType = Constants.ContentTypeXml;
request.ContentLength = byteArray.Length;

Stream dataStream = request.GetRequestStream();

// write the data to the request stream.
dataStream.Write(byteArray, 0, byteArray.Length);

// Get the response.
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

// Get the x-ms-requestID
string requestID = response.GetResponseHeader(Constants.RequestIdHeader);

// Clean up the streams
dataStream.Close();
response.Close();

return requestID;

In this next sample, we make a rather simple request but do more with the result in parsing the returned XML blob which is fairly trivial although it does have custom namespaces which have to be accounted for when you crawl the XML tree.

// Build uri string
// format:https://management.core.windows.net/<subscription-id>/services
//          /hostedservices/<service-name>/deploymentslots
//          /<deployment-name/
var url = string.Format(
    "{0}{1}/services/hostedservices/{2}/deploymentslots/{3}",
    Constants.AzureManagementUrlBase,
    subscriptionId,
    serviceName,
    deploymentSlot.ToString());

// make uri request using created uri string
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

// make header, method, and certificated requests
request.Method = "GET";
request.ClientCertificates.Add(managementCertificate);
request.Headers.Add(Constants.VersionHeader, Constants.VersionTarget);
request.ContentType = Constants.ContentTypeXml;

// Get the response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

// put response into string text
StreamReader dataStream = new StreamReader(response.GetResponseStream());
string text = dataStream.ReadToEnd();

// create an xml document
XmlDocument xml = new XmlDocument();

// load up the response text as xml
xml.LoadXml(text);

// get the NS manager
XmlNamespaceManager ns = new XmlNamespaceManager(xml.NameTable);
ns.AddNamespace("az", Constants.AzureXmlNamespace);

// return the status
DeploymentStatus currentStatus;
var statusText = xml.SelectSingleNode("//az:Status", ns).InnerText;

if (Enum.TryParse<DeploymentStatus>(statusText, true, out currentStatus))
{
    FullDeploymentStatus fullStatus = new FullDeploymentStatus() 
        { MainStatus = currentStatus };

    // now try to get the status values for each instance
    XmlNodeList instances = xml.SelectNodes("//az:RoleInstance", ns);

    foreach (XmlNode instance in instances)
    {
        var instanceStatus = new InstanceDetails()
        {
            RoleName = 
                instance.SelectSingleNode("az:RoleName", ns).InnerText,
            InstanceName = 
                instance.SelectSingleNode("az:InstanceName", ns).InnerText,
            Status = (InstanceStatus)Enum.Parse(typeof(InstanceStatus),
                instance.SelectSingleNode("az:InstanceStatus", ns).InnerText)
        };

        fullStatus.Instances.Add(instanceStatus);
    }

    return fullStatus;
}
else
{
    throw new ArgumentOutOfRangeException("Status",
        "The status returned for the deployment is outside the range of " +
        "acceptable values");
}

That is about it. Hopefully this is helpful and give you more comfort interacting with HTTP-based services that aren’t simply a matter of pointing at a WSDL and having magic happen.

General Development ,

Windows Azure, Climate Data, and Microsoft Surface

18. December 2009

I’ve been working on moving a large collection data to, from, and around Azure as we are testing the data profile for scientific computing and large-scale experiment post-processing and, in order to verify the data we uploaded and processed turned out as we wanted tit to, I built a simple visualization app that does a real-time query against the data in Azure and displays it. Originally the app was built as a simple WPF desktop application, but I got to thinking that it would be particularly interesting on the Surface and therefore took a day or two to port it over. The video below is a walkthrough of the app – the dialog is a bit cheesy but the app is interesting as it provides a very tactile means of interacting with otherwise stale data.

Cloud Computing , , , , ,