Avoiding Exceptions in ASP.NET Core

Sam Ferree - Aug 13 '18 - - Dev Community

Prerequisites

To get the most out of this post, it's best if you are familiar with Object Oriented Programming, C# (or other C family language) and the basics of ASP.NET Core and MVC architecture.

If you're unfamiliar with any of these topics, but want to learn more here are some fantastic pluralsight courses:

If you don't have a pluralsight membership, they offer a 10-day free trial. If you want my two-cents: I've tried Treehouse, Udemy and others and Pluralsight is my clear winnner. Whether you're a code newbie or a seasoned engineer it's well worth the monthly fee.

Why I dislike dealing with exceptions

Throwing Exceptions is Dishonest

Consider the following code snippet:

public decimal SquareRootOf(int x)
{
    if(x < 0)
        throw new ArgumentException("x is negative");

    // Calculate the square root
}
Enter fullscreen mode Exit fullscreen mode

We see code like this all the time, and it's not a terrible practice. It's a good defensive programming practice to validate data that is passed into functions. But this function lied. It said it was going to return a decimal when passed an int, and it might, but it might also return an ArgumentException. There are a couple ways this code might be made more honest, but one appropriate fix is to be more honest about what type of parameters you can accept. If this function was declared that x was of type uint, then no check is needed and it can honestly say that yes, uint's always get decimals's back.

Exception Handling is a Poor Way to Control the Flow of Execution

Consider the following controller action for ASP.NET Core

[HttpDelete("{id}"]
public IActionResult DeletePost(int id)
{
    try
    {
        var post = _postRepository.GetPostById(id);

        var currentUser = _userService.GetCurrentUser();

        if(post.Author.Id == currentUser.Id)
        {
            _postRepository.Delete(post);
            return NoContent();
        }

        return Forbid();
    }
    catch (NotFoundException nfe)
    {
        return NotFound();
    }
    catch (AuthorizationException ae)
    {
        return Unauthorized();
    }
    catch
    {
        return StatusCodeResult(500);
    }
}
Enter fullscreen mode Exit fullscreen mode

Sure looks like resilient code. It checks for authorizations, returns custom exceptions that map nicely to the appropriate HTTP result. But it looks a bit jarring, and the control flow is routed mostly by exceptions. Additionally, we have some validation logic in here, and I prefer to keep the single responsibility of my controllers confined to orchestration.

Life Without Exception Handling

This is not a post about railway programming.

Imagine, if objects honestly told you they could return a few different types? What if a repository method could tell you "I'm going to return either a Post or an Exception"? Wouldn't that be nice.

Well there is a concept borrowed from functional programming, call railway oriented programming that is just that... but I won't be talking about it here. Personally, I've walked down this road before, and once you correctly deal with Asynchronous controllers... the readability (IMHO) just vanishes, and I suspect that C#, while multi-purpose, just isn't quite there yet to support paradigms so closely tied to functional programming. I prefer a more Object oriented approach. I will however leave this link for the interested.

Note: This post details railway oriented programming in F#, a proper functional language that is much more suited to this task than C#, but it explains the concept. For information on how it's done in C#, Zoran Horvat has this great course (yes, on pluralsight) about making your C# code more functional, and it has a module on railway oriented programming.

An old favorite, the Result class

I really have only one question when I ask some service method to do something, or get me something. Did it work? IF so, I've got the thing, and I can move on. IF something when wrong, I should be able to fail fast and return whatever error occurred further up the chain (As we are used to doing with exceptions)

There's an old pattern that I've used in many languages and frameworks, where a wrapper class holds both the data, and the success/failure information of the call.

Since we're confining ourself to the domain of ASP.NET Core controllers, we get a big break. Something that works can return the object we wanted. Something that fails can return an ActionResult. Since the class doing the actual work knows more about exactly what went wrong than the controller, it can return the correct ActionResult. This way, the controller is now relieved of the job of mapping exceptions to status codes, and can focus entirely on it's single responsibility of orchestration.

Let's look at some code, first a result implementation for void methods

public abstract class Result
{
    public ActionResult ErrorResult { get; private set; }

    public bool Errored => ErrorResult != null;

    protected Result()
    {
        ErrorResult = null;
    }

    protected Result(ActionResult errorResult)
    {
        ErrorResult = errorResult;
    }

    private class Success : Result { }

    private class Error : Result
    {
        public Error(ActionResult errorResult) : base(errorResult) { }
    }

    public static Result WithoutError => new Success();

    public static implicit operator Result(ActionResult errorResult) => new Error(errorResult);
}
Enter fullscreen mode Exit fullscreen mode

Let's unpack this a bit here.

First, I like making result class abstract because I don't want people using the constructor. There's a static factory property, and an implicit conversion we'll talk about later.

The one field of the Result is the ActionResult ErrorResult. Since ActionResult implements IActionResult and ASP.NET Core controllers usually return IActionResult, we can take any Result that Errored, and just return it's ErrorResult property.

Then I define two private implementations of Result: Success and Error. I don't want any users to actually know about these types, they are just implementations for me to use when I need to instantiate a Result object

Next we have a way to indicate the successful run of a void method a static property that returns a new instance of Success, encapsulated behind the Result type.

Then we have an implicit conversion from any ActionResult to a Result that instantiates a new Error (again, encapsulated)

So then we can return from "void" methods the following way


public Result DoSomething()
{
    // Do Something
    return Result.WithoutError;
}

public Result DoSomethingThatFails()
{
    // Do Something That Fails
    return new InternalServerError();
}

public Result DoSomethingThatWorks()
{
    // Do Some Stuff

    Result otherMethodResult = Other.MethodThatWorks();

    // Fail Fast
    if(otherMethodResult.Errored)
        return otherMethodResult.ErrorResult;

    // Do Some More Stuff

    return Result.WithoutError;
}
Enter fullscreen mode Exit fullscreen mode

So we see how to return without error, return ActionResult objects and have them converted to Result by the compiler, and easily return errors from other Result types. Works well for commands, but this doesn't give us data back from queries.

Getting something back, with Result

So what about methods that were supposed to return me some data? How do I get it? Well, we have another subclass of Result that can handle this

public class Result<T> : Result
{
    public T Value { get; private set; }

    private Result(T value)
    {
        Value = value;
    }

    private Result(ActionResult errorResult)
        : base(errorResult) { }

    public static implicit operator Result<T>(T value) => new Result<T>(value);
    public static implicit operator Result<T>(ActionResult errorResult) =>
        new Result<T>(errorResult);
}
Enter fullscreen mode Exit fullscreen mode

So now I've added a field for the data we hope to return, and made the class generic so we can make results of any type. I've kept the constructors private because I want people to use the implicit conversions. So methods that return Result can either return the object of type T, or any ActionResult that represents an error.

Side Note: Yes, you can make an object with a method that returns a Result<ActionResult>... or in other words always returns some ActionResult, for both successes and errors, which seems a little confusing... And it certainly would not play nice with the constructors and implicit conversions we've set up... but there is already an abstraction for objects that always return an HTTP results whether the action was a success or not. They're called controllers! It probably doesn't make sense for a controller to be making queries to some object that is essentially another controller... so I think returning Result<ActionResult> is a pretty pungent code smell. Moving on...

Let's revisit our delete post action, where we can see that the controller has reduced it's responsibility to nothing more than orchestrating the process for resolving a request, and it fails fast as soon as some part of that process returns an error.

[HttpDelete("{id}")]
public IActionResult DeletePost(int id)
{
    Result<User> getUser = _userService.GetCurrentUser();

    // The user might be unauthenticated, the user
    // service is now responsible for detecting that
    // logging it, responding with challenge etc.
    if (getUser.Errored)
        return getUser.ErrorResult

    Result deletion = _postRepository.DeletePost(id, getUser.Value);

    // Was the post not found? 
    // Was the user unauthorized?
    // Did we lose our database connection? 
    // We don't know, we don't care. 
    if(deletion.Errored)
        return deletion.ErrorResult;

    // All must have went well.
    return NoContent();
}
Enter fullscreen mode Exit fullscreen mode

Whew! This was a long post, and thanks for making it all the way to the end! If you have any questions or comments, feel free to leave them here, or if they come to you later, tweet them may way @sam_ferree! Like the idea? Fork me on GitHub

. . . . . . . . . . . .
Terabox Video Player