Freigeben über


Erstellen von Back-End-Diensten für native mobile Apps mit ASP.NET Core

Von James Montemagno

Mobile Apps können mit Back-End-Diensten von ASP.NET Core kommunizieren. Anweisungen zum Verbinden lokaler Webdienste von iOS-Simulatoren und Android-Emulatoren finden Sie unter Herstellen einer Verbindung mit lokalen Webdiensten von Android-Emulatoren und iOS-Simulatoren.

Anzeigen oder Herunterladen von Beispielcode für Back-End-Dienste

Die native mobile Beispiel-App

In diesem Tutorial wird veranschaulicht, wie Back-End-Dienste mit ASP.NET Core zur Unterstützung nativer mobiler Apps erstellt werden. Sie verwendet eine .NET MAUI App als nativen Client. Das Beispiel enthält ein ASP.NET Core Web API-Dienstprojekt, dessen Aufbau in diesem Artikel Schritt für Schritt erläutert wird.

Ausführen der ToDoRest-App auf einem Android-Smartphone

Features

Die TodoREST-App unterstützt das Auflisten, Hinzufügen, Löschen und Aktualisieren von Todoelementen. Jedes Element verfügt über eine ID, einen Namen, Hinweise und über eine Eigenschaft, die angibt, ob ein Element abgeschlossen ist.

Im letzten Beispiel werden der Hauptansicht der Elemente die Namen der einzelnen Elemente aufgeführt. Sie sind mit einem Häkchen versehen, wenn sie abgeschlossen sind.

Durch Tippen auf das + Symbol wird zur Seite "Element hinzufügen" navigiert:

Dialogfeld „Element hinzufügen“

Durch Tippen auf ein Element auf der Hauptseite wird zu einer Bearbeitungsseite navigiert, auf der der Name, die Notizen und die fertigen Einstellungen geändert werden können, oder das Element kann gelöscht werden:

Dialogfeld „Element bearbeiten“

Aktualisieren Sie die Konstante RestUrl der App, um sie selbst mit der im nächsten Abschnitt erstellten ASP.NET Core-App zu testen, wenn Sie sie online hosten. Andernfalls kommuniziert die App mit der ASP.NET Core-App, die lokal auf Ihrem Computer gehostet wird.

Android-Emulatoren werden nicht auf dem lokalen Computer ausgeführt und verwenden eine Loopback-IP (10.0.2.2) für die Kommunikation mit dem lokalen Computer. Verwenden Sie .NET MAUIdie DeviceInfo-Klasse , um das Betriebssystem zu erkennen, auf dem die App ausgeführt wird, um die richtige URL zu verwenden.

Navigieren Sie zum TodoREST-Projekt, und öffnen Sie die Datei Constants.cs. Die Datei Constants.cs enthält die folgende Konfiguration:

namespace TodoREST
{
    public static class Constants
    {
        // URL of REST service
        //public static string RestUrl = "https://dotnetmauitodorest.azurewebsites.net/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 LocalhostUrl = DeviceInfo.Platform == DevicePlatform.Android ? "10.0.2.2" : "localhost";
        public static string Scheme = "https"; // or http
        public static string Port = "5001";
        public static string RestUrl = $"{Scheme}://{LocalhostUrl}:{Port}/api/todoitems/{{0}}";
    }
}

Optional können Sie den Webdienst in einem Clouddienst wie Azure bereitstellen und RestUrl aktualisieren.

Erstellen des ASP.NET Core-Projekts

Erstellen Sie in Visual Studio eine neue ASP.NET Core-Webanwendung. Wählen Sie die Web-API-Vorlage aus. Geben Sie dem Projekt den Namen TodoAPI.

Dialogfeld „Neue ASP.NET-Webanwendung“ mit ausgewählter Web-API-Projektvorlage

Die App sollte auf alle Anforderungen reagieren, die über HTTPS an Port 5001 vorgenommen wurden.

Hinweis

Führen Sie die App direkt und nicht hinter IIS Express aus. IIS Express ignoriert standardmäßig nicht lokale Anforderungen. Führen Sie dotnet run über eine Eingabeaufforderung aus, oder wählen Sie aus der Dropdownliste „Debugziel“ in der Symbolleiste von Visual Studio das App-Namensprofil aus.

Fügen Sie eine Modellklasse hinzu, in der die To-Do-Elemente dargestellt werden sollen. Markieren Sie erforderliche Felder mit dem Attribut [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; }
    }
}

API-Methoden erfordern, dass das Definieren erfolgt, um mit Daten zu arbeiten. Verwenden Sie dieselbe ITodoRepository Schnittstelle, die im Beispiel verwendet wird:

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);
    }
}

Für dieses Beispiel verwendet die Repositoryimplementierung nur eine private Sammlung von Elementen:

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 Code",
                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);
        }
    }
}

Konfigurieren der Implementierung in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddSingleton<TodoAPI.Interfaces.ITodoRepository, TodoAPI.Services.TodoRepository>();
builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Erstellen des Controllers

Fügen Sie dem Projekt einen neuen Controller hinzu: TodoItemsController. Er sollte von ControllerBase erben. Fügen Sie das Attribut Route hinzu, um anzugeben, dass der Controller Anforderungen für Pfade bearbeitet, die mit api/todoitems beginnen. Das Token [controller] in der Route wird durch den Namen des Controllers ersetzt (dabei wird das Suffix Controller ausgelassen) und ist für globale Routen besonders nützlich. Weitere Informationen zu Routing.

Damit der Controller funktioniert, ist eine ITodoRepository-Schnittstelle erforderlich. Fordern Sie über den Konstruktor des Controllers eine Instanz dieses Typs an. Zur Laufzeit wird diese Instanz über die Frameworkunterstützung für Dependency Injection bereitgestellt.

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

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

Diese API unterstützt vier verschiedene HTTP-Verben zur Durchführung von CRUD-Vorgängen für die Datenquelle (CRUD = Create, Read, Update, Delete; Erstellen, Lesen, Aktualisieren, Löschen). Am einfachsten ist der Lesevorgang, der einer HTTP GET-Anforderung entspricht.

Testen der API mit curl

Sie können die API-Methode mit einer Vielzahl von Tools testen. In diesem Tutorial werden die folgenden Open Source-Befehlszeilentools verwendet:

  • curl: Überträgt Daten mithilfe verschiedener Protokolle, einschließlich HTTP und HTTPS. curl wird in diesem Tutorial verwendet, um die API mit HTTP-Methoden GET, POST, PUT und DELETE aufzurufen.
  • jq: Ein JSON-Prozessor, der in diesem Lernprogramm zum Formatieren von JSON-Daten verwendet wird, sodass es einfach aus der API-Antwort zu lesen ist.

Installieren von curl und jq

curl ist unter macOS vorinstalliert und wird direkt in der macOS Terminal-Anwendung verwendet. Weitere Informationen zur Installation von curl finden Sie auf der offiziellen curl-Website.

jq kann von Homebrew aus dem Terminal installiert werden:

Installieren Sie Homebrew, falls es noch nicht installiert ist, mit dem folgenden Befehl:

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

Folgen Sie den Anweisungen des Installers.

Installieren Sie jq mithilfe von Homebrew mit dem folgenden Befehl:

brew install jq

Weitere Informationen zur Installation von Homebrew und jq finden Sie unter Homebrew und jq.

Lesen von Elementen

Die Anforderung einer Liste mit Elementen erfolgt über eine GET-Anforderung an die Methode List. Das Attribut [HttpGet] in der Methode List gibt an, dass diese Aktion nur GET-Anforderungen verarbeiten soll. Die Route für diese Aktion ist die auf dem Controller angegebene Route. Sie müssen den Aktionsnamen nicht unbedingt als Teil der Route verwenden. Sie müssen nur sicherstellen, dass die einzelnen Aktionen über eine eindeutige Route verfügen. Routingattribute können auf Controller- und auf Methodenebene für die Erstellung bestimmter Routen angewendet werden.

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

Rufen Sie im Terminal den folgenden curl-Befehl auf:

curl -v -X GET 'https://localhost:5001/api/todoitems/' | jq

Der vorherige curl-Befehl enthält die folgenden Komponenten:

  • -v: Aktiviert den ausführlichen Modus, der detaillierte Informationen über die HTTP-Antwort liefert und für API-Tests und die Fehlersuche nützlich ist.
  • -X GET: Gibt die Verwendung der HTTP-Methode GET für die Anforderung an. Während curl oft die beabsichtigte HTTP-Methode ableiten kann, macht diese Option sie explizit.
  • 'https://localhost:5001/api/todoitems/': Dies ist die Ziel-URL der Anforderung. In diesem Fall handelt es sich um einen REST-API-Endpunkt.
  • | jq: Dieses Segment ist nicht direkt mit curl verknüpft. Bei der Pipe | handelt es sich um einen Shelloperator, der die Ausgabe des Befehls auf der linken Seite annimmt und ihn auf der rechten Seite an den Befehl leitet. jq ist ein Befehlszeilen-JSON-Prozessor. jq ist zwar nicht erforderlich, erleichtert aber die Lesbarkeit der zurückgegebenen JSON-Daten.

Die List-Methode gibt den Antwortcode „200 OK“ und alle als JSON serialisierten To-Do-Elemente zurück.

[
  {
    "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 Code",
    "done": false
  },
  {
    "id": "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
    "name": "Publish apps",
    "notes": "All app stores",
    "done": false
  }
]

Erstellen von Elementen

Gemäß der Konvention wird die Erstellung neuer Datenelemente dem HTTP POST-Verb zugeordnet. Auf die Methode Create wird das Attribut [HttpPost] angewendet, und sie akzeptiert eine TodoItem-Instanz. Da das Argument item im POST-Text übergeben wird, gibt dieser Parameter das Attribut [FromBody] an.

Innerhalb der Methode wird überprüft, ob das Element gültig ist und ob es bereits im Datenspeicher vorhanden ist. Wenn keine Probleme auftreten, wird es über das Repository hinzugefügt. Bei der Überprüfung von ModelState.IsValid wird eine Modellvalidierung durchgeführt. Dies sollte bei jeder API-Methode geschehen, die Benutzereingaben akzeptiert.

[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);
}

Im Beispiel wird eine enum mit Fehlercodes verwendet, die an den mobilen Client übergeben werden:

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

Im Terminal testen Sie das Hinzufügen neuer Elemente, indem Sie den folgenden curl-Befehl mithilfe des POST-Verbs aufrufen und das neue Objekt im JSON-Format im Textkörper der Anfrage übergeben.

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

Der vorherige curl-Befehl enthält die folgenden Optionen:

  • --header 'Content-Type: application/json': Legt den Content-Type-Header auf application/json fest, was angibt, dass der Anforderungstext JSON-Daten enthält.
  • --data '{...}': Sendet die angegebenen Daten im Anforderungstext.

Die Methode gibt das neu erstellte Element in der Antwort zurück.

Aktualisieren von Elementen

Das Ändern von Datensätzen erfolgt mithilfe von HTTP-Anforderungen PUT . Abgesehen von dieser Änderung ist die Methode Edit mit der Methode Create weitgehend identisch. Die Aktion Edit gibt die Antwort „NotFound (404)“ zurück, wenn der Datensatz nicht gefunden werden kann.

[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();
}

Ändern Sie zum Testen mit curl das Verb in PUT. Geben Sie die aktualisierten Objektdaten in den Anforderungstext ein.

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

Diese Methode gibt bei erfolgreicher Ausführung die Antwort „NoContent (204)“ für die Konsistenz mit der bereits vorhandenen API zurück.

Löschen von Elementen

Datensätze werden gelöscht, indem DELETE-Anforderungen an den Dienst gestellt werden und die ID des zu löschenden Elements übergeben wird. Wie bei Updates erhalten Anforderungen bei nicht vorhandenen Elementen die Antwort NotFound. Bei erfolgreicher Ausführung einer Anforderung hingegen wird die Antwort „NoContent (204)“ zurückgegeben.

[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();
}

Testen Sie mit curl, indem Sie das HTTP-Verb zu DELETE ändern und die ID des zu löschenden Datenobjekts am Ende der URL anfügen. Im Textkörper der Anforderung ist nichts erforderlich.

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

Vermeidung von übermäßigen Postings

Derzeit macht die Beispiel-App das gesamte TodoItem-Objekt verfügbar. In den Produktions-Apps sind die Daten, die eingegeben und mithilfe einer Teilmenge des Modells zurückgegeben werden, in der Regel eingeschränkt. Hierfür gibt es mehrere Gründe, wobei die Sicherheit einer der Hauptgründe ist. Die Teilmenge eines Modells wird üblicherweise als Datenübertragungsobjekt (DTO, Data Transfer Object), Eingabemodell oder Anzeigemodell bezeichnet. In diesem Artikel wird das DTO verwendet.

Ein DTO kann für Folgendes verwendet werden:

  • Vermeiden Sie Overposting.
  • Ausblenden von Eigenschaften, die Clients nicht einsehen sollen
  • Lassen Sie bestimmte Eigenschaften weg, um die Nutzlastgröße zu reduzieren.
  • Vereinfachen von Objektgraphen, die geschachtelte Objekte enthalten Vereinfachte Objektgraphen können für Clients zweckmäßiger sein.

Weitere Informationen zum DTO-Ansatz finden Sie unter Verhindern von Overposting.

Allgemeine Web-API-Konventionen

Da Sie die Back-End-Dienste für Ihre App entwickeln, sollten Sie sich auch eine Reihe von konsistenten Konventionen oder Richtlinien für den Umgang mit bereichsübergreifenden Anliegen überlegen. Im vorher dargestellten Dienst haben Anforderungen für bestimmte Datensätze, die nicht gefunden werden konnten, beispielsweise die Antwort NotFound statt der Antwort BadRequest erhalten. Gleichermaßen haben für diesen Dienst durchgeführte Befehle, die gebundene Modelltypen übergeben haben, immer ModelState.IsValid überprüft und bei ungültigen Modelltypen eine BadRequest zurückgegeben.

Sobald Sie eine gängige Richtlinie für Ihre APIs ermittelt haben, können Sie diese in der Regel in einen Filter einschließen. Weitere Informationen zum Einschließen gängiger API-Richtlinien in ASP.NET Core MVC-Anwendungen.

Siehe auch