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
Evitare la command injection in un workflow di GitHub
Effettuare lo stream della risposta in ASP.NET Core tramite IAsyncEnumerable
Modificare i metadati nell'head dell'HTML di una Blazor Web App
Usare il versioning con i controller di ASP.NET Core Web API
Sfruttare lo stream rendering per le pagine statiche di Blazor 8
Utilizzare Model as a Service su Microsoft Azure
Ottimizzazione dei block template in Angular 17
Sfruttare lo streaming di una chiamata Http da Blazor
Mascherare l'output di un valore all'interno dei log di un workflow di GitHub
Creare un'applicazione React e configurare Tailwind CSS
Sfruttare al massimo i topic space di Event Grid MQTT
I più letti di oggi
- Utilizzare Docker Compose con Azure App Service
- Utilizzare QuickGrid di Blazor con Entity Framework
- Modernizzare le applicazioni WPF e Windows Forms con Blazor
- ASP 3 per esempi
- annunciato #netstandard 2.1. .NET Core lo supporterà a partire da #netcore3, così come le prossime versione di #xamarin, #mono e #unity.il supporto per #netfx 4.8, invece, non ci sarà. https://aspit.co/bq2
- Steel Style CheckBox per Silverlight 4.0