I regularly consume REST services from code running in Web Roles and Worker Roles. The best practice when it comes to anything going over the network, is to use the The Transient Fault Handling Application Block. The RestClient from this post is a Fluent REST Client that uses the HttpTransientErrorDetectionStrategy and a RetryPolicy to be tolerant of transient faults. Find out more about the HttpTransientErrorDetectionStrategy in an earlier post.
Take some time to get to know the characteristics of good REST services. They are part of an established convention that help us to rapidly understand and consume new REST services.
The code from this Post is part of the Brisebois.WindowsAzure NuGet Package
To install Brisebois.WindowsAzure, run the following command in the Package Manager Console
PM> Install-Package Brisebois.WindowsAzure
Calling HTTP GET on a REST Endpoint
Making a HTTP GET request is done by specifying a Uri. Then by calling the GetAsync method with a lambda for the onError parameter. This lambda will be called when exceptions occur during the requests to the endpoint. The request gets initiated when the GetAsync method is called.
The code below defaults to 10 retries if transient Http Status Codes are returned by the endpoint.
Action<Uri, HttpStatusCode, string> onError = (uri, code, message) => { Console.WriteLine(code.ToString()); }; var result = await RestClient.Uri("http://localhost:81/testWebApi/api/values") .GetAsync(onError);
The onError parameter of the GetAsync method can also be a private method. This is where you can react to specific Http Status Codes. For example, if the REST service returns a 401 Unauthorized, you can log the active user off of your application.
private void OnError(Uri uri, HttpStatusCode statusCode, string content) { switch (statusCode) { case HttpStatusCode.NotFound: { // TODO: do something } break; case HttpStatusCode.Unauthorized: { // TODO: log user out } break; default: { var message = string.Format("{1}{0}{0}{2}", Environment.NewLine, uri, content); throw new RestClientException(statusCode, message); } } }
The RestClientException above is a great way to propagate RestClient related exceptions. In most cases, the most important part of an error response from a REST service is the Http Status Code. You can use this exception to communicate the code in order to show the right error messages to your end users.
Calling HTTP GET on a REST Endpoint With Tracing
Activating the trace on requests for logging or debug purposes can be done by passing a Progress<string> instance to the GetAsync method. An example of a trace can be found below the following code.
var progress = new Progress<string>(Console.WriteLine); Action<Uri, HttpStatusCode, string> onError = (uri, code, message) => { Console.WriteLine(code.ToString()); }; var result = await RestClient.Uri("http://localhost:81/testWebApi/api/values") .GetAsync(onError, progress);
Trace
Considering Http Status Code 404 Not Found as Transient
If you are trying to call a REST service hosted on Windows Azure, it can be very convenient to consider a 404 Http Status Code to be transient. Roles can become unavailable for a number of reasons and this can happen at any time. But in most circumstances, this is temporary and having code that is able to retry calling the REST services goes a long way in dealing with these unplanned interruptions.
var progress = new Progress<string>(Console.WriteLine); Action<Uri, HttpStatusCode, string> onError = (uri, code, message) => { //Do nothing }; const bool notFoundIsTransient = true; var result = await RestClient.Uri("http://localhost:81/testWebApi/api/404") .Retry(3, notFoundIsTransient) .GetAsync(onError, progress);
Trace
This trace shows what happens when the 404 Not Found Http Status Code is considered to be transient. The code will retry until the it reaches the requested number of attempts. Then it will throws the exception so that it can be handled accordingly.
Calling HTTP POST on a REST Endpoint With Tracing
Making a HTTP POST request is done by specifying a Uri. Build the QueryString by adding Parameters, then set the Content and it’s Content Type. Call the PostAsync method with a lambda for the onError parameter. This lambda will be called when exceptions occur during the requests to the endpoint. The request gets initiated when the PostAsync method is called.
var r = new Resource { Name = "Alexandre Brisebois", Values = new Dictionary<string, string> { {"specialty", "Windows Azure"} } }; var progress = new Progress<string>(Console.WriteLine); var resul = await RestClient.Uri("http://localhost:81/testWebApi/api/values") .Retry(2, true) .Parameter("_token", Guid.NewGuid().ToString()) .Content(JsonConvert.SerializeObject(r)) .ContentType("application/json; charset=utf-8") .PostAsync(OnError, progress);
Trace
Calling HTTP PUT on a REST Endpoint With Tracing
Making a HTTP PUT request is done by specifying a Uri. Build the QueryString by adding Parameters, then set the Content and it’s Content Type. Call the PutAsync method with a lambda for the onError parameter. This lambda will be called when exceptions occur during the requests to the endpoint. The request gets initiated when the PutAsync method is called.
var r = new Resource { Name = "Alexandre Brisebois", Values = new Dictionary<string, string> { {"specialty", "Windows Azure"} } }; var progress = new Progress<string>(Console.WriteLine); var result = await RestClient.Uri("http://localhost:81/testWebApi/api/values") .Retry(2, true) .Content(JsonConvert.SerializeObject(r)) .ContentType("application/json; charset=utf-8") .PutAsync(OnError, progress);
Trace
Calling HTTP DELETE on a REST Endpoint With Tracing
Making a HTTP DELETE request is done by specifying a Uri. Call the DeleteAsync method with a lambda for the onError parameter. This lambda will be called when exceptions occur during the requests to the endpoint. The request gets initiated when the DeleteAsync method is called.
var progress = new Progress<string>(Console.WriteLine); var result = await RestClient.Uri("http://localhost:81/testWebApi/api/values") .Retry(2, true) .DeleteAsync(OnError, progress);