Exceptional Exception Handling

Often times developers see exceptions as negative things, but what if they weren’t? What if we could take the exceptions our code throws at us and use them to our advantage? To make the application more verbose, logging more clear and error codes and response messages more meaningful? After all an exception is just your codes way of telling you it’s sick and you are the code doctor! So Dr. Code, let’s go through a few examples that may just change the way you feel about exceptions.

You can find this example code in my SubscriptionServiceApi GitHub repository.

In this example let’s say we work for a streaming service like Netflix or Spotify and we are tasked with creating a REST API for other internal applications to manage our customers subscriptions.

Getting Started

First let’s create the subscription model we will work with:

public class UserSubscription
{
    public string AccountNumber { get; set; }
    public bool IsActive { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
    public string SubscriptionType { get; set; }
    public string PaymentMethod { get; set; }
    public DateTime ExpirationDate { get; set; }
    public DateTime CreateDate { get; set; }
    public DateTime LastRenewedDate { get; set; }
}

Okay now that we have our model, let’s create the controller for the consuming systems to interact with:

[Route("[controller]")]
public class UserSubscriptionController : Controller
{
    [HttpPost]
    public IActionResult Post([FromBody] UserSubscription subscription)
    {
        // call some code to create the new subscription in our database

        return new OkResult();
    }
}

Now for Some Validation

Okay now that our API is up and running lets take a look at some of the business requirements we have been provided. We see that when a new subscription is created via our POST endpoint if the SubscriptionType property has a value of “Paid” then the PaymentMethod property must have a value. So we decide to take these rules and create a Validator class that will extend the UserSubscription class to throw exceptions if these rules are violated:

public static class UserSubscriptionValidator
{
    public static void Validate(this UserSubscription subscription)
    {
        if (subscription.SubscriptionType == "Paid" &&
               string.IsNullOrWhiteSpace(subscription.PaymentMethod))
        {
            throw new Exception(
               "A payment method must be provided when subscription type is Paid");
        }
    }
}

Now in our controller we can implement that Validate method in our POST endpoint:

[HttpPost]
public IActionResult Post([FromBody] UserSubscription subscription)
{
    subscription.Validate();

    // call some code to create the new subscription in our database

    return new OkResult();
}

Now if we send the following request to our POST endpoint it will throw an exception:

{
    "AccountNumber": "123456789",
    "FirstName": "John",
    "LastName": "Doe",
    "EmailAddress": "JohnDoe@gmail.com",
    "SubscriptionType": "Paid"
}

This is going to return a 500 status code to our consumer with a nice long stack trace error in the response body:

Result when sending an invalid SubscriptionType/PaymentMethod combination

This is kind of useful when you are using a tool like postman to hit these endpoints manually but for a consuming application writing code to handle that error would be sloppy. Also, investigating any logs that may record that exception becomes riddled with extra information you do not need to diagnose the reason the exception was thrown.

The First Exception

So how do we make this response more verbose? How do we make this exception more meaningful when we log it? Let’s create a custom exception that we can use in our code to gain some inches:

public class UserSubscriptionValidationException : Exception
{
    public UserSubscriptionValidationException() { }
    public UserSubscriptionValidationException(string message) : base(message) { }
    public UserSubscriptionValidationException(string message, Exception innerException) : base(message, innerException) { }
}

Now we can implement it in our validation class like this:

These highly effective and reliable medicines are easily available at any authorized medical store; you can also order Kamagra tablets online through a registered online pharmacy, but only after purchase generic cialis http://appalachianmagazine.com/2019/04/18/the-crucifixion-legend-of-the-dogwood-tree/ prescription.
public static class UserSubscriptionValidator
{
    public static void Validate(this UserSubscription subscription)
    {
        if (subscription.SubscriptionType.Equals("Paid", StringComparison.OrdinalIgnoreCase) &&
            string.IsNullOrWhiteSpace(subscription.PaymentMethod))
        {
            throw new UserSubscriptionValidationException("A payment method must be provided when subscription type is Paid");
        }
    }
}

Now when we send that same bad payload we still get 500 response code but our exception name is a little more descriptive:

Result when sending an invalid SubscriptionType/PaymentMethod combination

Let’s Gain Some Inches

So we didn’t gain much by just throwing our own custom exception alone so let’s add some try/catch logic in our controllers POST endpoint:

[HttpPost]
public IActionResult Post([FromBody] UserSubscription subscription)
{
    try
    {
        subscription.Validate();

        // call some code to create the new subscription in our database

        return new OkResult();
    }
    catch (UserSubscriptionValidationException usvx)
    {
        return new BadRequestObjectResult(usvx.Message);
    }
}

Now since we have a specific catch block for our custom exception we can return a BadRequestObjectResult with the exception message inside that block. By doing this it not only simplifies the response we send back to the consuming system but it also returns a 400 response code which indicates to the consuming system that they sent us a bad request:

Result when sending an invalid SubscriptionType/PaymentMethod combination

Now that is much better for our consumers and much easier for us to debug our logs. But let’s say we have a bunch of different custom exceptions we have created that we want to handle as a bad request. Do we create a catch block in our controller for each type of exception we created? While we could do that our code can get really messy very fast with that approach. This is where my good friend inheritance comes in.

Gaining Some Inches Later On

So now we get some new requirements from the boss. He tells us that when a new subscription is created the FirstName, LastName and EmailAddress properties are all required fields. Let’s create some new exceptions for these different scenarios that will inherit our UserSubscriptionValidationException so we can reuse the same catch block in our controller:

public class SubscriptionPaymentException : UserSubscriptionValidationException
{
    private const string ErrorCode = "Invalid subscription type/payment combination.";

    public SubscriptionPaymentException() : base(ErrorCode) { }
}

public class RequiredPersonalInfoMissingException : UserSubscriptionValidationException
{
    private const string ErrorCode = "Subscription must contain FirstName, LastName and Email.";

    public RequiredPersonalInfoMissingException() : base(ErrorCode) { }
}

Now let’s update our validation class to handle the new requirements we have been given:

public static class UserSubscriptionValidator
{
    public static void Validate(this UserSubscription subscription)
    {
        if (subscription.SubscriptionType == "Paid" &&
            string.IsNullOrWhiteSpace(subscription.PaymentMethod))
        {
            throw new SubscriptionPaymentException();
        }

        if (string.IsNullOrWhiteSpace(subscription.FirstName) ||
               string.IsNullOrWhiteSpace(subscription.LastName) ||
               string.IsNullOrWhiteSpace(subscription.EmailAddress))
        {
            throw new RequiredPersonalInfoMissingException();
        }
    }
}

So here we updated our Validate method to first check the SubscriptionType and PaymentMethod combination and throw the new SubscriptionPaymentException if the combination is invalid. Then we added another if statement to validate that FirstName, LastName and EmailAddress are all provided in the incoming request and, if those requirement are not met throw a RequiredPersonalInfoMissingException. Let’s try sending some invalid responses and see what happens:

Result when sending an invalid SubscriptionType/PaymentMethod combination
Result when sending a payload with a null EmailAddress property

Notice they both returned a 400 response code but different response messages but we never changed any of the code in our controller. Since both of these new custom exceptions we created inherit the UserSubscriptionValidationException that the same catch block will handle both of the new custom exceptions when they are thrown. This helps us expand our exception handling for any future changes that we would need to return a what we deem a request to our client.

So what should we take away here?

  • Exceptions are not always a bad thing.
  • Exceptions are your codes way of telling you that it is sick and needs you, Dr. Code, to make it better.
  • You can create your own custom exception classes to handle specific scenarios throughout your code by inheriting the Exception class.
  • Throwing custom exceptions can give consuming systems better exception handling in their code.
  • Debugging your logs can be much easier when your exceptions have specific names and messages.
  • You can inherit custom exceptions you create from other custom exceptions and use a single catch block to handle them to help keep your code simpler and cleaner.
  • Inheriting exceptions can help future proof your applications for new scenarios where you may need to throw different exceptions but need them to be handled in the same way.

Leave a Reply

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