Azure and the REST API

by ingvar 13. april 2011 21:09

Introduction

In this post I’ll write about the interesting things i discovered when doing REST to Azure. And I’ll also post the code needed to; create a hosted server, created a new deplyment and upgrade an existing deployment.

The three most important findings for me was how to get meaningfull error messages when the web request failed. The right way to use Uri for some REST operations and the right way to encode service configuration files in the body of the request.

Documentation for the Azure REST API can be found here: http://msdn.microsoft.com/en-us/library/ee460799.aspx

Certificate

You need a certificate to identify you REST commands to Auzre.

You can create a new certificate by issuing this command:

makecert -r -pe -a sha1 -n CN=AzureMgmt -ss My “AzureMgmt.cer"

The file 'AzureMgmt.ce' that was created by this command should be added to your Azure subscribtion. You can do this through the Azure Management Portal.

Generic setup

Here is a generic setup for issuing REST operations. 

Edit (2011-04-13): Setting the request method to "POST" should only be done if there is a body to send, thanks to Christian Horsdal for pointing this out.

string requestUrl = "";
string certificatePath = "";
string headerVersion = "";
string requestBody = "";

HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(new Uri(requestUrl, true));
httpWebRequest.ClientCertificates.Add(new X509Certificate2(certificatePath));
httpWebRequest.Headers.Add("x-ms-version", headerVersion);
httpWebRequest.ContentType = "application/xml";

if (!string.IsNullOrEmpty(requestBody))
{
   httpWebRequest.Method = "POST";
   byte[] requestBytes = Encoding.UTF8.GetBytes(requestBody);
   httpWebRequest.ContentLength = requestBytes.Length;

   using (Stream stream = httpWebRequest.GetRequestStream())
   {
      stream.Write(requestBytes, 0, requestBytes.Length);
   }
}

try
{
   using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
   {
      Console.WriteLine("Response status code: " + httpWebResponse.StatusCode);       WriteRespone(httpWebResponse);
   }
}
catch (WebException ex)
{
   Console.WriteLine("Response status code: " + ex.Status);
   WriteRespone(ex.Response);
}

Here is the code for the WriteRepsonse method:

static void WriteRespone(WebResponse webResponse)
{
   using (Stream responseStream = webResponse.GetResponseStream())
   {
      if (responseStream == null) return;

      using (StreamReader reader = new StreamReader(responseStream))
      {
         Console.WriteLine("Response output:");
         Console.WriteLine(reader.ReadToEnd());
      }
   }
}

The three variables; requestUrl, headerVersion and requestBody) are the only things you need to change to do any REST operation. 

The certificatePath variable is the same for all operations, it just need to be the path to your certificate file. 

The requestUrl variable is constructed to match the specific REST operation. See the the documentation for the different REST operations here [http://msdn.microsoft.com/en-us/library/ee460799.aspx].

The headerVersion varies from operation to operation. Some operations has the same version. You can find the correct version in the documentation.

The requestBody is a small XML document. Not all operations needs a body and this is handled by the generic code above. See the documentation [http://msdn.microsoft.com/en-us/library/ee460799.aspx] if the body is needed and if it is, how its constructed.

 

Important findings

Here is a short list of the problems I ran into when doing REST operations and the solutions to them:

  • WebException.Response.GetResponseStream() is your friend! When a operation fails with an exception and the error code 400, reading the WebExceptions response stream can help you finding the reason why.
  • The Uri constructor should have dontEscape=false for the request URL. If this is not the case, REST operations like Upgrade Deployment fails.
  • The ordering of the elements in the body matters, it is XML, so not so supprising.
  • If a Azure deployment package is needed, this should be located in a blob store. This is also documented in the documentation.
  • If an Azure service configuration is needed, this should be read as a string and base64 encoded like i did in the code. My first try i read the file as byte (File.ReadBytes) but this made the operation fail.

 

Create Hosted Service Example

Here is the value of the needed three variables:

string requestUrl = "https://management.core.windows.net/<subscription-id>/services/hostedservices";
string headerVersion = "2010-10-28";
string requestBody = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
   "<CreateHostedService xmlns=\"http://schemas.microsoft.com/windowsazure\">" +
      "<ServiceName>myservicename</ServiceName>" +
      "<Label>" + Convert.ToBase64String(Encoding.UTF8.GetBytes("myservicename")) + "</Label>" +
      "<Location>North Central US</Location>" +
   "</CreateHostedService>";

Create Deployment Example

Here is the value of the needed three variables:

string requestUrl = "https://management.core.windows.net/<subscription-id>/services/hostedservices/myservicename/deploymentslots/staging";
string headerVersion = "2009-10-01";
string requestBody = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
   "<CreateDeployment xmlns=\"http://schemas.microsoft.com/windowsazure\">" +
      "<Name>mydeployment</Name>" +
      "<PackageUrl>http://myblob.blob.core.windows.net/MyPackage.cspkg</PackageUrl>" +
      "<Label>" +
      Convert.ToBase64String(Encoding.UTF8.GetBytes("mydeployment")) +
      "</Label>" +
      "<Configuration>" +
      Convert.ToBase64String(Encoding.UTF8.GetBytes(
         File.ReadAllText(@"C:\MyServiceConfiguration.cscfg"))) +
      "</Configuration>" +
      "<StartDeployment>true</StartDeployment>" +
   "</CreateDeployment>";

Upgrade Deployment Example

Here is the value of the needed three variables:

string requestUrl = "https://management.core.windows.net/<subscription-id>/services/hostedservices/myservicename/deploymentslots/staging/?comp=upgrade";
string headerVersion = "2009-10-01";
string requestBody = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
   "<UpgradeDeployment xmlns=\"http://schemas.microsoft.com/windowsazure\">" +
      "<PackageUrl>http://myblob.blob.core.windows.net/MyPackage.cspkg</PackageUrl>" +
      "<Configuration>" +
      Convert.ToBase64String(Encoding.UTF8.GetBytes(
            File.ReadAllText(@"C:\MyServiceConfiguration.cscfg"))) +
      "</Configuration>" + "<Mode>auto</Mode>" +
      "<Label>"
      Convert.ToBase64String(Encoding.UTF8.GetBytes("mydeployment")) + 
      "</Label>" +
   "</UpgradeDeployment>";

Tags:

.NET | Azure | C#

Comments (3) -

Christian Horsdal
Christian Horsdal Denmark
14-04-2011 12:02:43 #

I n the first code snippet you set

httpWebRequest.Method = "POST";

is that always the case? -if so that's not exactly RESTful.

ingvar
ingvar Denmark
14-04-2011 12:25:56 #

I pretty sure its needed when there is a body (requestBod) to send. Just for the fun of it i tried with PUT and GET and got the following errors:
PUT: ProtocolError
GET: Cannot send a content-body with this verb-type.

But for operations without any body, POST will actually make it fail. Thx for the input Smile

Christian Horsdal
Christian Horsdal Denmark
14-04-2011 13:29:46 #

Right, and in your cases do actually want to post something up to Azure, so that makes sense in REST terms.

Pingbacks and trackbacks (1)+

About the author

Martin Ingvar Kofoed Jensen

Architect and Senior Developer at Composite on the open source project Composite C1 - C#/4.0, LINQ, Azure, Parallel and much more!

Follow me on Twitter

Read more about me here.

Read press and buzz about my work and me here.

Stack Overflow

Month List