Udostępnij za pośrednictwem


Typy zwracane akcji kontrolera w interfejsie API sieci Web na platformie ASP.NET Core

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz artykuł w wersji .NET 9.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącym wydaniem, zobacz wersję tego artykułu dla .NET 9.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.

Wyświetl lub pobierz przykładowy kod (jak pobrać)

ASP.NET Core udostępnia następujące opcje dla typów zwracanych akcji kontrolera internetowego interfejsu API:

W tym artykule wyjaśniono, kiedy najbardziej odpowiednie jest użycie każdego typu zwracanego.

Określony typ

Najbardziej podstawowa akcja zwraca typ danych pierwotnych lub złożonych, na przykład string lub obiekt niestandardowy. Rozważmy następującą akcję, która zwraca kolekcję obiektów niestandardowych Product :

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

Bez znanych warunków, przed którymi trzeba by się chronić, zwrócenie określonego typu może wystarczyć. Poprzednia akcja nie akceptuje żadnych parametrów, więc weryfikacja ograniczeń parametrów nie jest wymagana.

Gdy możliwe jest wiele typów zwracanych, często miesza się ActionResult z typem zwracanym pierwotnym lub złożonym. Aby uwzględnić ten typ akcji, konieczne jest rozwiązanie IActionResult lub ActionResult<T> . W tym artykule przedstawiono kilka przykładów wielu typów zwracanych.

Zwracanie wartości IEnumerable<T> lub IAsyncEnumerable<T>

Zobacz Zwracanie IEnumerable<T> lub IAsyncEnumerable<T> dla zagadnień dotyczących wydajności.

ASP.NET Core buforuje wynik działań zwracających IEnumerable<T>, zanim zapisze je w odpowiedzi. Rozważ zadeklarowanie typu zwrotnego podpisu akcji jako IAsyncEnumerable<T>, aby zagwarantować asynchroniczną iterację. Ostatecznie tryb iteracji jest oparty na zwracanym podstawowym typie, a wybrany formater wpływa na sposób przetwarzania wyniku:

  • W przypadku korzystania z System.Text.Json programu formatującego mvC opiera się na obsłudze System.Text.Json dodanej w celu przesyłania strumieniowego wyniku.
  • W przypadku używania Newtonsoft.Json lub z formaterami XML-based wynik jest buforowany.

Rozważmy następującą akcję, która zwraca rekordy produktów w obniżonej cenie jako IEnumerable<Product>:

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

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

Odpowiednikiem IAsyncEnumerable<Product> poprzedniej akcji jest:

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

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

Typ IActionResult

Typ IActionResult jest odpowiedni, gdy w akcji możliwe są różne typy zwracania ActionResult. Typy ActionResult reprezentują różne kody stanu HTTP. Każda nie abstrakcyjna klasa wyprowadzająca z ActionResult kwalifikuje się jako prawidłowy typ zwracany. Niektóre typowe typy zwracane w tej kategorii to BadRequestResult (400), NotFoundResult (404) i OkObjectResult (200). Alternatywnie metody pomocnicze w klasie ControllerBase mogą służyć do zwracania typów ActionResult przez akcję. Na przykład return BadRequest(); jest skróconą formą return new BadRequestResult();.

Ponieważ istnieje wiele typów zwracanych i ścieżek w tym typie akcji, konieczne jest liberalne użycie atrybutu [ProducesResponseType] . Ten atrybut tworzy bardziej opisowe szczegóły odpowiedzi dla stron pomocy interfejsu API sieci Web generowanych przez narzędzia, takie jak Swagger. [ProducesResponseType] wskazuje znane typy i kody stanu HTTP, które mają być zwracane przez akcję.

Akcja synchroniczna

Rozważmy następującą akcję synchroniczną, w której istnieją dwa możliwe typy zwracane:

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt reprezentowany przez id nie istnieje w bazowym magazynie danych. Metoda pomocnicza NotFound jest wywoływana jako skrót dla return new NotFoundResult();.
  • Kod stanu 200 jest zwracany z obiektem Product , gdy produkt istnieje. Metoda ułatwiająca Ok jest wywoływana jako skrót dla return new OkObjectResult(product);.

Akcja asynchroniczna

Rozważ następującą akcję asynchroniczną, w której istnieją dwa możliwe typy zwracane:

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

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

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

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy opis produktu zawiera element "Widżet XYZ". Metoda BadRequest jest wywoływana jako skrót do return new BadRequestResult();.

  • Kod stanu 201 jest generowany przez metodę pomocniczą CreatedAtAction podczas tworzenia produktu. Poniższy kod jest alternatywą dla wywołania metody CreatedAtAction:

    return new CreatedAtActionResult(nameof(GetByIdAsync), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    W poprzednim przebiegu kodu obiekt Product jest przekazywany w treści odpowiedzi. Zostanie Location udostępniony nagłówek odpowiedzi zawierający adres URL nowo utworzonego produktu.

Na przykład poniższy model wskazuje, że żądania muszą zawierać właściwości Name oraz Description. Niepowodzenie podania Name i Description w żądaniu powoduje niepowodzenie walidacji modelu.

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

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

[ApiController] Jeśli atrybut zostanie zastosowany, błędy walidacji modelu spowodują wyświetlenie kodu stanu 400. Aby uzyskać więcej informacji, zobacz Automatyczne odpowiedzi HTTP 400.

ActionResult vs IActionResult

Poniższa sekcja porównuje ActionResult z IActionResult

ActionResult<T>, typ

ASP.NET Core zawiera typ zwracany ActionResult<T> dla akcji kontrolera internetowego interfejsu API. Umożliwia zwracanie typu pochodzącego z ActionResult lub zwracanie określonego typu. ActionResult<T> oferuje następujące korzyści w porównaniu z typem IActionResult:

  • Atrybutu [ProducesResponseType] właściwość Type można wykluczyć. Na przykład [ProducesResponseType(200, Type = typeof(Product))] jest uproszczony do [ProducesResponseType(200)]. Oczekiwany typ zwracany akcji jest wywnioskowany z elementu T w ActionResult<T>pliku .
  • Niejawne operatory rzutowania obsługują konwersję zarówno T, jak i ActionResult na ActionResult<T>. T konwertuje się na ObjectResult, co oznacza, że return new ObjectResult(T); jest uproszczone do return T;.

Język C# nie obsługuje niejawnych operatorów rzutowania w interfejsach. W rezultacie konieczna jest konwersja interfejsu na konkretny typ, aby korzystać z elementu ActionResult<T>. Na przykład użycie elementu IEnumerable w poniższym przykładzie nie działa:

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

Jedną z opcji naprawienia poprzedniego kodu jest zwrócenie _repository.GetProducts().ToList();.

Większość działań ma określony typ zwracany. Podczas wykonywania akcji mogą wystąpić nieoczekiwane warunki, w tym przypadku określony typ nie jest zwracany. Na przykład parametr wejściowy akcji może zakończyć się niepowodzeniem weryfikacji modelu. W takim przypadku często zwracany jest odpowiedni ActionResult typ zamiast określonego typu.

Akcja synchroniczna

Rozważ akcję synchroniczną, w której istnieją dwa możliwe typy wyników.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany z odpowiednim Product obiektem, gdy produkt istnieje.

Akcja asynchroniczna

Rozważ akcję asynchroniczną, w której istnieją dwa możliwe typy wartości zwracanych.

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

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

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

W poprzedniej akcji:

  • Kod stanu 400 (BadRequest) jest zwracany przez środowisko uruchomieniowe ASP.NET Core, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez CreatedAtAction metodę podczas tworzenia produktu. W tej ścieżce kodu Product obiekt jest udostępniany w treści odpowiedzi. Zostanie udostępniony nagłówek odpowiedzi Location, który zawiera adres URL nowo utworzonego produktu.

Typ HttpResults

Oprócz wbudowanych typów wyników MVC (IActionResult i ActionResult<T>) ASP.NET Core obejmuje typy HttpResults, które mogą być używane zarówno w Minimal APIs, jak i w Web API.

Różne od typów wyników specyficznych dla MVC: HttpResults

  • To implementacja wyników przetwarzana przez wywołanie metody IResult.ExecuteAsync.

  • Nie korzysta ze skonfigurowanych formaterów. Nie wykorzystanie skonfigurowanych formaterów oznacza:

    • Niektóre funkcje, takie jak Content negotiation nie są dostępne.
    • Produkcja Content-Type jest decydowana przez wdrożenie HttpResults.

Może HttpResults to być przydatne podczas udostępniania kodu między minimalnymi interfejsami API i internetowym interfejsem API.

Typ IResult

Microsoft.AspNetCore.Http.HttpResults Przestrzeń nazw zawiera klasy implementujące IResult interfejs. Interfejs IResult definiuje kontrakt reprezentujący wynik punktu końcowego HTTP. Statyczna klasa Results służy do tworzenia różnych IResult obiektów reprezentujących różne typy odpowiedzi.

W tabeli wyników wbudowanych przedstawiono powszechne narzędzia do obsługi wyników.

Spójrzmy na poniższy kod:

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany z odpowiednim Product obiektem, gdy produkt istnieje, i jest wygenerowany przez Results.Ok<T>().

Spójrzmy na poniższy kod:

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

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez Results.Created metodę podczas tworzenia produktu. W tej ścieżce kodu Product obiekt jest udostępniany w treści odpowiedzi. Nagłówek odpowiedzi Location zawierający adres URL nowo utworzonego produktu zostanie udostępniony.

Wyniki<TResult1, typ TResultN>

Statyczna klasa TypedResults zwraca konkretną IResult implementację, która umożliwia użycie IResult jako typu zwracanego. Użycie konkretnej IResult implementacji oferuje następującą korzyść nad typem IResult:

  • [ProducesResponseType] Wszystkie atrybuty można wykluczyć, ponieważ implementacja HttpResult automatycznie przyczynia się do metadanych punktu końcowego.

Gdy potrzebne są różne IResult typy zwracane, preferowane jest zwracanie Results<TResult1, TResultN> zamiast IResult. Preferowane jest zwracanie Results<TResult1, TResultN>, ponieważ typy związków ogólnych automatycznie zachowują metadane punktu końcowego.

Typy Results<TResult1, TResultN> unii implementują niejawne operatory rzutowania, aby kompilator mógł automatycznie konwertować typy określone w argumentach ogólnych na wystąpienie typu unii. Ma to dodatkową zaletę w postaci sprawdzania w czasie kompilacji, że obsługiwacz tras faktycznie zwraca tylko te wyniki, które deklaruje. Próba zwrócenia typu, który nie jest zadeklarowany jako jeden z argumentów ogólnych Results<>, skutkuje błędem kompilacji.

Spójrzmy na poniższy kod:

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany z odpowiednim Product obiektem, gdy produkt istnieje, a obiekt ten jest generowany przez TypedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez TypedResults.Created metodę podczas tworzenia produktu. W tej ścieżce kodu obiekt Product jest udostępniany w treści odpowiedzi. Zostanie Location udostępniony nagłówek odpowiedzi zawierający adres URL nowo utworzonego produktu.

Dodatkowe zasoby

Wyświetl lub pobierz przykładowy kod (jak pobrać)

ASP.NET Core udostępnia następujące opcje dla typów zwracanych akcji kontrolera internetowego interfejsu API:

W tym artykule wyjaśniono, kiedy najbardziej odpowiednie jest użycie każdego typu zwracanego.

Określony typ

Najbardziej podstawowa akcja zwraca typ danych pierwotnych lub złożonych, na przykład string lub obiekt niestandardowy. Rozważ następującą akcję, która zwraca kolekcję niestandardowych obiektów Product:

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

Jeśli nie ma znanych warunków, przed którymi trzeba się zabezpieczyć, zwrócenie określonego typu może wystarczyć. Poprzednia akcja nie akceptuje żadnych parametrów, więc weryfikacja ograniczeń parametrów nie jest wymagana.

Jeśli możliwe jest użycie wielu typów zwracanych, często miesza się typ zwracany ActionResult z typem zwracanym pierwotnym lub złożonym. Aby uwzględnić ten typ akcji, konieczne jest rozwiązanie IActionResult lub ActionResult<T> . W tym artykule przedstawiono kilka przykładów wielu typów zwracanych.

Zwróć IEnumerable<T> lub IAsyncEnumerable<T>

Zobacz Return IEnumerable<T> lub IAsyncEnumerable<T> dla uwag dotyczących wydajności.

ASP.NET Core buforuje wynik akcji, które zwracają IEnumerable<T>, zanim zapisze je w odpowiedzi. Rozważ zadeklarowanie typu zwracalnego podpisu akcji jako IAsyncEnumerable<T>, aby zagwarantować asynchroniczną iterację. Ostatecznie tryb iteracji jest oparty na zwracanym podstawowym typie, a wybrany formater wpływa na sposób przetwarzania wyniku:

  • W przypadku korzystania z System.Text.Json programu formatującego mvC opiera się na obsłudze System.Text.Json dodanej w celu przesyłania strumieniowego wyniku.
  • W przypadku używania Newtonsoft.Json lub z formaterami XML-based wynik jest buforowany.

Rozważ następującą akcję, która zwraca rekordy produktów po cenie promocyjnej jako IEnumerable<Product>:

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

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

Odpowiednikiem IAsyncEnumerable<Product> poprzedniej akcji jest:

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

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

Typ IActionResult

Typ zwracany IActionResult jest odpowiedni, gdy w akcji możliwe są różne ActionResult typy zwracane. Typy ActionResult reprezentują różne kody stanu HTTP. Każda nie-abstrakcyjna klasa wywodząca się z ActionResult kwalifikuje się jako prawidłowy typ zwracany. Niektóre typowe typy zwracane w tej kategorii to BadRequestResult (400), NotFoundResult (404) i OkObjectResult (200). Alternatywnie, w klasie ControllerBase można użyć metod pomocniczych do zwracania typów ActionResult z akcji. Na przykład return BadRequest(); jest skróconą formą return new BadRequestResult();.

Ponieważ istnieje wiele typów wartości zwracanych i dróg w tego rodzaju akcji, konieczne jest liberalne użycie atrybutu [ProducesResponseType]. Ten atrybut tworzy bardziej opisowe szczegóły odpowiedzi dla stron pomocy interfejsu API sieci Web generowanych przez narzędzia, takie jak Swagger. [ProducesResponseType] wskazuje znane typy i kody stanu HTTP, które mają być zwracane przez akcję.

Akcja synchroniczna

Rozważmy następującą akcję synchroniczną, w której istnieją dwa możliwe typy zwrotne:

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt reprezentowany przez id nie istnieje w bazowym magazynie danych. Metoda pomocnicza NotFound jest wywoływana jako skrócona forma dla return new NotFoundResult();.
  • Kod stanu 200 jest zwracany z obiektem Product , gdy produkt istnieje. Metoda Ok jest wywoływana jako skrót od return new OkObjectResult(product);.

Akcja asynchroniczna

Rozważ następującą akcję asynchroniczną, w której istnieją dwa możliwe typy zwracane:

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

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

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

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy opis produktu zawiera element "Widżet XYZ". Metoda pomocnicza BadRequest jest wywoływana jako skrót do return new BadRequestResult();.

  • Kod stanu 201 jest generowany przez CreatedAtAction metodę pomocniczą podczas tworzenia produktu. Poniższy kod jest alternatywą dla wywołania metody CreatedAtAction:

    return new CreatedAtActionResult(nameof(GetById), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    W poprzedniej ścieżce kodu obiekt Product jest udostępniany w treści odpowiedzi. Nagłówek odpowiedzi Location zawierający adres URL nowo utworzonego produktu zostanie dostarczony.

Na przykład poniższy model wskazuje, że żądania muszą zawierać właściwości Name i Description. Niepowodzenie podania Name i Description w żądaniu powoduje niepowodzenie walidacji modelu.

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

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

[ApiController] Jeśli atrybut zostanie zastosowany, błędy walidacji modelu spowodują wyświetlenie kodu stanu 400. Aby uzyskać więcej informacji, zobacz Automatyczne odpowiedzi HTTP 400.

ActionResult vs IActionResult

Poniższa sekcja porównuje ActionResult z IActionResult

ActionResult<T>, typ

ASP.NET Core zawiera typ zwracany ActionResult<T> dla akcji kontrolera interfejsu API. Umożliwia zwracanie typu pochodzącego z ActionResult lub zwracanie określonego typu. ActionResult<T> oferuje następujące korzyści w porównaniu do typu IActionResult:

  • Można wykluczyć właściwość Type atrybutu [ProducesResponseType]. Na przykład [ProducesResponseType(200, Type = typeof(Product))] jest uproszczony do [ProducesResponseType(200)]. Oczekiwany typ zwracany akcji jest wywnioskowany z T w ActionResult<T>.
  • Niejawne operatory rzutowania obsługują konwersję T oraz ActionResult na ActionResult<T>. T konwertuje na ObjectResult, co oznacza, że return new ObjectResult(T); jest upraszczane na return T;.

Język C# nie obsługuje niejawnych operatorów rzutowania w interfejsach. W związku z tym konwersja interfejsu na konkretny typ jest niezbędna, aby użyć elementu ActionResult<T>. Na przykład użycie elementu IEnumerable w poniższym przykładzie nie działa:

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

Jedną z opcji naprawienia poprzedniego kodu jest zwrócenie _repository.GetProducts().ToList();.

Większość akcji ma określony typ zwracany. Podczas wykonywania akcji mogą wystąpić nieoczekiwane warunki, w takim przypadku określony typ nie zostanie zwrócony. Na przykład parametr wejściowy akcji może zakończyć się niepowodzeniem weryfikacji modelu. W takim przypadku często zwracany jest odpowiedni ActionResult typ zamiast określonego typu.

Akcja synchroniczna

Rozważ akcję synchroniczną, w której istnieją dwa możliwe rodzaje wartości zwracanych.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany z odpowiednim Product obiektem, gdy produkt istnieje.

Akcja asynchroniczna

Rozważ akcję asynchroniczną, w której mogą wystąpić dwa możliwe typy wartości zwracanych:

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

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

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

W poprzedniej akcji:

  • Kod stanu 400 (BadRequest) jest zwracany przez środowisko uruchomieniowe ASP.NET Core, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez CreatedAtAction metodę podczas tworzenia produktu. W tej ścieżce kodu obiekt Product jest zawarty w treści odpowiedzi. Nagłówek odpowiedzi zawierający adres URL nowo utworzonego produktu zostanie dostarczony.

Typ HttpResults

Oprócz wbudowanych typów wyników specyficznych dla MVC (IActionResult i ActionResult<T>), ASP.NET Core obejmuje typy HttpResults, które mogą być używane zarówno w Minimal APIs, jak i w Web API.

Różni się od typów wyników charakterystycznych dla wzorca MVC:HttpResults

  • To implementacja wyników przetwarzana przez wywołanie metody IResult.ExecuteAsync.

  • Nie korzysta ze skonfigurowanych formatterów. Nie wykorzystywanie skonfigurowanych formatterów oznacza:

    • Niektóre funkcje, takie jak Content negotiation nie są dostępne.
    • Decyzja dotycząca Content-Type jest podejmowana przez implementację HttpResults.

HttpResults może być przydatne podczas udostępniania kodu między minimalnymi API i interfejsem Web API.

Typ IResult

Microsoft.AspNetCore.Http.HttpResults Przestrzeń nazw zawiera klasy implementujące IResult interfejs. Interfejs IResult definiuje kontrakt reprezentujący wynik punktu końcowego HTTP. Statyczna klasa Results służy do tworzenia różnych IResult obiektów reprezentujących różne typy odpowiedzi.

W tabeli wbudowanych wyników przedstawiono typowych asystentów wyników.

Spójrzmy na poniższy kod:

[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany z odpowiednim Product obiektem, gdy produkt istnieje, wygenerowany przez Results.Ok<T>().

Spójrzmy na poniższy kod:

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

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez Results.Create metodę podczas tworzenia produktu. W tej ścieżce kod obiekt Product jest udostępniany w treści odpowiedzi. Zostanie Location udostępniony nagłówek odpowiedzi zawierający adres URL nowo utworzonego produktu.

Wyniki<TResult1, typ TResultN>

Statyczna klasa TypedResults zwraca konkretną IResult implementację, która umożliwia użycie IResult jako typu zwracanego. Użycie konkretnej IResult implementacji zapewnia następującą korzyść nad typem IResult:

  • [ProducesResponseType] Wszystkie atrybuty można wykluczyć, ponieważ implementacja HttpResult automatycznie przyczynia się do metadanych punktu końcowego.

Gdy potrzebnych jest wiele IResult typów zwracania, preferowane jest zwracanie Results<TResult1, TResultN> zamiast IResult. Zwracanie Results<TResult1, TResultN> jest preferowane, ponieważ ogólne typy związków automatycznie zachowują metadane punktu końcowego.

Typy unii Results<TResult1, TResultN> implementują niejawne operatory rzutowania, aby kompilator mógł automatycznie konwertować typy, określone w argumentach ogólnych, na wystąpienie typu unii. Ma to dodatkową zaletę zapewnienia sprawdzania czasu kompilacji, że program obsługi tras rzeczywiście zwraca tylko wyniki, które deklaruje. Usiłowanie zwrócenia typu, który nie jest zadeklarowany jako jeden z argumentów ogólnych Results<>, skutkuje błędem kompilacji.

Spójrzmy na poniższy kod:

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany wraz z odpowiednim obiektem Product kiedy produkt istnieje, generowany przez TypedResults.Ok<T>.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez TypedResults.Create metodę podczas tworzenia produktu. W tej ścieżce kodu obiekt Product jest udostępniany w treści odpowiedzi. Nagłówek odpowiedzi Location zawierający adres URL nowo utworzonego produktu zostanie udostępniony.

Dodatkowe zasoby

Wyświetl lub pobierz przykładowy kod (jak pobrać)

ASP.NET Core oferuje następujące opcje dla typów zwracanych akcji kontrolera internetowego interfejsu API:

W tym dokumencie wyjaśniono, kiedy najbardziej odpowiednie jest użycie każdego typu zwracanego.

Określony typ

Najprostsza akcja zwraca typ danych pierwotnych lub złożonych (na przykład string lub typ obiektu niestandardowego). Rozważmy następującą akcję, która zwraca kolekcję obiektów niestandardowych Product :

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

Przy braku znanych warunków do zabezpieczenia przed wykonaniem akcji, zwrócenie określonego typu może wystarczyć. Poprzednia akcja nie akceptuje żadnych parametrów, więc weryfikacja ograniczeń parametrów nie jest wymagana.

Często łączy się zwracany typ ActionResult z typem zwracanym pierwotnym lub złożonym, gdy możliwe jest zwrócenie więcej niż jednego typu. Aby uwzględnić ten typ akcji, konieczne jest rozwiązanie IActionResult lub ActionResult<T> . W tym dokumencie przedstawiono kilka przykładów wielu typów zwracanych.

Zwróć IEnumerable<T> lub IAsyncEnumerable<T>

ASP.NET Core buforuje wynik akcji, które zwracają IEnumerable<T>, zanim zostaną zapisane w odpowiedzi. Rozważ w celu zagwarantowania asynchronicznej iteracji zadeklarowanie typu zwrotnego podpisu akcji jako IAsyncEnumerable<T>. Ostatecznie tryb iteracji opiera się na zwracanym typie bazowym. MvC automatycznie buforuje każdy konkretny typ, który implementuje IAsyncEnumerable<T>.

Rozważmy następującą akcję, która zwraca rekordy produktów w cenie sprzedaży jako IEnumerable<Product>:

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

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

Odpowiednikiem IAsyncEnumerable<Product> poprzedniej akcji jest:

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

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

Typ IActionResult

Typ zwrotu IActionResult jest właściwy, gdy w akcji możliwe jest kilka typów zwrotu ActionResult. Typy ActionResult reprezentują różne kody stanu HTTP. Każda nie abstrakcyjna klasa wyprowadzająca z ActionResult kwalifikuje się jako prawidłowy typ zwracany. Niektóre typowe typy zwracane w tej kategorii to BadRequestResult (400), NotFoundResult (404) i OkObjectResult (200). Alternatywnie, w klasie ControllerBase można użyć metod ułatwiających do zwracania typów ActionResult z akcji. Na przykład return BadRequest(); jest skróconą formą return new BadRequestResult();.

Ponieważ w tym typie akcji występuje wiele typów zwrotów i ścieżek, konieczne jest swobodne użycie atrybutu [ProducesResponseType]. Ten atrybut tworzy bardziej opisowe szczegóły odpowiedzi dla stron pomocy interfejsu API sieci Web generowanych przez narzędzia, takie jak Swagger. [ProducesResponseType] wskazuje znane typy i kody stanu HTTP, które mają być zwracane przez akcję.

Akcja synchroniczna

Rozważmy następującą akcję synchroniczną, w której mogą wystąpić dwa możliwe typy zwracania:

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

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt reprezentowany przez id nie istnieje w bazowym magazynie danych. Metoda ułatwiająca NotFound jest wywoływana jako skrót od return new NotFoundResult();.
  • Kod stanu 200 jest zwracany z obiektem Product , gdy produkt istnieje. Metoda pomocnicza Ok jest wywoływana jako skrót dla return new OkObjectResult(product);.

Akcja asynchroniczna

Rozważ następującą akcję asynchroniczną, w której istnieją dwa możliwe typy zwracane:

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

W poprzedniej akcji:

  • Kod stanu 400 jest zwracany, gdy opis produktu zawiera element "Widżet XYZ". Metoda pomocnicza BadRequest jest wywoływana jako skrótowa forma dla return new BadRequestResult();.
  • Kod stanu 201 jest generowany przez CreatedAtAction metodę pomocniczą w momencie tworzenia produktu. Alternatywą dla wywołania CreatedAtAction jest return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. W tej ścieżce kodu obiekt Product jest udostępniany w treści odpowiedzi. Zostanie Location udostępniony nagłówek odpowiedzi zawierający adres URL nowo utworzonego produktu.

Na przykład poniższy model wskazuje, że żądania muszą zawierać właściwości Name i Description. Niepowodzenie podania Name i Description w żądaniu powoduje niepowodzenie walidacji modelu.

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

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

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

    public bool IsOnSale { get; set; }
}

[ApiController] Jeśli atrybut zostanie zastosowany, błędy walidacji modelu spowodują wyświetlenie kodu stanu 400. Aby uzyskać więcej informacji, zobacz Automatyczne odpowiedzi HTTP 400.

ActionResult vs IActionResult

Poniższa sekcja porównuje ActionResult z IActionResult

typ ActionResult<T>

ASP.NET Core zawiera typ zwracany przez ActionResult<T> dla akcji kontrolera interfejsu API dla aplikacji webowych. Umożliwia zwracanie typu pochodzącego z ActionResult lub zwracania określonego typu. ActionResult<T> oferuje następujące korzyści w porównaniu do typu IActionResult:

  • Właściwość [ProducesResponseType] atrybutu Type można wykluczyć. Na przykład [ProducesResponseType(200, Type = typeof(Product))] jest uproszczony do [ProducesResponseType(200)]. Oczekiwany typ zwracany akcji jest wywnioskowany zamiast tego z elementu T w ActionResult<T>.
  • Niejawne operatory rzutowania obsługują konwersję zarówno T, jak i ActionResult na ActionResult<T>. T konwertuje na ObjectResult, co oznacza, że return new ObjectResult(T); jest uproszczone na return T;.

Język C# nie obsługuje niejawnych operatorów rzutowania w interfejsach. W związku z tym do użycia ActionResult<T> niezbędna jest konwersja interfejsu na konkretny typ. Na przykład użycie elementu IEnumerable w poniższym przykładzie nie działa:

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

Jedną z opcji naprawienia poprzedniego kodu jest zwrócenie _repository.GetProducts().ToList();.

Większość akcji ma określony typ zwracany. Podczas wykonywania akcji mogą wystąpić nieoczekiwane warunki, w takim przypadku określony typ nie jest zwracany. Na przykład parametr wejściowy akcji może zakończyć się niepowodzeniem weryfikacji modelu. W takim przypadku często zwracany jest odpowiedni ActionResult typ zamiast określonego typu.

Akcja synchroniczna

Rozważ akcję synchroniczną, w której istnieją dwa możliwe typy zwracane:

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

W poprzedniej akcji:

  • Kod stanu 404 jest zwracany, gdy produkt nie istnieje w bazie danych.
  • Kod stanu 200 jest zwracany z odpowiednim Product obiektem, gdy produkt istnieje.

Akcja asynchroniczna

Rozważ akcję asynchroniczną, w której istnieją dwa możliwe typy zwracane:

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

W poprzedniej akcji:

  • Kod stanu 400 (BadRequest) jest zwracany przez środowisko uruchomieniowe ASP.NET Core, gdy:
    • Atrybut został zastosowany, a walidacja [ApiController] modelu kończy się niepowodzeniem.
    • Opis produktu zawiera element "Widżet XYZ".
  • Kod stanu 201 jest generowany przez CreatedAtAction metodę podczas tworzenia produktu. W tej ścieżce kodu obiekt Product jest zawarty w treści odpowiedzi. Nagłówek odpowiedzi zawierający adres URL nowo utworzonego produktu zostanie udostępniony.

Dodatkowe zasoby