Middleware-based Exception Handling in .NET

Exception handling is a crucial aspect of developing reliable and robust applications. In .NET 6 APIs, implementing effective exception handling using middlewares can greatly enhance error reporting, application stability, and overall user experience. This blog post focuses on implementing exception handling in .NET 6 Web API using custom middlewares and provides practical examples for a better understanding.

Exception Handling Middleware in .NET Web API

Middlewares are components that process HTTP requests and responses in a pipeline. Exception handling middlewares intercept exceptions, allowing developers to handle them gracefully. Follow the steps below to implement exception handling.

Create a Custom Exception Middleware

Create a custom middleware class that will handle exceptions globally. This middleware will catch any unhandled exceptions and perform the necessary actions. Inside the InvokeAsync method of the custom exception middleware, you can define the actions to be performed when an exception occurs. These actions may include logging the exception details and returning an appropriate response to the client. Here's an example:

public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception exception)
        {
            var response = context.Response;
            response.ContentType = "application/json";

            switch (exception)
            {
                case AppException ex:
                    response.StatusCode = StatusCodes.Status400BadRequest;
                    break;
                case ArgumentException ex:
                    response.StatusCode = StatusCodes.Status400BadRequest;
                    break;
                case NotFoundException ex:
                    response.StatusCode = StatusCodes.Status404NotFound;
                    break;
                default:
                    // Unhandled error
                    _logger.LogError(exception, exception.Message);
                    response.StatusCode = StatusCodes.Status500InternalServerError;
                    break;
            }

            var resultContent = JsonSerializer.Serialize(new { message = exception.Message });
            await response.WriteAsync(resultContent);
        }
    }
}

In the above example, based on different types of exceptions, a corresponding HTTP status code is returned to the client along with the exception message. You can add more exception types here to be globally handled. If the exception does not match any specified type, the default behavior is to return an Internal Server Error (500) HTTP status code. 

Register Middleware

In the Startup.cs (or Program.cs since .NET 7) file, configure the custom exception middleware to handle exceptions globally. This middleware should be added to the pipeline before any other middlewares to ensure proper exception handling. Here's an example of configuring the middleware:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<ExceptionHandlingMiddleware>();

    // Remaining configurations...
}

Now, whenever an exception is thrown by the controllers or their underlying implementation, the exceptions will be processed by the middleware.

Summary

Implementing exception handling in .NET Web APIs using middlewares is a powerful technique for improving error reporting, maintaining application stability, and enhancing the overall user experience. By creating a custom exception middleware and configuring it in the pipeline, you can capture exceptions and perform necessary actions such as logging and returning appropriate responses to the client.


Minimal APIs vs MVC Controllers: Choosing the Best Option for Your .NET Project

When it comes to building APIs in .NET, developers have two main options: Minimal APIs and Controllers-based APIs. While both options can accomplish the same goal of creating a functional API, they differ in their approaches and the trade-offs they offer.

In this blog post, we will take a closer look at both approaches and explore the advantages and disadvantages of each.

What is a Minimal API?

As shown in our previous post, a Minimal API is a new feature introduced in .NET 6 that allows developers to create simple, single-file APIs without the need for controllers or configuration files. This approach is ideal for developers who want to create small APIs quickly and easily.

Minimal APIs are built using the Microsoft.AspNetCore.Builder namespace and are defined using a single endpoint configuration block. Here's an example of a simple Minimal API:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

To learn more about how to get started with Minimal APIs, see our post.

What is a Controllers-based API?

A Controllers-based API is the traditional approach to building APIs in .NET. It involves creating controllers that handle HTTP requests and responses. Controllers-based APIs are more flexible than Minimal APIs, as they allow developers to separate concerns and organize code into separate files.

Here's an example of a simple Controllers-based API:

[ApiController]
[Route("[controller]")]
public class HelloController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("Hello World!");
    }
}

In this example, we create a controller named HelloController that handles HTTP GET requests to the root endpoint (/) and responds with "Hello World!".

Advantages and Disadvantages of Minimal APIs

Minimal APIs offer several advantages over Controllers-based APIs:

  • Simplicity: Minimal APIs are easier to write and maintain than Controllers-based APIs. They require fewer files and less configuration, making them ideal for small projects and prototypes.
  • Performance: Minimal APIs are faster than Controllers-based APIs because they have fewer layers of abstraction. This can be a significant advantage in high-traffic scenarios.

However, Minimal APIs also have some disadvantages:

  • Limited Flexibility: Minimal APIs are designed to be simple, so they lack some of the features and flexibility of Controllers-based APIs. For example, they cannot handle complex data types or perform model validation.
  • Debugging: Because Minimal APIs are usually defined in a single file, debugging can be more challenging than with Controllers-based APIs, where code is organized into separate files. While there are workarounds in Minimal API that can improve maintainability and allow for endpoint separation across different files (such as using extension methods), it may not be as effective as the traditional approach of using controllers.

Advantages and Disadvantages of Controllers-based APIs

Controllers-based APIs offer several advantages over Minimal APIs:

  • Flexibility: Controllers-based APIs are more flexible than Minimal APIs because they allow developers to separate concerns and organize code into separate files. This makes it easier to handle complex data types and perform model validation.
  • Debugging: Controllers-based APIs are easier to debug than Minimal APIs because code is organized into separate files.

However, Controllers-based APIs also have some disadvantages:

  • Complexity: Controllers-based APIs are more complex than Minimal APIs. They require more files and configuration, making them more challenging to write and maintain.
  • Performance: Controllers-based APIs are slower than Minimal APIs because they have more layers of abstraction. This can be a significant disadvantage in high-traffic scenarios.

Conclusion

Both Minimal APIs and Controllers-based APIs offer advantages and disadvantages, and the choice between them ultimately depends on the specific requirements of the project.

Minimal APIs are ideal for small projects and prototypes that require simplicity and performance. They are easy to write and maintain, but they lack some of the features and flexibility of Controllers-based APIs.

On the other hand, Controllers-based APIs are ideal for larger projects that require flexibility and maintainability. They are more complex than Minimal APIs and require more files and configuration, but they offer greater flexibility and ease of debugging.

In summary, both Minimal APIs and Controllers-based APIs have their place in the .NET ecosystem, and developers should choose the one that best fits their project's requirements. The decision should be based on factors such as project size, complexity, and performance requirements. By understanding the trade-offs of each approach, developers can choose the right one for their needs and build high-quality APIs in .NET.

Getting Started with Minimal API in .NET 7

Minimal API in .NET is a powerful tool for building simple and lightweight web APIs. It streamlines the development process by allowing developers to create APIs with minimal boilerplate code. In this post, we'll walk you through the steps of getting started with Minimal API in .NET, including its most basic features and setup steps.

Prerequisites

  • .NET 6 SDK (or higher) - we'll use .NET 7
  • Visual Studio 2022 or Visual Studio Code

Getting Started

To create a new Minimal API project, run the following command in your terminal or command prompt:
$ dotnet new web -n ProjectName -minimal
Alternatively you can use Visual Studio and create a new project using "ASP.NET Core Web API" template.
Make sure to untick the 'Use controllers' checkbox:
Once the project is created, navigate to the Program.cs file and override the content with following code:
var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () => "Hello, world!");

app.Run();
This code creates a new instance of the WebApplication class, adds a single endpoint to the API, and runs the application. It's the most basic use of the Minimal API.

To run the application, use either Visual Studio or use the following command in the terminal.
$ dotnet run
Your API is now running and ready for testing.

Routing

Map Methods

To add more endpoints, you can use the MapGet, MapPost, MapPut and MapDelete methods of the WebApplication class. Each of these methods takes two parameters - the endpoint URL and an async delegate that defines the endpoint handler. Here are few examples:
  • MapGet: handles HTTP GET requests
app.MapGet("/hello", () => "Hello, world!");
  • MapPost: handles HTTP POST requests
app.MapPost("/submit", async (HttpContext context) =>
{
    // handle the POST request
});
  • MapPut: handles HTTP PUT requests
app.MapPut("/update/{id}", async (int id, HttpContext context) =>
{
    // handle the PUT request with the given id
});
app.MapDelete("/delete/{id}", async (int id, HttpContext context) =>
{
    // handle the DELETE request with the given id
});

Additionally you can use MapMethods method. MapMethods is a routing method that allows you to define an endpoint that can handle multiple HTTP methods. This method takes three parameters - the endpoint URL, a list of HTTP methods that the endpoint should handle and an async delegate that defines the endpoint handler.
app.MapMethods("/hello", new[] { HttpMethods.Get, HttpMethods.Post }, async (HttpContext context) =>
{
    if (context.Request.Method == HttpMethods.Get)
    {
        return "Hello, world!";
    }
    else if (context.Request.Method == HttpMethods.Post)
    {
        // handle the POST request
    }
});

The extension method MapGroup is useful for organizing groups of endpoints that share a common prefix. It helps eliminate redundant code and allows customization of entire groups of endpoints. In the example below authorization is applied to all endpoints a group called projectGroup.
var projectGroup = app.MapGroup("/projects")
    .RequireAuthorization();

projectGroup.MapGet("/{projectId}", GetProject);

projectGroup.MapDelete("{projectId}", DeleteProject);

Handlers

A handler is a function that processes incoming HTTP requests and generates HTTP responses. Handlers are used to implement the logic of an endpoint in a modular and reusable way.

To use a handler in a Minimal API, you can define Lambda Expression as in the examples above:
app.MapPost("/submit", async () =>
{
    // handle the POST request
});

Another way is to pass a method as parameter:
private string GetHandler() => "This is a Get handler";

app.MapGet("/", GetHandler);
More handler examples you can find here.

Parameters Binding

Parameter binding in Minimal API is a technique for extracting request data and converting it into strongly typed parameters defined in the route handler. Below, we will provide some basic examples for different types of binding sources.
Supported binding sources:
  • Route path parameters
  • Route query string
  • Body in JSON format
  • Header values
  • Services from dependency injection container
  • Custom
Basic parameter binding:
app.MapGet("/greet/{name}", (string name) => $"Hello, {name}!");
Header values binding:
app.MapGet("/user/{id}", (int id, [FromHeader(Name = "Authorization")] string authorizationHeader) => { });
In the above example, header parameter is explicitly declared using "From" parameter. More about explicit parameter binding can be found here.

Services binding from DI container:
app.MapGet("/user/{id}", (int id, [FromServices] Service service) => { });
You can find more about parameters biding in Minimal API here.

What's next?

In our upcoming posts, we will cover more features of Minimal API and its use cases. Additionally, you can learn more about Minimal API from Microsoft's official documentation.