Come visto nell'articolo "Applicazioni web basate su servizi: un approccio alternativo con ASP.NET MVC2", è possibile utilizzare ASP.NET MVC per realizzare un RESTful web service. L'esempio proposto nell'articolo evidenzia l'utilizzo dei verbi http più comuni, GET e POST, ma il paradigma REST contempla anche l'utilizzo dei verbi PUT e DELETE, tipicamente associati ad operazioni rispettivamente di aggiornamento e cancellazione.
Il vantaggio di utilizzare questi due ulteriori verbi è che ci consente di discriminare le operazioni da effettuare su una collection tramite essi, piuttosto che a livello di URL; quest'ultimo, invece, riveste semplicemente lo scopo di indentificare la risorsa che si vuole gestire. Supponendo, infatti, che l'indirizzo base per la collezione di elementi del guestbook sia, ad esempio, localhost/items, il servizio REST dovrebbe rispettare le seguenti convenzioni:
- A una richiesta in GET a localhost/items, restituire l'elenco degli elementi;
- a una richiesta in POST a localhost/items, aggiungere un elemento all'elenco;
- a una richiesta in GET a localhost/items/122, restituire l'elemento di ID 122;
- a una richiesta in PUT a localhost/items/122, aggiornare l'elemento di ID 122;
- a una richiesta in DELETE a localhost/items/122, eliminare l'elemento di ID 122;
In ASP.NET MVC, gli ActionMethod sono comunque in grado di gestire anche richieste PUT o DELETE una volta marcati gli con appositi attributi HttpPut ed HttpDelete, rispettivamente per associare il metodo al solo verbo PUT ed al solo verbo DELETE.
Ipotizziamo allora di voler eliminare un item, identificato univocamente da un id numerico, dal repository di dati del guestbook di esempio dell'articolo: decoriamo il metodo Delete del controller con l'attributo HttpDelete.
[HttpDelete][ActionName("Default")] public ActionResult Delete(int id) { string returnValue = string.Empty; try { // ... // Logica di gestione della cancellazione // ... returnValue = "OK"; } catch { // Valorizza il messaggio di risposta in caso di errore returnValue = "Si è verificato un errore interno. Non è stato possibile eliminare il post."; } return Json(retValue); }
In tal modo l'ActionMethod risponderà solo ed esclusivamente a richieste http DELETE. In tutti gli altri casi verrà restituito un errore 404. Come possiamo notare, è stato aggiunto anche l'attributo ActionName, per associare questo metodo ad una action chiamata Default. In questo modo, configurando quest'ultima come quella di default per il controller ItemsController, è possibile rispettare perfettamente la convenzione sul naming che abbiamo espresso in precedenza.
Il caso dell'aggiornamento è analogo a quello della cancellazione, ad eccezione del marcatore, e del parametro di input, che in questo caso conterrà l'intero oggetto.
[HttpPut][ActionName("Default")] public ActionResult Update(GuestbookItemModel item) { if (ModelState.IsValid) { try { // ... // Logica di gestione aggiornamento // ... item.ResponseMessage = "OK"; } catch { // Valorizza il messaggio di risposta in caso di errore item.ResponseMessage = "Si è verificato un errore interno. Non è stato possibile eliminare il post."; } } else { // Valorizza il messaggio di risposta in caso di errore // di validazione e popola una collection con i dettagli. item.ResponseMessage = "Uno o più campi non sono validi"; item.ErrorDetails = new ArrayList(); foreach (var key in ModelState.Keys) { var error = ModelState[key].Errors.FirstOrDefault(); if (error != null) item.ErrorDetails.Add(error.ErrorMessage); } } return Json(item); }
Per verificarne il funzionamento, anche in questo caso è possibile simulare le chiamate e vederne gli effetti utilizzando jQuery, prestando però attenzione ad un aspetto importante: jQuery supporta perfettamente i verbi PUT e DELETE, come pure i principali browser (Internet Explorer, Firefox, Chrome e Opera), ma in generale non tutti i browser sono in grado di gestire questi verbi.
Nel caso della cancellazione, ad esempio, possiamo effettuare una chiamata al metodo ajax() impostando il verbo DELETE tramite il parametro type, ed associando all'URL l'id dell'elemento da eliminare serializzato in JSON.
function submitDelete() { // Legge i dati var id = $('#id).val(); // Invia il DELETE $.ajax({ url: '/Items/' + id, type: "DELETE", dataType: "json", success: function (result) { if (result.ResponseMessage == "OK") { // ... // Gestisce la risposta corretta // ... } else { // ... // Gestisce l'errore // ... } } }); }
Il caso dell'aggiornamento è assolutamente analogo al precedente, dal quale differisce per il parametro type della chiamata, impostato a PUT, e per i dati inviati, serializzati in JSON, che comprendono in questo caso l'intero oggetto del modello.
function submitUpdate() { // Legge i dati dal form var id = $('#id').val(); var title = $('#Title').val(); var body = $('#Body').val(); var authorName = $('#AuthorName').val(); var authorEmail = $('#AuthorEmail').val(); // Costruisce la struttura JSON var putData = '{ "item" : { "ID" : "' + id + '", "Title" : "' + title + '", "Body" : "' + body + '", "AuthorName" : "' + authorName + '", "AuthorEmail" : "' + authorEmail + '" } }'; // Invia il PUT $.ajax({ url: '/Items/' + id, type: "PUT", data: putData , dataType: "json", contentType: "application/json", success: function (result) { if (result.ResponseMessage == "OK") { // ... // Gestisce la risposta corretta // ... } else { // ... // Gestisce l'errore // ... } } }); }
Un'ultima nota riguarda il requisito, anch'esso convenzionale, che invocazioni PUT e DELETE siano idempotenti. Pertanto, è necessario implementare i rispettivi metodi sul servizio in modo che, anche in presenza di esecuzioni multiple, il risultato sia sempre il medesimo.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Generare HTML a runtime a partire da un componente Razor in ASP.NET Core
Implementare il throttling in ASP.NET Core
Personalizzare l'errore del rate limiting middleware in ASP.NET Core
Load test di ASP.NET Core con k6
Usare un KeyedService di default in ASP.NET Core 8
Registrare servizi multipli tramite chiavi in ASP.NET Core 8