With Custom Exceptions

Exception Handling in .NET Core Web API

Merwan Chinta
CodeNx
Published in
3 min readJan 14, 2024

--

Handling exceptions effectively is crucial for building robust and user-friendly applications. This article dives into exception handling in .NET Core Web API, showing how to create custom exceptions and manage them globally for a cleaner, more efficient codebase.

Why Exception Handling Matters

  • Properly handled exceptions ensure that your application behaves predictably under all circumstances.
  • It prevents sensitive error details from being exposed to the end user.
  • It provides meaningful feedback to users, enhancing their experience.
  • It makes the codebase easier to maintain and debug.

Building Custom Exceptions

Custom exceptions allow you to express specific error conditions more clearly. Let’s create four custom exceptions for our Core Web API.

NotFoundException

This exception is thrown when a requested resource is not found.

public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message) { }
}

ValidationException

Use this when data validation fails.

public class ValidationException : Exception
{
public ValidationException(string message) : base(message) { }
}

UnauthorizedAccessException

Indicates unauthorized access to a resource.

public class UnauthorizedAccessException : Exception
{
public UnauthorizedAccessException(string message) : base(message) { }
}

InternalServerErrorException

Represents unexpected failures in the application.

public class InternalServerErrorException : Exception
{
public InternalServerErrorException(string message) : base(message) { }
}

Global Exception Handling

Instead of scattering try-catch blocks throughout your code, .NET Core allows you to handle exceptions globally. This is done using middleware or filters. We’ll focus on using a global filter.

public class GlobalExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
var statusCode = context.Exception switch
{
NotFoundException => StatusCodes.Status404NotFound,

ValidationException => StatusCodes.Status400BadRequest,

UnauthorizedAccessException => StatusCodes.Status401Unauthorized,

_ => StatusCodes.Status500InternalServerError
};

context.Result = new ObjectResult(new
{
error = context.Exception.Message,
stackTrace = context.Exception.StackTrace
})
{
StatusCode = statusCode
};
}
}

This filter will intercept exceptions and convert them to appropriate HTTP responses.

Registering the Global Filter

In the Startup.cs, register the global filter.

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add(new GlobalExceptionFilter());
});
}
Throwing custom exceptions, caught by global filter in .Net Core Web API
Throwing custom exceptions, caught by global filter in .Net Core Web API— Image source: Created by Author

Usage of the custom exceptions

To illustrate the usage of the custom exceptions and the global exception filter in a .NET Core Web API, let’s create a simple example. This will involve a controller that throws these exceptions, demonstrating how they are caught and processed by the global exception filter.

[ApiController]
[Route("[controller]")]
public class SampleController : ControllerBase
{
[HttpGet("not-found")]
public ActionResult GetNotFound()
{
// Simulate a situation where a resource is not found
throw new NotFoundException("The requested resource was not found.");
}

[HttpGet("invalid")]
public ActionResult GetInvalid()
{
// Simulate a validation error
throw new ValidationException("Validation failed for the request.");
}

[HttpGet("unauthorized")]
public ActionResult GetUnauthorized()
{
// Simulate unauthorized access
throw new UnauthorizedAccessException("You do not have permission to access this resource.");
}

[HttpGet("internal-error")]
public ActionResult GetInternalError()
{
// Simulate an internal server error
throw new InternalServerErrorException("An unexpected error occurred.");
}
}

When you run your Web API and make requests to these endpoints, the corresponding custom exceptions will be thrown.

A GET request to /sample/not-found will throw the NotFoundException.

A GET request to /sample/invalid will throw the ValidationException.

When these exceptions are thrown, the global exception filter catches them. It then maps each exception to an appropriate HTTP status code and returns a structured response to the client.

For example, if NotFoundException is thrown, the filter will return a 404 Not Found status with a JSON body containing the error message and stack trace.

Unit testing the global exception filter

Testing the global exception filter involves checking if it properly converts exceptions into the corresponding HTTP responses.

To test this, you can simulate an action method throwing an exception and then assert the response from the filter. Example test using Moq and xUnit.

public class GlobalExceptionFilterTests
{
[Fact]
public void OnException_ShouldSetCorrectStatusCodeForNotFoundException()
{
// Arrange
var exceptionContextMock = new Mock<ExceptionContext>();
exceptionContextMock.SetupGet(x => x.Exception).Returns(new NotFoundException("Not found"));
var filter = new GlobalExceptionFilter();

// Act
filter.OnException(exceptionContextMock.Object);

// Assert
var objectResult = exceptionContextMock.Object.Result as ObjectResult;
Assert.NotNull(objectResult);
Assert.Equal(StatusCodes.Status404NotFound, objectResult.StatusCode);
}
}

Good exception handling significantly improves the quality of software.

I trust this information has been valuable to you. 🌟 Wishing you an enjoyable and enriching learning journey!

📚 For more insights like these, feel free to follow 👉 Merwan Chinta

--

--

Merwan Chinta
CodeNx

🚧 Roadblock Eliminator & Learning Advocate 🖥️ Software Architect 🚀 Efficiency & Performance Guide 🌐 Cloud Tech Specialist