Tuesday, April 18, 2023

What is Exception Handling? How do you implement exception handling in .NET Core?

Exception Handling:

Exception handling is the process of responding to runtime errors or exceptional conditions in a controlled manner to prevent program crashes or undefined behavior. In .NET, exceptions are represented by the System.Exception class and its derivatives. Exception handling helps maintain the stability, reliability, and usability of applications. 

Exception Handling

 

In .NET Core, error handling can be implemented using several techniques, depending on the application type (e.g., web, desktop, or console). Common approaches include:

Exception handling in .NET is a robust process aimed at managing errors gracefully while ensuring application reliability and user satisfaction. Below is a detailed explanation of all major methods, processes, and techniques, complete with examples, implementation details, and use cases.

1. Structured Exception Handling (SEH)

This is the foundation of exception handling in .NET and involves the use of try, catch, finally, and throw.

How It Works

  • try block: Encloses the code that might throw exceptions.
  • catch block: Catches specific exceptions and handles them.
  • finally block: Executes cleanup code regardless of whether an exception occurred.
  • throw: Used to propagate exceptions.

Example


try
{
    int number = int.Parse("invalid"); // Will throw FormatException
}
catch (FormatException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
finally
{
    Console.WriteLine("Execution completed.");
}
    

Use Cases

  • Handling runtime errors like parsing, file I/O, or network connectivity issues.
  • Ensuring resources are released (e.g., closing a file handle).

2. Global Exception Handling in ASP.NET Core

In web applications, centralizing error handling improves maintainability and ensures consistent error responses.

How to Implement

Custom Middleware:


public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public GlobalExceptionMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;

        var response = new { message = "An unexpected error occurred." };
        return context.Response.WriteAsJsonAsync(response);
    }
}
    

Register Middleware in Program.cs:


app.UseMiddleware<GlobalExceptionMiddleware>();
    

Use Cases

  • Providing unified error responses for REST APIs.
  • Logging and masking sensitive errors.

3. Developer Exception Page

A built-in feature in ASP.NET Core for detailed error information during development.

How It Works


if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
    

Use Cases

  • Debugging during development.
  • Quickly identifying the root cause of exceptions.

4. Exception Filters

Filters are action-level or global handlers in ASP.NET Core MVC for catching exceptions.

How to Implement

Custom Exception Filter:


public class CustomExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        context.Result = new JsonResult(new { error = context.Exception.Message })
        {
            StatusCode = 500
        };
    }
}
    

Register Filter Globally:


services.AddControllers(options =>
{
    options.Filters.Add<CustomExceptionFilter>();
});
    

Use Cases

  • Handling specific exceptions at the controller level.
  • Logging or transforming exceptions into meaningful HTTP responses.

5. Logging Exceptions

Logging ensures you capture errors for debugging, monitoring, and auditing.

How to Implement


try
{
    throw new InvalidOperationException("Example exception");
}
catch (Exception ex)
{
    ILogger logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<Program>();
    logger.LogError(ex, "An error occurred");
}
    

Use Cases

  • Monitoring application health.
  • Analyzing trends and patterns in production errors.

6. Using Exception Filters in Middleware

Exception filters can be added as middleware for fine-grained error handling.

How It Works


public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        try
        {
            await next.Invoke();
        }
        catch (Exception ex)
        {
            // Handle the exception
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = 500;
            await context.Response.WriteAsync(new { error = ex.Message }.ToString());
        }
    });
}
    

Use Cases

  • Centralizing exception handling in middleware pipelines.
  • Ensuring custom responses for all unhandled exceptions.

7. Graceful Shutdown Handling

Ensure resources are released and errors are managed during application shutdown.

How It Works


public class Program
{
    public static async Task Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
        lifetime.ApplicationStopping.Register(OnApplicationStopping);

        await host.RunAsync();
    }

    private static void OnApplicationStopping()
    {
        Console.WriteLine("Application is shutting down gracefully.");
    }
}
    

Use Cases

  • Releasing resources like database connections.
  • Handling cleanup tasks during server shutdown.

8. Validation and Guard Clauses

Proactively prevent exceptions by validating input and business logic upfront.

How It Works


public void ProcessOrder(Order order)
{
    if (order == null)
        throw new ArgumentNullException(nameof(order));
    if (order.Quantity <= 0)
        throw new ArgumentException("Quantity must be greater than zero.");
}
    

Use Cases

  • Validating user inputs in web forms or APIs.
  • Ensuring preconditions for business logic.

9. Async Exception Handling

Manage exceptions in asynchronous programming effectively to avoid unobserved task errors.

How It Works


public async Task ProcessDataAsync()
{
    try
    {
        await Task.Run(() => throw new InvalidOperationException("Async error."));
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Caught exception: {ex.Message}");
    }
}
    

Use Cases

  • Handling exceptions in asynchronous workflows.
  • Avoiding app crashes due to unobserved task exceptions.

10. Retry Mechanisms and Polly Library

Use retry mechanisms to handle transient exceptions like network issues.

How It Works


var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .Retry(3, (exception, retryCount) =>
    {
        Console.WriteLine($"Retrying... Attempt: {retryCount}");
    });

retryPolicy.Execute(() =>
{
    // Code that might throw exceptions
    HttpClient client = new HttpClient();
    client.GetAsync("https://example.com").Wait();
});
    

Use Cases

  • Retrying failed HTTP requests in APIs.
  • Handling transient database connection errors.

11. Circuit Breaker Pattern

Prevent application overload by implementing the circuit breaker pattern using Polly or custom logic.

How It Works


var circuitBreakerPolicy = Policy
    .Handle<Exception>()
    .CircuitBreaker(2, TimeSpan.FromSeconds(30), 
        onBreak: (ex, duration) => Console.WriteLine($"Circuit broken for {duration}"),
        onReset: () => Console.WriteLine("Circuit reset"));

circuitBreakerPolicy.Execute(() =>
{
    // Code that might throw exceptions
    throw new InvalidOperationException("Simulated failure.");
});
    

Use Cases

  • Preventing cascading failures in distributed systems.
  • Limiting retries during persistent failures.

Summary of Exception Handling Techniques and Use Cases

Technique Use Cases
Try-Catch Blocks
  • Handling runtime exceptions locally.
  • Executing fallback logic for specific errors.
Global Exception Handling
  • Managing unhandled exceptions globally.
  • Logging and providing consistent error responses.
Custom Exceptions
  • Defining domain-specific errors.
  • Improving code readability and maintainability.
Exception Filters
  • Centralizing exception handling in middleware pipelines.
  • Ensuring custom responses for unhandled exceptions.
Graceful Shutdown Handling
  • Releasing resources like database connections.
  • Handling cleanup tasks during server shutdown.
Validation and Guard Clauses
  • Validating user inputs in web forms or APIs.
  • Ensuring preconditions for business logic.
Async Exception Handling
  • Handling exceptions in asynchronous workflows.
  • Avoiding app crashes due to unobserved task exceptions.
Retry Mechanisms
  • Retrying failed HTTP requests in APIs.
  • Handling transient database connection errors.
Circuit Breaker Pattern
  • Preventing cascading failures in distributed systems.
  • Limiting retries during persistent failures.

By combining these techniques, you can build resilient, user-friendly, and maintainable applications in .NET.

No comments:

Post a Comment

Please keep your comments relevant.
Comments with external links and adult words will be filtered.