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 Herstellen einer Verbindung zwischen lokalen Webdienste von iOS-Simulatoren und Android-Emulatoren finden Sie unter Herstellen von Verbindungen mit lokalen Webdiensten von iOS-Simulatoren und Android-Emulatoren.

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. Darin wird die Xamarin.Forms-App TodoRest als nativer Client verwendet, die separate native Clients für Android, iOS und Windows enthält. Sie können das verlinkten Tutorial befolgen, um die native App zu erstellen (und die erforderlichen kostenfreien Xamarin-Tools zu installieren) und um die Xamarin-Beispielprojektmappe herunterzuladen. Das Xamarin-Beispiel umfasst ein Dienstprojekt der ASP.NET Core-Web-API, das die ASP.NET Core-App aus diesem Artikel ersetzt (dabei sind keine Änderungen durch den Client erforderlich).

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

Features

Die TodoREST-App unterstützt das Auflisten, Hinzufügen, Löschen und Aktualisieren von To-Do-Elementen. 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 ein Dialogfeld zum Hinzufügen eines Elements geöffnet:

Dialogfeld „Element hinzufügen“

Durch Tippen auf ein Element auf dem Bildschirm mit der Hauptliste wird ein Dialogfeld zur Bearbeitung geöffnet, in dem die Einstellungen „Name“, „Hinweise“, und „Fertig“ für das Element geändert oder das Element gelöscht werden kann:

Dialogfeld „Element bearbeiten“

Wenn Sie diese selbst mit der im nächsten Abschnitt erstellten ASP.NET Core-App testen möchten, die auf Ihrem Computer ausgeführt wird, aktualisieren Sie die RestUrl-Konstante der App.

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 Xamarin.Essentials DeviceInfo, um zu ermitteln, welches Betriebssystem auf dem System ausgeführt wird, damit die richtige URL verwendet wird.

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

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

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 an Port 5000 gesendet werden, einschließlich Klartext-HTTP-Datenverkehr für unseren mobilen Client. Aktualisieren Sie Startup.cs, damit UseHttpsRedirection nicht im Entwicklungsmodus ausgeführt wird:

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

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

Die API-Methoden müssen mit Daten arbeiten können. Verwenden Sie die gleiche ITodoRepository-Schnittstelle, die im ursprünglichen Xamarin-Beispiel verwendet wird:

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

In diesem Beispiel verwendet die Implementierung nur eine private Sammlung von Elementen:

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

Konfigurieren der Implementierung in Startup.cs:

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

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 Homebrew- und jq-Installation 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 'http://localhost:5000/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.
  • 'http://localhost:5000/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 Methode List 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 for Mac",
    "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
}

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

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

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

Datensätze werden mithilfe von HTTP PUT-Anforderungen geändert. 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 '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

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 | MSDN

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 'http://localhost:5000/api/todoitems/6bb8b868-dba1-4f1a-93b7-24ebce87e243'

Vermeiden von Overposting

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 einige Eigenschaften weg, um die Nutzdatengröße zu verringern.
  • 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 Konventionen für die Web-API

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.

Zusätzliche Ressourcen