Exception Filters in .NET Framework: Addio all'inferno dei Try-Catch
Quante volte hai visto controller .NET Framework pieni di blocchi try-catch identici? Se stai lavorando con Web API .NET Framework 4.8 e ti ritrovi con try-catch ripetuti ovunque, questo articolo ti mostrerà come centralizzare la gestione degli errori per migliorare la manutenibilità e la coerenza.
Il Problema: Try-Catch Ovunque
Iniziamo con un esempio di un controller .NET Framework che, a prima vista, sembra ben strutturato:
[Authorize]
[RoutePrefix("api/user")]
public class UserController : BaseController
{
private readonly ILogger _logger;
private readonly IUserService _userService;
[HttpGet]
[Route("getUser")]
public IHttpActionResult GetUser()
{
try
{
var userData = _userService.GetUser();
return Ok(userData);
}
catch (Exception ex)
{
_logger.LogError($"Error in GetUser: {ex}");
return InternalServerError();
}
}
[HttpGet]
[Route("getAssets")]
public IHttpActionResult GetAssets()
{
try
{
var assets = _userService.GetAssets();
return Ok(assets);
}
catch (Exception ex)
{
_logger.LogError($"Error in getAssets: {ex}");
return BadRequest();
}
}
// Altri 20 metodi con lo stesso pattern...
}
A prima vista questo controller sembra ben strutturato: delega correttamente la logica di business ai servizi (_userService.GetUser(), _userService.GetAssets()), mantiene responsabilità chiare e si concentra sulla gestione delle richieste HTTP.
Ma c’è un problema nascosto: ogni metodo è costretto a duplicare la stessa logica di gestione degli errori, appesantendo il codice e creando incoerenze.
I Problemi
- Duplicazione: Lo stesso codice di gestione degli errori ripetuto ovunque
- Incoerenza: Alcuni metodi restituiscono
BadRequest(), altriInternalServerError()senza logica - Manutenibilità: Modificare la logica richiede modifiche in decine di posti
La Soluzione: Exception Filters
Cosa Sono gli Exception Filters
Gli Exception Filters in .NET Framework sono componenti che fanno parte della pipeline Web API. Sono attributi speciali che intercettano le eccezioni non gestite durante l’esecuzione delle action prima che vengano restituite al client. Gli Exception Filters vengono eseguiti solo quando si verifica un’eccezione e sono posizionati strategicamente per “catturare” tutte le eccezioni prima che diventino errori generici.
Gli Exception Filters possono essere applicati a due livelli:
- Globalmente: Registrati in
WebApiConfig.cse applicati automaticamente a tutti i controller e le action - Livello Controller/Action: Applicati usando l’attributo
[GlobalExceptionFilter]su controller o metodi specifici
Implementazione Base
public class GlobalExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly ILogger _logger;
public GlobalExceptionFilterAttribute()
{
_logger = DependencyResolver.Current.GetService<ILogger>();
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var exception = actionExecutedContext.Exception;
LogException(exception, actionExecutedContext);
var actionResult = CreateActionResult(exception);
actionExecutedContext.Response = actionResult.ExecuteAsync(CancellationToken.None).Result;
}
private void LogException(Exception exception, HttpActionExecutedContext context)
{
var controllerName = context.ActionContext.ControllerContext.Controller.GetType().Name;
var actionName = context.ActionContext.ActionDescriptor.ActionName;
_logger.LogError(exception,
"Unhandled exception in {Controller}.{Action}",
controllerName, actionName);
}
private IHttpActionResult CreateActionResult(Exception exception)
{
switch (exception)
{
case NotFoundException notFoundEx:
return CreateErrorResponse(HttpStatusCode.NotFound, "NotFoundException", notFoundEx.Message);
case ValidationException validationEx:
return CreateErrorResponse(HttpStatusCode.BadRequest, "ValidationException", validationEx.Message);
case UnauthorizedException unauthorizedEx:
return CreateErrorResponse(HttpStatusCode.Unauthorized, "UnauthorizedException", "Unauthorized access");
default:
return CreateErrorResponse(HttpStatusCode.InternalServerError, "InternalServerError", "An internal server error occurred");
}
}
private IHttpActionResult CreateErrorResponse(HttpStatusCode statusCode, string error, string message)
{
var errorResponse = new
{
error = error,
status = (int)statusCode,
message = message,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
};
var jsonContent = Newtonsoft.Json.JsonConvert.SerializeObject(errorResponse);
return new ResponseMessageResult(
new HttpResponseMessage(statusCode)
{
Content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json")
});
}
}
Eccezioni Personalizzate
public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message) { }
}
public class ValidationException : Exception
{
public ValidationException(string message) : base(message) { }
}
public class UnauthorizedException : Exception
{
public UnauthorizedException(string message = "Access denied") : base(message) { }
}
Registrazione del Filtro
In WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Registrazione globale del filtro
config.Filters.Add(new GlobalExceptionFilterAttribute());
// Altre configurazioni...
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Approccio Alternativo: Invece della registrazione globale, potresti applicare il filtro a livello di controller usando il decoratore [GlobalExceptionFilter] su controller specifici.
Tuttavia, l’approccio globale garantisce una copertura completa ed elimina la possibilità di dimenticare di applicare il filtro a nuovi controller.
Struttura del Progetto con Exception Filters
Riprendendo l’esempio che abbiamo visto con il controller che delega correttamente la logica di business ai servizi, l’integrazione degli Exception Filters richiede l’aggiunta di alcuni componenti specifici alla struttura esistente. Ecco come evolve il progetto:
📁 ExceptionFiltersDemo/
├── 📁 Controllers/
│ └── 📄 UserController.cs # Controller esistenti (refactorizzati)
├── 📁 Services/
│ ├── 📄 IUserService.cs # Layer di servizio esistente
│ └── 📄 UserService.cs # (modificato per eccezioni tipizzate)
├── 📁 Repository/
│ ├── 📄 IUserRepository.cs # Repository esistente
│ └── 📄 UserRepository.cs # (nessuna modifica necessaria)
├── 📁 Models/
│ ├── 📄 UserViewModel.cs # DTO esistenti
│ └── 📄 CreateUserRequest.cs # (nessuna modifica necessaria)
├── 📁 Filters/
│ └── 📄 GlobalExceptionFilterAttribute.cs # Il nostro Exception Filter
├── 📁 Exceptions/
│ ├── 📄 NotFoundException.cs # Eccezioni personalizzate
│ ├── 📄 ValidationException.cs
│ └── 📄 UnauthorizedException.cs
├── 📁 App_Start/
│ └── 📄 WebApiConfig.cs # (modificato per registrare il filtro)
├── 📄 Global.asax.cs # Configurazione esistente
├── 📄 Web.config # Configurazione esistente
Le modifiche principali riguardano:
- Aggiunta della cartella
/Filters/per il GlobalExceptionFilterAttribute - Aggiunta della cartella
/Exceptions/per le eccezioni tipizzate - Modifica di
WebApiConfig.csper registrare il filtro globalmente - Refactoring dei controller rimuovendo i blocchi try-catch
- Aggiornamento dei servizi per lanciare eccezioni tipizzate invece di gestirle
Refactoring del Controller
Prima: Inferno dei Try-Catch
[Authorize]
[RoutePrefix("api/user")]
public class UserController : BaseController
{
private readonly ILogger _logger;
private readonly IUserService _userService;
[HttpGet]
[Route("getUser")]
public IHttpActionResult GetUser()
{
try
{
var userData = _userService.GetUser();
return Ok(userData);
}
catch (Exception ex)
{
_logger.LogError($"Error in GetUser: {ex}");
return InternalServerError();
}
}
}
Dopo: Controller Ottimizzato
[Authorize]
// Attributo [GlobalExceptionFilter] non necessario grazie alla registrazione globale
[RoutePrefix("api/user")]
public class UserController : BaseController
{
private readonly IUserService _userService;
[HttpGet]
[Route("getUser")]
public IHttpActionResult GetUser()
{
// Codice pulito concentrato sulla logica di business
var userData = _userService.GetUser();
return Ok(userData);
}
}
Layer di Servizio con Eccezioni Tipizzate
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
public UserViewModel GetUser()
{
var userId = GetUserId();
var user = _userRepository.GetById(userId);
if (user == null)
{
throw new NotFoundException($"User {userId} not found");
}
if (!user.IsActive)
{
throw new ValidationException("User account is not active");
}
return new UserViewModel
{
Id = user.Id,
Name = user.Name,
Email = user.Email
};
}
}
Quando Utilizzare gli Exception Filters
Gli Exception Filters sono ideali per applicazioni .NET Framework 4.8 con Web API, specialmente quando vuoi centralizzare la gestione comune degli errori e mantenere i controller puliti e focalizzati. Sono particolarmente utili in contesti di sviluppo in team dove è necessario uno standard condiviso per la gestione degli errori.
Conclusioni
Gli Exception Filters in .NET Framework rappresentano una soluzione elegante per eliminare l’inferno dei Try-Catch dai controller mantenendo una chiara separazione delle responsabilità. Permettono di scrivere codice più pulito dove i controller si concentrano esclusivamente sulla loro responsabilità principale: orchestrare le chiamate ai servizi e gestire le richieste HTTP.
Centralizzare la gestione degli errori garantisce coerenza in tutta l’applicazione e semplifica enormemente la manutenzione del codice. Quando è necessario modificare la logica di gestione degli errori, basta intervenire in un solo punto invece che in decine di controller diversi. Implementando questa soluzione, trasformerai controller frammentati da try-catch in codice pulito e professionale.
Buon coding con .NET Framework!
Alberto
Related Posts

Utilizzare le Value Tuple
Tutti noi scriviamo metodi che restituiscono un valore, un metodo string restituisce una stringa, un metodo int restituisce un int, ma cosa fai quando hai bisogno di restituire più di un valore?

Dictionary invece di switch o if statements v2
Nell'articolo precedente ho parlato di come, a mio parere, sia possibile migliorare la qualità del codice memorizzando chiavi/valori in un dictionary invece di usare switch o istruzioni condizionali.

Dictionary invece di switch o if statements
Le condizioni sono un concetto base della programmazione ma le best practices suggeriscono di evitare un uso eccessivo degli if statements.