I have an API endpoint:
[HttpGet("api/query")]
public async IAsyncEnumerable<dynamic> Query(string name)
{
await foreach(var item in _myService.CallSomethingReturningAsyncStream(name))
{
yield return item;
}
}
I would like to be able to in case of an ArgumentException return something like "Bad request" response.
If I try using try-catch block, I get error:
CS1626: Cannot yield a value in the body of a try block with a catch clause
Please note that it is an API endpoint method, so error handling should ideally be in the same method, without need for making additional middlewares.
CodePudding user response:
You have to split method. Extract part which does async processing:
private async IAsyncEnumerable<dynamic> ProcessData(TypeOfYourData data)
{
await foreach(var item in data)
{
yield return item;
}
}
And then in API method do:
[HttpGet("api/query")]
public IActionResult Query(string name)
{
TypeOfYourData data;
try {
data = _myService.CallSomethingReturningAsyncStream(name);
}
catch (...) {
// do what you need
return BadRequest();
}
return Ok(ProcessData(data));
}
Or actually you can just move the whole thing into separate method:
[HttpGet("api/query")]
public IActionResult Query(string name)
{
try {
return Ok(TheMethodYouMovedYourCurrentCodeTo);
}
catch (...) {
// do what you need
return BadRequest();
}
}
It will of course only catch exceptions thrown before actual async enumeration starts, but that's fine for your use case as I understand. Returning bad request after async enumeration has started is not possible, because response is already being sent to client.
CodePudding user response:
You could install the System.Interactive.Async package, and do this:
[HttpGet("api/query")]
public IAsyncEnumerable<dynamic> Query(string name)
{
return AsyncEnumerableEx.Defer(() => _myService.CallSomethingReturningAsyncStream(name))
.Catch<dynamic, Exception>(ex =>
AsyncEnumerableEx.Return<dynamic>($"Bad request: {ex.Message}"));
}
The signature of the Defer
operator:
// Returns an async-enumerable sequence that invokes the specified
// factory function whenever a new observer subscribes.
public static IAsyncEnumerable<TSource> Defer<TSource>(
Func<IAsyncEnumerable<TSource>> factory)
The signature of the Catch
operator:
// Continues an async-enumerable sequence that is terminated by
// an exception of the specified type with the async-enumerable sequence
// produced by the handler.
public static IAsyncEnumerable<TSource> Catch<TSource, TException>(
this IAsyncEnumerable<TSource> source,
Func<TException, IAsyncEnumerable<TSource>> handler);
The signature of the Return
operator:
// Returns an async-enumerable sequence that contains a single element.
public static IAsyncEnumerable<TValue> Return<TValue>(TValue value)
The Defer
might seem superficial, but it is needed for the case that the _myService.CallSomethingReturningAsyncStream
throws synchronously. In case this method is implemented as an async
iterator, it will never throw synchronously, so you could omit the Defer
.