Handling Exceptions

When using the WebApiClient, you don't need to worry about handling request and response messages because WebApiClient does this work for you. So, looking from the service consumer perspective, if a call to the api works, it returns whatever is necessary. If it doesn't, an exception is thrown.

The WebApiException Class

All exceptions are encapsulated in a object instance of WebApiException class. It has the following properties:

  • StatusCode: One of the available HttpStatusCode
  • Message: Description of what happend
  • Details: An object that describes what happend in more detail
    • Reason: The reason why the http request didn't complete with success
    • StackTrace: The exception stack trace
    • ModelState: The ModelState containing the model validation errors
    • ExceptionType: The type of the exception that was thrown

When the WebApi handles the exceptions that may occur and returns a response, the Details object doesn't have much information to help you realize what has happend. On the other side, if the WebApi throws an unhandled exception, this object has much more details that may help you.

Let's see some examples. If your service throws an HttpResponseException reporting that the id was not found, then you can understand better what has happend:

[Test]
public async Task TestIdNotFound()
{
	//arrange
	int id = 9999;

	try
	{
		WebApiClientOptions options = new WebApiClientOptions("http://localhost/testapi", "restaurants");
		
		using(WebApiClient<Restaurant> client = new WebApiClient<Restaurant>(options))
		{
			//act
			var rest = await client.GetOneAsync(id);
		}

		Assert.Fail("WebApiException expected");
	}
	catch (WebApiClientException e)
	{
        Assert.AreEqual(HttpStatusCode.NotFound, e.StatusCode);
        Assert.IsNotNull(e.Details);
        Assert.AreEqual("Invalid Id", e.Details.Reason);
        Assert.AreEqual(string.Format("The id {0} was not found in the database", id), e.Details.Message);
    }
}

Instead of throwing an exception, if your service just return a NotFoundResult, then you have less information:

[Test]
public async Task TestIdNotFound()
{
	//arrange
	int id = 9999;

	try
	{
		WebApiClientOptions options = new WebApiClientOptions("http://localhost/testapi", "restaurants");
		
		using(WebApiClient<Restaurant> client = new WebApiClient<Restaurant>(options))
		{
			//act
			var rest = await client.GetOneAsync(id);
		}

		Assert.Fail("WebApiException expected");
	}
	catch (WebApiClientException e)
	{
        Assert.AreEqual(HttpStatusCode.NotFound, e.StatusCode);
    }
}

Now, what if you try to create an object that has an invalid model state?

[Test]
public async Task TestCreateWithInvalidModelState()
{
	//arrange
	Restaurant rest = new Restaurant();
	rest.Name = "invalid name".PadRight(70); //suppose Name has MaxLength(50)
	rest.CountryId = 1;
	rest.Address = "1234, Road Street";

	try
	{
		WebApiClientOptions options = new WebApiClientOptions("http://localhost/testapi", "restaurants");
		
		using(WebApiClient<Restaurant> client = new WebApiClient<Restaurant>(options))
		{
			//act
			var ret = await client.CreateAsync(rest);

			Assert.Fail("WebApiException expected");
		}
	}
	catch (WebApiClientException e)
	{
		Assert.AreEqual(HttpStatusCode.BadRequest, e.StatusCode);
        Assert.IsNotEmpty(e.Details.ModelState);

        foreach (var item in e.Details.ModelState)
        {
            foreach (var error in item.Value)
            {
                Console.WriteLine(string.Format("Parameter: {0} - Error: {1}", item.Key, error));
            }
        }
    }
}

When a timeout occurs, StatusCode equals to RequestTimeout:

[Test]
public async Task TestTimeout()
{
	//arrange
	int id = 1;

	try
	{
		WebApiClientOptions options = new WebApiClientOptions("http://localhost/testapi", "restaurants") { Timeout = 100 };
		
		using(WebApiClient<Restaurant> client = new WebApiClient<Restaurant>(options))
		{
			//act
			var rest = await client.GetOneAsync(id);
		}

		Assert.Fail("WebApiException expected");
	}
	catch (WebApiClientException e)
	{
		Assert.AreEqual(HttpStatusCode.RequestTimeout, e.StatusCode);
	}
}
Last but not least, if any other type of unhandled exception is thrown in the middle layer, e.g. problems when communicating with the WebApi, a WebApiException is thrown with StatusCode equals to InternalServerError and the InnerException property fullfilled.