Skip to content
loading...

Avoiding Exceptions in ASP.NET Core

Sam Ferree on August 13, 2018

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) ... [Read Full]
markdown guide
 

In all the asp.net apis I have written I have adopted the global exception handler approach to returning errors.

It gives me a central location for logging and an easy way for nested services to fail fast.

Do you find all the if (result.errored) checks everywhere add a lot more conditional paths to your code?

Where are the sharp edges, in your experience, with this approach?

How do you feel about using an app framework type (ActionResult) throughout your business logic? Do all your libs take a dependency on Asp.net now?

Thanks for the interesting post.

 

If you do a good job of applying the single responsibility principle, in my experience the controller doesn’t usually have more than a handful of calls to make.

The if (action.Errored) are used more like guard clauses with early exits so it doesn’t feel to me like it gets too busy, could just be a preference though.

The biggest problem is constructors. I would love for new T() to return a Result with a bad request error if the validation failed. I end up moving this logic into a TFactory and then I’m left with anemic data models which doesn’t make my inner programmer happy... an object should be able to enforce it’s own rules about data validity and not offload that to some other class. I’m playing with a few solutions, but my favorite one won’t have the necessary language features (interfaces and abstract classes that can define static methods) until C# 8.0 (or even later :( )

ActionResult is used as the error type because it’s so handy in ASP.NET Core, i’ve Used other failure types, and sometimes my own depending on the context I’m working in.

If I need to keep web client framework code out of my domain layer, I’d probably have my domain code throw appropriate exceptions, and then apply a facade in me .net Core App that catches those exceptions and maps them to ActionResults. My main goal here is to avoid catching and handling exceptions in my controllers. Some other web framework might have a really elegant way of handling exceptions, and while I dislike it, it is a pattern than a lot of other developers understand.

 

You got it wrong (about exception handling and flow control). The way you use Result is correct and when you can do that instead of throwing exceptions - you should go for it. Exception handling is much slower and it's not meant to replace the normal workflow. The whole idea of throwing exceptions is, that you may have several method calls nested (even recursively) and you may handle the exceptions way up in the stack calls. If you do this using Result - the whole process turns into a lot of result checking and returning errors back to the caller, which is obviously worse and makes the code hard to read just like it's harder to read when you use exceptions as flow control.

 

Things get a lot better when you can use Railway Oriented Programming but C# isn't built for it. There is certainly a trade-off: explicit function contracts or nicer nesting. At this point in my career I choose the former and then lean toward languages that support both (F#).

 

Ah functional programming... you know what they say - it's like communism - most arguments against communism can be applied to functional programming as well:

  • If it is so good, why does it fail every single time?
  • Why is it preached only amongst scientists, who didn't work a real job a single day in their life?
  • If you believe it's so good, why don't you make your own company and use it to take over the market?
  • When it fails the explanation was: it's because it wasn't pure functional programming...
 

That's a great point I hadn't considered.

 

This is the pattern I've been using in C# too. I've tried functional concepts like "either" but the language fights you and it's an added complexity. Nice idea using implicit operators. I'm stealing that.

 
 

I don't see a lot of exception handling code because I believe in writing preventive code whenever in the stack that makes sense.

 

Yup. That’s mostly my intent here. Services with preventative code. In the event of some inability to complete the request, the services know more about what kind of failure happened than the controller, so I use the this enable them to dictate the correct HTTP Response.

 

Could this be applied to async code? Was having some issues figuring out how.

 

This is some older code, and I've played with a few versions of result now.

but I did often want to take a Result of Task of T and await it to get Result of T

Essentially to make anything awaitable you need to have a method that
returns a TaskAwaiter of T, or in our case Result of Task of T, which we can do with an extension method.

This is based off a different Result class I've been working with, but the basic idea should work.

        private static async Task<Result<TData>> FlipAsync<TData>(Result<Task<TData>> asyncResult)
        {
            try { return await asyncResult.Resolve(error => throw error); }
            catch (Exception error) { return error; }
        }

        public static TaskAwaiter<Result<TData>> GetAwaiter<TData>(this Result<Task<TData>> asyncResult) =>
            Result.FlipAsync(asyncResult).GetAwaiter();
 

Awesome, thank you for sharing. If you are willing, could you also add your newer approach to your Github? Would like to check that out too!

 

I use the following library to handle all exceptions without any try..catch in controllers. github.com/khellang/Middleware

 

Nice! seems quite useful in general for error handling, I might put this to use in some node side projects I have

thanks for the post!

 

Nice! Loved the implicit operator, cool approach reminds me of Option/Maybe.

 

I think you might need to come to the XXI century, to a more elegant, robust error handling in where we got rid of ResultXXX objects

 

Hey, I read your comment and was curious about what you think is a more elegant, robust error handling approach, but no details were listed in your comment. Would you expand on your thoughts here?

code of conduct - report abuse