Renvoyer IAsyncEnumerable <T> et NotFound à partir du contrôleur principal Asp.Net

10

Quelle est la bonne signature pour une action de contrôleur qui renvoie un IAsyncEnumerable<T>et un NotFoundResultmais qui est toujours traitée de manière asynchrone?

J'ai utilisé cette signature et elle ne se compile pas car elle IAsyncEnumerable<T>n'est pas attendue:

[HttpGet]
public async Task<IActionResult> GetAll(Guid id)
{
    try
    {
        return Ok(await repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable
    }
    catch (NotFoundException e)
    {
        return NotFound(e.Message);
    }
}

Celui-ci compile bien mais sa signature n'est pas asynchrone. Je me demande donc si cela bloquera ou non les threads du pool de threads:

[HttpGet]
public IActionResult GetAll(Guid id)
{
    try
    {
        return Ok(repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable
    }
    catch (NotFoundException e)
    {
        return NotFound(e.Message);
    }
}

J'ai essayé d'utiliser une await foreachboucle comme celle-ci, mais cela ne se compilerait évidemment pas non plus:

[HttpGet]
public async IAsyncEnumerable<MyObject> GetAll(Guid id)
{
    IAsyncEnumerable<MyObject> objects;
    try
    {
        objects = contentDeliveryManagementService.GetAll(id); // GetAll() returns an IAsyncEnumerable
    }
    catch (DeviceNotFoundException e)
    {
        return NotFound(e.Message);
    }

    await foreach (var obj in objects)
    {
        yield return obj;
    }
}
Frédéric le fou
la source
5
Vous retournez plusieurs MyObjectarticles avec le même id? Normalement, vous n'enverriez pas un NotFoundpour quelque chose qui renvoie un IEnumerable- il serait simplement vide - ou vous renverriez le seul élément avec le id/ demandé NotFound.
crgolden
1
IAsyncEnumerableest attendue. Utilisez await foreach(var item from ThatMethodAsync()){...}.
Panagiotis Kanavos
Si vous souhaitez revenir IAsyncEnumerable<MyObject>, renvoyez simplement le résultat, par exemple return objects. Ce ne sera pas convertir une action HTTP à une méthode GRPC ou SignalR le streaming bien. Le middleware consommera toujours les données et enverra une seule réponse HTTP au client
Panagiotis Kanavos
L'option 2 est très bien. La plomberie ASP.NET Core prend en charge l'énumération et est IAsyncEnumerableconsciente à partir de 3.0.
Kirk Larkin
Merci les gars. Je sais que je n'ai pas rendu de 404 ici, mais ce n'est qu'un exemple artificiel. Le code réel est assez différent. @KirkLarkin désolé d'être un ravageur, mais êtes-vous sûr à 100% que cela ne causera aucun blocage? Si oui, alors l'option 2 est la solution évidente.
Frederick The Fool

Réponses:

6

L'option 2, qui transmet une implémentation de IAsyncEnumerable<>dans l' Okappel, est très bien. La plomberie ASP.NET Core prend en charge l'énumération et est IAsyncEnumerable<>consciente à partir de 3.0.

Voici l'appel de la question, répété pour le contexte:

return Ok(repository.GetAll(id)); // GetAll() returns an IAsyncEnumerable

L'appel à Okcrée une instance de OkObjectResult, qui hérite ObjectResult. La valeur transmise pour Okest du type object, qui est maintenu dans le ObjectResultde » Valuela propriété. ASP.NET Core MVC utilise le modèle de commande , la commande étant une implémentation de IActionResultet exécutée à l'aide d'une implémentation de IActionResultExecutor<T>.

Pour ObjectResult, ObjectResultExecutorest utilisé pour transformer le ObjectResulten une réponse HTTP. C'est l' implémentation de ObjectResultExecutor.ExecuteAsynccela qui est IAsyncEnumerable<>conscient:

public virtual Task ExecuteAsync(ActionContext context, ObjectResult result)
{
    // ...

    var value = result.Value;

    if (value != null && _asyncEnumerableReaderFactory.TryGetReader(value.GetType(), out var reader))
    {
        return ExecuteAsyncEnumerable(context, result, value, reader);
    }

    return ExecuteAsyncCore(context, result, objectType, value);
}

Comme le montre le code, la Valuepropriété est vérifiée pour voir si elle implémente IAsyncEnumerable<>(les détails sont masqués dans l'appel à TryGetReader). Si c'est le cas, ExecuteAsyncEnumerableest appelé, qui effectue l'énumération, puis transmet le résultat énuméré à ExecuteAsyncCore:

private async Task ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, object asyncEnumerable, Func<object, Task<ICollection>> reader)
{
    Log.BufferingAsyncEnumerable(Logger, asyncEnumerable);

    var enumerated = await reader(asyncEnumerable);
    await ExecuteAsyncCore(context, result, enumerated.GetType(), enumerated);
}

readerdans l'extrait ci-dessus est l'endroit où l'énumération se produit. C'est un peu enterré, mais vous pouvez voir la source ici :

private async Task<ICollection> ReadInternal<T>(object value)
{
    var asyncEnumerable = (IAsyncEnumerable<T>)value;
    var result = new List<T>();
    var count = 0;

    await foreach (var item in asyncEnumerable)
    {
        if (count++ >= _mvcOptions.MaxIAsyncEnumerableBufferLimit)
        {
            throw new InvalidOperationException(Resources.FormatObjectResultExecutor_MaxEnumerationExceeded(
                nameof(AsyncEnumerableReader),
                value.GetType()));
        }

        result.Add(item);
    }

    return result;
}

Le IAsyncEnumerable<>est énuméré dans une List<>utilisation await foreachqui, presque par définition, ne bloque pas un thread de demande. Comme Panagiotis Kanavos l'a indiqué dans un commentaire sur le PO, cette énumération est effectuée en entier avant qu'une réponse ne soit renvoyée au client.

Kirk Larkin
la source
Merci pour la réponse détaillée Kirk :). Une préoccupation que j'ai est avec la méthode d'action elle-même dans l'option 2. J'ai compris que sa valeur de retour sera énumérée de manière asynchrone, mais elle ne renvoie pas d' Taskobjet. Ce fait empêche-t-il en soi l'asynchronicité? Surtout par rapport à, disons, une méthode similaire qui a renvoyé a Task.
Frederick The Fool
1
Non, ça va. Il n'y a aucune raison de renvoyer un Task, car la méthode elle-même n'effectue aucun travail asynchrone. C'est l'énumération asynchrone, qui est gérée comme décrit ci-dessus. Vous pouvez voir qu'il Taskest utilisé ici, dans l'exécution du ObjectResult.
Kirk Larkin