Rückgabetypen für Controlleraktionen in der ASP.NET Core-Web-API

Von Scott Addie

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

In ASP.NET Core haben Sie die folgenden Optionen für Rückgabetypen für Web-API-Controlleraktionen:

In diesem Artikel wird die Verwendung der einzelnen Rückgabetypen erklärt.

Spezifischer Typ

Die einfachste Aktion gibt einen primitiven oder komplexen Datentyp zurück (z.B. string oder einen benutzerdefinierten Objekttyp). Die folgende Aktion gibt eine Auflistung benutzerdefinierter Product-Objekte zurück:

[HttpGet]
public List<Product> Get() =>
    _repository.GetProducts();

Ohne bekannte Bedingungen, gegen die während der Ausführung einer Aktion Schutzmaßnahmen ergriffen werden müssen, reicht die Rückgabe eines spezifischen Typs möglicherweise aus. Die vorherige Aktion akzeptiert keine Parameter, weshalb eine Validierung von Parametereinschränkungen nicht erforderlich ist.

Wenn mehrere Rückgabetypen möglich sind, ist es üblich, einen ActionResult Rückgabetyp mit dem primitiven oder komplexen Rückgabetyp zu kombinieren. Für diesen Aktionstyp sind entweder IActionResult oder <T> ActionResult erforderlich. In diesem Dokument werden mehrere Beispiele für mehrere Rückgabetypen bereitgestellt.

Zurückgeben von IEnumerable <T> oder IAsyncEnumerable<T>

Wenn eine Aktion in ASP.NET Core 2.2 und früher IEnumerable<T> zurückgibt, führt dies zu einer synchronen Sammlungsiteration durch das Serialisierungsprogramm. Das Ergebnis sind die Blockierung von Aufrufen und die potenzielle Außerkraftsetzung des Threadpools. Stellen Sie sich zur Veranschaulichung vor, dass der Entity Framework-Core (EF) für die Datenzugriffsanforderungen der Web-API verwendet wird. Der Rückgabetyp der folgenden Aktion wird während der Serialisierung synchron aufgezählt:

public IEnumerable<Product> GetOnSaleProducts() =>
    _context.Products.Where(p => p.IsOnSale);

Um synchrone Enumeration und blockierendes Warten auf die Datenbank in ASP.NET Core 2.2 und früher zu vermeiden, rufen Sie ToListAsync auf:

public async Task<IEnumerable<Product>> GetOnSaleProducts() =>
    await _context.Products.Where(p => p.IsOnSale).ToListAsync();

In ASP.NET Core 3.0 und höher gilt Folgendes, wenn eine Aktion IAsyncEnumerable<T> zurückgibt:

  • Es kommt nicht mehr zu synchroner Iteration.
  • Gleiche Effizienz wie bei Rückgabe von IEnumerable<T>.

ASP.NET Core 3.0 und höher puffert das Ergebnis der folgenden Aktion, bevor es dem Serialisierungsprogramm bereitgestellt wird:

public IEnumerable<Product> GetOnSaleProducts() =>
    _context.Products.Where(p => p.IsOnSale);

Sie sollten den Rückgabetyp der Signatur der Aktion als IAsyncEnumerable<T> deklarieren, um die asynchrone Iteration sicherzustellen. Letztendlich basiert der Iterationsmodus auf dem zugrunde liegenden konkreten Typ, der zurückgegeben wird. MVC puffert automatisch jeden konkreten Typ, der IAsyncEnumerable<T> implementiert.

Sehen Sie sich die folgende Aktion an, die den Verkaufspreis enthaltende Produktdatensätze als IEnumerable<Product> zurückgibt:

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _repository.GetProducts();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Die IAsyncEnumerable<Product>-Entsprechung der vorherigen Aktion lautet wie folgt:

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _repository.GetProductsAsync();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

Beide vorangehenden Aktionen führen ab ASP.NET Core 3.0 nicht mehr zu Blockierungen.

IActionResult-Typ

Der Rückgabetyp IActionResult eignet sich in Fällen, in denen mehrere ActionResult-Rückgabetypen in einer Aktion möglich sind. Die ActionResult-Typen stellen verschiedene HTTP-Statuscodes dar. Alle nicht abstrakten Klassen, die von ActionResult abgeleitet werden, qualifizieren sich als gültiger Rückgabetyp. Einige allgemeine Rückgabetypen in dieser Kategorie sind BadRequestResult (400), NotFoundResult (404) und OkObjectResult (200). Alternativ können Hilfsmethoden in der ControllerBase-Klasse verwendet werden, um ActionResult-Typen aus einer Aktion zurückzugeben. return BadRequest(); ist beispielsweise eine Kurzform von return new BadRequestResult();.

Da es mehrere Rückgabetypen und Pfade in dieser Art von Aktion gibt, ist eine unterschiedliche Verwendung des [ProducesResponseType] Attributs erforderlich. Dieses Attribut erzeugt aussagekräftigere Antwortdetails für Web-API-Hilfeseiten, die mit Tools wie Swagger erstellt wurden. [ProducesResponseType] gibt die bekannten Typen und HTTP-Statuscodes an, die von der Aktion zurückgegeben werden sollen.

Synchrone Aktion

Bei der folgenden synchronen Aktion gibt es zwei mögliche Rückgabetypen:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return Ok(product);
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return Ok(product);
}

In der vorhergehenden Aktion geschieht Folgendes:

  • Ein 404-Statuscode wird zurückgegeben, wenn das durch id dargestellte Produkt im zugrunde liegenden Datenspeicher nicht vorhanden ist. Die NotFound-Hilfsmethode wird als Kurzform für return new NotFoundResult(); aufgerufen.
  • Ein 200-Statuscode wird mit dem Product-Objekt zurückgegeben, wenn das Produkt vorhanden ist. Die Ok-Hilfsmethode wird als Kurzform für return new OkObjectResult(product); aufgerufen.

Asynchrone Aktion

Bei der folgenden asynchronen Aktion gibt es zwei mögliche Rückgabetypen:

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}
[HttpPost]
[Consumes("application/json")]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync([FromBody] Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

In der vorhergehenden Aktion geschieht Folgendes:

  • Ein 400-Statuscode wird zurückgegeben, wenn die Produktbeschreibung „XYZ-Widget“ enthält. Die BadRequest-Hilfsmethode wird als Kurzform für return new BadRequestResult(); aufgerufen.
  • Ein 201-Statuscode wird von der Hilfsmethode CreatedAtAction generiert, wenn ein Produkt erstellt wird. Eine Alternative zum Aufrufen von CreatedAtAction ist return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. In diesem Codepfad wird das Product-Objekt im Antworttext bereitgestellt. Ein Location-Antwortheader, der die neu erstellte Produkt-URL enthält, wird bereitgestellt.

Das folgende Modell gibt beispielsweise an, dass Anforderungen die Name- und Description-Eigenschaften einbinden müssen. Wenn Name und Description in der Anforderung nicht bereitgestellt werden, tritt bei der Modellvalidierung ein Fehler auf.

public class Product
{
    public int Id { get; set; }

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

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

Wenn das [ApiController] -Attribut in ASP.NET Core 2.1 oder höher angewendet wird, führen Modellvalidierungsfehler zu einem 400-Statuscode. Weitere Informationen finden Sie unter Automatische HTTP 400-Antworten.

ActionResult im Vergleich zu IActionResult

Im folgenden Abschnitt wird mit ActionResult verglichen. IActionResult

<T>ActionResult-Typ

ASP.NET Core enthält den ActionResult-Rückgabetyp <T> für Web-API-Controlleraktionen. Damit wird die Rückgabe eines von ActionResult abgeleiteten Typs oder eines spezifischen Typs ermöglicht. ActionResult<T> besitzt gegenüber dem IActionResult-Typ die folgenden Vorteile:

  • Die [ProducesResponseType] -Eigenschaft des Attributs Type kann ausgeschlossen werden. [ProducesResponseType(200, Type = typeof(Product))] wird beispielsweise zu [ProducesResponseType(200)] vereinfacht. Der erwartete Rückgabetyp der Aktion wird stattdessen von T in ActionResult<T> abgeleitet.
  • Implizite Umwandlungsoperatoren unterstützen die Konvertierung von T und ActionResult in ActionResult<T>. T wird in ObjectResult konvertiert, wodurch return new ObjectResult(T); zu return T; vereinfacht wird.

C# unterstützt keine impliziten Umwandlungsoperatoren in Schnittstellen. Daher ist die Konvertierung der Schnittstelle in einen konkreten Typ erforderlich, um ActionResult<T> verwenden zu können. Die Verwendung von IEnumerable im folgenden Beispiel funktioniert also nicht:

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

Eine Möglichkeit zur Korrektur des oben stehenden Codes besteht darin, _repository.GetProducts().ToList(); zurückzugeben.

Die meisten Aktionen haben einen bestimmten Rückgabetyp. Während der Ausführung der Aktion können unerwartete Bedingungen auftreten, wodurch der spezifische Typ nicht zurückgegeben wird. So kann beispielsweise die Modellvalidierung des Eingabeparameters einer Aktion fehlschlagen. In diesem Fall wird üblicherweise der entsprechende ActionResult-Typ anstatt des spezifischen Typs zurückgegeben.

Synchrone Aktion

Bei der folgenden synchronen Aktion gibt es zwei mögliche Rückgabetypen:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return product;
}

In der vorhergehenden Aktion geschieht Folgendes:

  • Ein 404-Statuscode wird zurückgegeben, wenn das Produkt in der Datenbank nicht vorhanden ist.
  • Ein 200-Statuscode wird mit dem entsprechenden Product-Objekt zurückgegeben, wenn das Produkt vorhanden ist. Vor ASP.NET Core 2.1 hätte die Zeile return product; stattdessen return Ok(product); lauten müssen.

Asynchrone Aktion

Bei der folgenden asynchronen Aktion gibt es zwei mögliche Rückgabetypen:

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

In der vorhergehenden Aktion geschieht Folgendes:

  • Ein 400-Statuscode (BadRequest) wird durch die ASP.NET Core-Runtime zurückgegeben, wenn:
    • Das [ApiController] Attribut wurde angewendet, und die Modellvalidierung schlägt fehl.
    • Die Produktbeschreibung „XYZ-Widget“ enthält.
  • Ein 201-Statuscode wird von der Methode CreatedAtAction generiert, wenn ein Produkt erstellt wird. In diesem Codepfad wird das Product-Objekt im Antworttext bereitgestellt. Ein Location-Antwortheader, der die neu erstellte Produkt-URL enthält, wird bereitgestellt.

Zusätzliche Ressourcen