Condividi tramite


Creare servizi back-end per app native per dispositivi mobili con ASP.NET Core

Di James Montemagno

Le app per dispositivi mobili possono comunicare con i servizi back-end di ASP.NET Core. Per istruzioni sulla connessione a servizi Web locali da simulatori iOS ed emulatori Android, vedere Connettersi a servizi Web locali da simulatori iOS ed emulatori Android.

Visualizzare o scaricare codice di esempio di servizi back-end

App per dispositivi mobili nativa di esempio

Questa esercitazione illustra come creare servizi back-end usando ASP.NET Core per supportare app per dispositivi mobili native. Usa l'app TodoRest Xamarin.Forms come client nativo, che include client nativi separati per Android, iOS e Windows. È possibile seguire l'esercitazione collegata per creare l'app nativa (e installare gli strumenti Xamarin gratuiti necessari) e scaricare la soluzione di esempio Xamarin. L'esempio Xamarin include un progetto di servizi API Web core ASP.NET, che sostituisce l'app ASP.NET Core di questo articolo (senza alcuna modifica richiesta dal client).

Applicazione To Do Rest in esecuzione su uno smartphone Android

Funzionalità

L'app TodoREST supporta l'inserzione, l'aggiunta, l'eliminazione e l'aggiornamento di elementi attività. Ogni elemento include un ID, un nome, delle note e una proprietà che indica se è stato eseguito.

Nell'esempio precedente la visualizzazione principale degli elementi elenca il nome di ogni elemento e indica se è stata eseguita con un segno di spunta.

Se si tocca l'icona + viene visualizzata una finestra di dialogo per l'aggiunta di elementi:

Finestra di dialogo per l'aggiunta di elementi

Se si tocca un elemento sulla schermata dell'elenco principale viene visualizzata una finestra di dialogo di modifica in cui è possibile modificare le opzioni Name (Nome), Notes (Note) e Done (Fatto) dell'elemento oppure eliminare l'elemento stesso:

Finestra di dialogo di modifica dell'elemento

Per testarla manualmente rispetto all'app ASP.NET Core creata nella sezione successiva in esecuzione nel computer, aggiornare la costante dell'app RestUrl .

Gli emulatori Android non vengono eseguiti nel computer locale e usano un indirizzo IP di loopback (10.0.2.2) per comunicare con il computer locale. Usare Xamarin.Essentials DeviceInfo per rilevare il sistema operativo in esecuzione per usare l'URL corretto.

Passare al TodoREST progetto e aprire il Constants.cs file. Il Constants.cs file contiene la configurazione seguente.

using Xamarin.Essentials;
using Xamarin.Forms;

namespace TodoREST
{
    public static class Constants
    {
        // URL of REST service
        //public static string RestUrl = "https://YOURPROJECT.azurewebsites.net:8081/api/todoitems/{0}";

        // URL of REST service (Android does not use localhost)
        // Use http cleartext for local deployment. Change to https for production
        public static string RestUrl = DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" : "http://localhost:5000/api/todoitems/{0}";
    }
}

Facoltativamente, è possibile distribuire il servizio Web in un servizio cloud, ad esempio Azure e aggiornare .RestUrl

Creazione del progetto ASP.NET Core

Creare una nuova applicazione Web ASP.NET Core in Visual Studio. Scegliere il modello api Web. Assegnare al progetto il nome TodoAPI.

Finestra di dialogo Nuova applicazione Web ASP.NET Core con il modello di progetto API Web selezionato

L'app deve rispondere a tutte le richieste effettuate alla porta 5000, incluso il traffico HTTP non crittografato per il client mobile. UseHttpsRedirection L'aggiornamento Startup.cs non viene eseguito nello sviluppo:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        // For mobile apps, allow http traffic.
        app.UseHttpsRedirection();
    }

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Nota

Eseguire l'app direttamente, anziché dietro IIS Express. IIS Express ignora le richieste non locali per impostazione predefinita. Eseguire dotnet run da un prompt dei comandi o scegliere il profilo del nome dell'app dall'elenco a discesa Destinazione di debug sulla barra degli strumenti di Visual Studio.

Aggiungere una classe modello che rappresenti gli elementi attività. Contrassegnare i campi obbligatori con l'attributo [Required] :

using System.ComponentModel.DataAnnotations;

namespace TodoAPI.Models
{
    public class TodoItem
    {
        [Required]
        public string ID { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public string Notes { get; set; }

        public bool Done { get; set; }
    }
}

I metodi dell'API richiedono un approccio per l'uso dei dati. Usare la stessa interfaccia ITodoRepository usata nell'esempio originale di Xamarin:

using System.Collections.Generic;
using TodoAPI.Models;

namespace TodoAPI.Interfaces
{
    public interface ITodoRepository
    {
        bool DoesItemExist(string id);
        IEnumerable<TodoItem> All { get; }
        TodoItem Find(string id);
        void Insert(TodoItem item);
        void Update(TodoItem item);
        void Delete(string id);
    }
}

Per questo esempio, l'implementazione usa solo una raccolta privata di elementi:

using System.Collections.Generic;
using System.Linq;
using TodoAPI.Interfaces;
using TodoAPI.Models;

namespace TodoAPI.Services
{
    public class TodoRepository : ITodoRepository
    {
        private List<TodoItem> _todoList;

        public TodoRepository()
        {
            InitializeData();
        }

        public IEnumerable<TodoItem> All
        {
            get { return _todoList; }
        }

        public bool DoesItemExist(string id)
        {
            return _todoList.Any(item => item.ID == id);
        }

        public TodoItem Find(string id)
        {
            return _todoList.FirstOrDefault(item => item.ID == id);
        }

        public void Insert(TodoItem item)
        {
            _todoList.Add(item);
        }

        public void Update(TodoItem item)
        {
            var todoItem = this.Find(item.ID);
            var index = _todoList.IndexOf(todoItem);
            _todoList.RemoveAt(index);
            _todoList.Insert(index, item);
        }

        public void Delete(string id)
        {
            _todoList.Remove(this.Find(id));
        }

        private void InitializeData()
        {
            _todoList = new List<TodoItem>();

            var todoItem1 = new TodoItem
            {
                ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
                Name = "Learn app development",
                Notes = "Take Microsoft Learn Courses",
                Done = true
            };

            var todoItem2 = new TodoItem
            {
                ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
                Name = "Develop apps",
                Notes = "Use Visual Studio and Visual Studio for Mac",
                Done = false
            };

            var todoItem3 = new TodoItem
            {
                ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
                Name = "Publish apps",
                Notes = "All app stores",
                Done = false,
            };

            _todoList.Add(todoItem1);
            _todoList.Add(todoItem2);
            _todoList.Add(todoItem3);
        }
    }
}

Configurare l'implementazione in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ITodoRepository, TodoRepository>();
    services.AddControllers();
}

Creazione del controller

Aggiungere un nuovo controller al progetto, TodoItemsController. Deve ereditare da ControllerBase. Aggiungere un Route attributo per indicare che il controller gestisce le richieste effettuate ai percorsi che iniziano con api/todoitems. Il token [controller] nella route viene sostituito dal nome del controller (omettendo il suffisso Controller) ed è particolarmente utile per le route globali. Altre informazioni sul routing.

Per funzionare, il controller richiede un ITodoRepository. Richiedere un'istanza di questo tipo tramite il costruttore del controller. In fase di esecuzione, questa istanza viene fornita usando il supporto del framework per l'inserimento delle dipendenze.

[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
    private readonly ITodoRepository _todoRepository;

    public TodoItemsController(ITodoRepository todoRepository)
    {
        _todoRepository = todoRepository;
    }

Questa API supporta quattro verbi HTTP diversi per eseguire operazioni CRUD (Create, Read, Update, Delete - Creazione, Lettura, Aggiornamento, Eliminazione) nell'origine dati. Il più semplice è l'operazione di lettura, che corrisponde a una richiesta HTTP GET .

Testare l'API usando curl

È possibile testare il metodo API usando un'ampia gamma di strumenti. Per questa esercitazione vengono usati gli strumenti da riga di comando open source seguenti:

  • curl: trasferisce i dati usando vari protocolli, tra cui HTTP e HTTPS. Curl viene usato in questa esercitazione per chiamare l'API usando i metodi GETHTTP , POST, PUTe DELETE.
  • jq: processore JSON usato in questa esercitazione per formattare i dati JSON in modo che sia facile leggere dalla risposta dell'API.

Installare curl e jq

curl è preinstallato in macOS e viene usato direttamente all'interno dell'applicazione terminale macOS. Per altre informazioni sull'installazione di curl, vedere il sito Web ufficiale curl.

Jq può essere installato da Homebrew dal terminale:

Installare Homebrew, se non è già installato, con il comando seguente:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Seguire le istruzioni presentate dal programma di installazione.

Installare jq usando Homebrew con il comando seguente:

brew install jq

Per altre informazioni sull'installazione di Homebrew e jq, vedere Homebrew e jq.

Lettura degli elementi

La richiesta di un elenco di elementi viene eseguita con una richiesta GET al metodo List. L'attributo [HttpGet] del metodo List indica che questa azione deve gestire solo le richieste GET. La route di questa azione è la route specificata nel controller. Non è necessario usare il nome dell'azione come parte della route. È sufficiente garantire che ogni azione disponga di una route univoca e non ambigua. Gli attributi di routing possono essere applicati sia a livello di controller che di metodo per definire route specifiche.

[HttpGet]
public IActionResult List()
{
    return Ok(_todoRepository.All);
}

Nel terminale chiamare il comando curl seguente:

curl -v -X GET 'http://localhost:5000/api/todoitems/' | jq

Il comando curl precedente include i componenti seguenti:

  • -v: attiva la modalità dettagliata, fornendo informazioni dettagliate sulla risposta HTTP ed è utile per il test e la risoluzione dei problemi delle API.
  • -X GET: specifica l'uso del metodo HTTP GET per la richiesta. Anche se curl spesso può dedurre il metodo HTTP previsto, questa opzione lo rende esplicito.
  • 'http://localhost:5000/api/todoitems/': è l'URL di destinazione della richiesta. In questa istanza si tratta di un REST endpoint API.
  • | jq: questo segmento non è correlato direttamente a curl. La pipe | è un operatore della shell che accetta l'output dal comando a sinistra e lo "pipe" al comando a destra. jq è un processore JSON della riga di comando. Anche se non è necessario, jq rende i dati JSON restituiti più facili da leggere.

Il List metodo restituisce un codice di risposta 200 OK e tutti gli elementi Todo, serializzati come JSON:

[
  {
    "id": "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
    "name": "Learn app development",
    "notes": "Take Microsoft Learn Courses",
    "done": true
  },
  {
    "id": "b94afb54-a1cb-4313-8af3-b7511551b33b",
    "name": "Develop apps",
    "notes": "Use Visual Studio and Visual Studio for Mac",
    "done": false
  },
  {
    "id": "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
    "name": "Publish apps",
    "notes": "All app stores",
    "done": false
  }
]

Creazione di elementi

Per convenzione, la creazione di nuovi elementi di dati viene mappata al verbo HTTP POST . Al Create metodo è applicato un [HttpPost] attributo e accetta un'istanza TodoItem . Poiché l'argomento item viene passato nel corpo di POST, questo parametro specifica l'attributo [FromBody] .

All'interno del metodo, l'elemento viene verificato per validità ed esistenza precedente nell'archivio dati. Se non si verificano problemi, l'elemento viene aggiunto tramite il repository. La verifica ModelState.IsValid esegue la convalida del modello e deve essere eseguita in ogni metodo dell'API che accetta input utente.

[HttpPost]
public IActionResult Create([FromBody]TodoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        bool itemExists = _todoRepository.DoesItemExist(item.ID);
        if (itemExists)
        {
            return StatusCode(StatusCodes.Status409Conflict, ErrorCode.TodoItemIDInUse.ToString());
        }
        _todoRepository.Insert(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
    }
    return Ok(item);
}

L'esempio usa un oggetto enum contenente i codici di errore passati al client mobile:

public enum ErrorCode
{
    TodoItemNameAndNotesRequired,
    TodoItemIDInUse,
    RecordNotFound,
    CouldNotCreateItem,
    CouldNotUpdateItem,
    CouldNotDeleteItem
}

Nel terminale testare l'aggiunta di nuovi elementi chiamando il comando curl seguente usando il POST verbo e specificando il nuovo oggetto in formato JSON nel corpo della richiesta.

curl -v -X POST 'http://localhost:5000/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
  "id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
  "name": "A Test Item",
  "notes": "asdf",
  "done": false
}' | jq

Il comando curl precedente include le opzioni seguenti:

  • --header 'Content-Type: application/json': imposta l'intestazione Content-Type su application/json, che indica che il corpo della richiesta contiene dati JSON.
  • --data '{...}': invia i dati specificati nel corpo della richiesta.

Il metodo restituisce l'elemento appena creato nella risposta.

Aggiornamento degli elementi

La modifica dei record viene eseguita usando le richieste HTTP PUT . Fatta eccezione per questa modifica, il metodo Edit è quasi identico a Create. Se il record non viene trovato, l'azione Edit restituisce una NotFound risposta (404).

[HttpPut]
public IActionResult Edit([FromBody] TodoItem item)
{
    try
    {
        if (item == null || !ModelState.IsValid)
        {
            return BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
        }
        var existingItem = _todoRepository.Find(item.ID);
        if (existingItem == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _todoRepository.Update(item);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
    }
    return NoContent();
}

Per eseguire il test con curl, modificare il verbo in PUT. Specificare i dati dell'oggetto aggiornati nel Corpo della richiesta.

curl -v -X PUT 'http://localhost:5000/api/todoitems/' \
--header 'Content-Type: application/json' \
--data '{
  "id": "6bb8b868-dba1-4f1a-93b7-24ebce87e243",
  "name": "A Test Item",
  "notes": "asdf",
  "done": true
}' | jq

Se va a buon fine, questo metodo restituisce una risposta NoContent (204) per coerenza con l'API preesistente.

Eliminazione di elementi

L'eliminazione dei record viene eseguita effettuando DELETE richieste al servizio e passando l'ID dell'elemento da eliminare. Come per gli aggiornamenti, le richieste di elementi che non esistono ricevono NotFound risposte. In caso contrario, una richiesta con esito positivo restituisce una NoContent risposta (204).

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
    try
    {
        var item = _todoRepository.Find(id);
        if (item == null)
        {
            return NotFound(ErrorCode.RecordNotFound.ToString());
        }
        _todoRepository.Delete(id);
    }
    catch (Exception)
    {
        return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
    }
    return NoContent();
}

Testare con curl modificando il verbo DELETE HTTP in e aggiungendo l'ID dell'oggetto dati da eliminare alla fine dell'URL. Non è necessario alcun elemento nel corpo della richiesta.

curl -v -X DELETE 'http://localhost:5000/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'

Impedire l'over-post

Attualmente l'app di esempio espone l'intero TodoItem oggetto. Le app di produzione limitano in genere i dati di input e restituiti usando un subset del modello. Esistono diversi motivi alla base di questa situazione e la sicurezza è una delle principali. Il subset di un modello viene in genere definito DTO (Data Transfer Object), modello di input o modello di visualizzazione. DTO viene usato in questo articolo.

Un DTO può essere usato per:

  • Impedire l'over-post.
  • Nascondere le proprietà che i client non devono visualizzare.
  • Omettere alcune proprietà per ridurre le dimensioni del payload.
  • Appiattire gli oggetti grafici che contengono oggetti annidati. Gli oggetti grafici appiattiti possono essere più pratici per i client.

Per illustrare l'approccio DTO, vedere Impedire l'over-post

Convenzioni comuni dell'API Web

Man mano che si sviluppano i servizi back-end per l'app, è consigliabile creare un set coerente di convenzioni o criteri per la gestione delle problematiche trasversali. Nel servizio illustrato in precedenza, ad esempio, le richieste di record specifici che non sono state trovate hanno ricevuto una NotFound risposta, anziché una BadRequest risposta. Analogamente, i comandi inoltrati a questo servizio che trasmettono tipi associati al modello verificano sempre ModelState.IsValid e restituiscono BadRequest per i tipi di modello non validi.

Dopo aver identificato criteri comuni per le API è in genere possibile incapsulare tali criteri in un filtro. Altre informazioni su come incapsulare criteri comuni per le API nelle applicazioni ASP.NET Core MVC.

Risorse aggiuntive