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.
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 |
|
Global Exception Handling |
|
Custom Exceptions |
|
Exception Filters |
|
Graceful Shutdown Handling |
|
Validation and Guard Clauses |
|
Async Exception Handling |
|
Retry Mechanisms |
|
Circuit Breaker Pattern |
|
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.