Tutte le volte che dobbiamo sollevare un errore in Web API, sappiamo che da un punto di vista semantico, questo si traddurrà in uno status code, e una descrizione per l'utente. Per esempio, se stiamo cercando di modificare una entity Order che non esiste, dovremo ritornare un 404 Not Found, mentre se i dati in input sono errati dovremo restituire un 400 Bad Request, magari con gli errori di validazione.
Il problema è che tipicamente resituiamo questi status code dal controller, mentre questi errori sono sollevati da altri componenti dell'architettura, per esempio un validator, o il nostro OrderRepository:
[HttpPut("{orderId}")] public IActionResult UpdateOrder(int orderId, [FromBody] Order order) { // fetch the order from the database and // return not found if it doesn't exist var originalOrder = _orderRepository.Get(orderId); if (originalOrder == null) { return NotFound(); } // update the order based on the input // ... // validate the updated order var validationResults = _orderValidator.Validate(order); if (!validationResults.IsValid) { return BadRequest(validationResults); } // save the changes to the database _orderRepository.Update(order); return NoContent(); }
Questo dà origine a codice che è molto verboso, e che inoltre ci espone ad alcuni "rischi", come il metodo Get di OrderRepository che può restituire un null, da cui il rischio di ritrovarci delle NullReferenceException se non effettuiamo i controlli dovuti.
Una possibile alternativa è quella di sollevare un particolare tipo di eccezione dai servizi applicativi, così poi da gestirla all'interno della nostra pipeline. Per esempio possiamo definire una nostra WebApiException come segue:
public class WebApiException : Exception { public HttpStatusCode StatusCode { get; } public WebApiException(HttpStatusCode statusCode, string message) : base(message) { StatusCode = statusCode; } }
e utilizzarla all'interno dei nostri servizi applicativi:
public class OrderRepository { public Order Get(int orderId) { // fetch from the database // and throw WebApiException.NotFound if not found // ... if (order == null) { throw new WebApiException(HttpStatusCode.NotFound, "Order not found"); } return order; } }
In questo esempio, la nostra classe OrderRepository solleverà una WebApiException, con il relativo status code, per segnalare che l'ordine cercato non è esistente. Si tratta di una scelta architetturale non propriamente "convenzionale", perché un repository non dovrebbe avere "cognizione" di trovarsi in esecuzione in una WebApi, tuttavia un sistema del genere semplifica di molto le cose. Nel controller, infatti, ora possiamo limitarci a chiamare i servizi applicativi, senza doverne controllare l'esito:
[HttpPut("{orderId}")] public IActionResult UpdateOrder(int orderId, [FromBody] Order order) { // fetch the order from the database and // return not found if it doesn't exist var originalOrder = _orderRepository.Get(orderId); // update the order based on the input // ... // validate the updated order _orderValidator.Validate(order); // save the changes to the database _orderRepository.Update(order); return NoContent(); }
Poi, possiamo impostare un global exception handler nella pipeline di ASP.NET Core, così da intercettare questa particolare eccezione e usarla per ritornarne il contenuto all'utente:
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // .. altro codice qui .. app.UseExceptionHandler(handler => { handler.Run(context => { var exception = context.Features.Get<IExceptionHandlerFeature>().Error; var message = "An error occurred while processing your request."; if (exception is WebApiException webApiException) { context.Response.StatusCode = (int)webApiException.StatusCode; message = webApiException.Message; } else { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } context.Response.ContentType = "application/json"; return context.Response.WriteAsync(JsonSerializer.Serialize(new { message })); }); }); .. app.Run(); }
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Cambiare la chiave di partizionamento di Azure Cosmos DB
Usare una container image come runner di GitHub Actions
.NET Conference Italia 2024
Eseguire attività pianificate con Azure Container Jobs
Eseguire le GitHub Actions offline
Implementare l'infinite scroll con QuickGrid in Blazor Server
Utilizzare un service principal per accedere a Azure Container Registry
Eseguire query manipolando le liste contenute in un oggetto mappato verso una colonna JSON
Sfruttare al massimo i topic space di Event Grid MQTT
Disabilitare automaticamente un workflow di GitHub
Ottimizzare il mapping di liste di tipi semplici con Entity Framework Core
Utilizzare Azure AI Studio per testare i modelli AI
I più letti di oggi
- Modificare il timeout della sessione
- Utilizzare parametri a livello di controller nel routing di ASP.NET Core
- Eseguire le GitHub Actions offline
- Il nuove valore inline-block della proprietà CSS display
- Effettuare il multibinding in un'applicazione Xamarin Forms
- .NET Framework 4.0 e VS 2010 in Release Candidate
- Condividere immagini in Windows Phone 8
- Utilizzare EF.Constant per evitare la parametrizzazione di query SQL
- Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API