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
Utilizzare una qualunque lista per i parametri di tipo params in C#
Raggruppare risorse in .NET Aspire
Gestione CSS in Blazor con .NET 9
Gestione ciclo di vita in .NET Aspire
Creare comandi nella dashboard .NET Aspire
Ospitare n8n su Azure App Service
Gestione degli eventi nei Web component HTML
Utilizzare WhenEach per processare i risultati di una lista di task
Gestire il routing HTTP in Azure Container App
Effettuare un clone parziale di un repository di GitHub
Utilizzare Locust con Azure Load Testing
Bloccare l'esecuzione di un pod in mancanza di un'artifact attestation di GitHub


