Skip to content

Commit 6a8ce3a

Browse files
pranavkmmkArtakMSFT
authored andcommitted
Add support for IAsyncEnumerable<T> where T is value type (#17154) (#17563)
* Add support for IAsyncEnumerable<T> where T is value type (#17154) * Add support for IAsyncEnumerable<T> where T is value type Fixes #17139 * Fixup ref asm * undo changes to blazor.server.js
1 parent 8e5767b commit 6a8ce3a

File tree

8 files changed

+268
-60
lines changed

8 files changed

+268
-60
lines changed

src/Mvc/Mvc.Core/ref/Microsoft.AspNetCore.Mvc.Core.Manual.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ public ActionSelector(Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorC
875875
internal sealed partial class AsyncEnumerableReader
876876
{
877877
public AsyncEnumerableReader(Microsoft.AspNetCore.Mvc.MvcOptions mvcOptions) { }
878-
public System.Threading.Tasks.Task<System.Collections.ICollection> ReadAsync(System.Collections.Generic.IAsyncEnumerable<object> value) { throw null; }
878+
public bool TryGetReader(System.Type type, out System.Func<object, System.Threading.Tasks.Task<System.Collections.ICollection>> reader) { throw null; }
879879
}
880880
internal partial class ClientErrorResultFilter : Microsoft.AspNetCore.Mvc.Filters.IAlwaysRunResultFilter, Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata, Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter, Microsoft.AspNetCore.Mvc.Filters.IResultFilter
881881
{

src/Mvc/Mvc.Core/src/Infrastructure/AsyncEnumerableReader.cs

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections;
66
using System.Collections.Concurrent;
77
using System.Collections.Generic;
8-
using System.Diagnostics;
98
using System.Reflection;
109
using System.Threading.Tasks;
1110
using Microsoft.AspNetCore.Mvc.Core;
@@ -17,8 +16,6 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson
1716
namespace Microsoft.AspNetCore.Mvc.Infrastructure
1817
#endif
1918
{
20-
using ReaderFunc = Func<IAsyncEnumerable<object>, Task<ICollection>>;
21-
2219
/// <summary>
2320
/// Type that reads an <see cref="IAsyncEnumerable{T}"/> instance into a
2421
/// generic collection instance.
@@ -34,8 +31,8 @@ internal sealed class AsyncEnumerableReader
3431
nameof(ReadInternal),
3532
BindingFlags.NonPublic | BindingFlags.Instance);
3633

37-
private readonly ConcurrentDictionary<Type, ReaderFunc> _asyncEnumerableConverters =
38-
new ConcurrentDictionary<Type, ReaderFunc>();
34+
private readonly ConcurrentDictionary<Type, Func<object, Task<ICollection>>> _asyncEnumerableConverters =
35+
new ConcurrentDictionary<Type, Func<object, Task<ICollection>>>();
3936
private readonly MvcOptions _mvcOptions;
4037

4138
/// <summary>
@@ -48,37 +45,39 @@ public AsyncEnumerableReader(MvcOptions mvcOptions)
4845
}
4946

5047
/// <summary>
51-
/// Reads a <see cref="IAsyncEnumerable{T}"/> into an <see cref="ICollection{T}"/>.
48+
/// Attempts to produces a delagate that reads a <see cref="IAsyncEnumerable{T}"/> into an <see cref="ICollection{T}"/>.
5249
/// </summary>
53-
/// <param name="value">The <see cref="IAsyncEnumerable{T}"/> to read.</param>
54-
/// <returns>The <see cref="ICollection"/>.</returns>
55-
public Task<ICollection> ReadAsync(IAsyncEnumerable<object> value)
50+
/// <param name="type">The type to read.</param>
51+
/// <param name="reader">A delegate that when awaited reads the <see cref="IAsyncEnumerable{T}"/>.</param>
52+
/// <returns><see langword="true" /> when <paramref name="type"/> is an instance of <see cref="IAsyncEnumerable{T}"/>, othwerise <see langword="false"/>.</returns>
53+
public bool TryGetReader(Type type, out Func<object, Task<ICollection>> reader)
5654
{
57-
if (value == null)
58-
{
59-
throw new ArgumentNullException(nameof(value));
60-
}
61-
62-
var type = value.GetType();
63-
if (!_asyncEnumerableConverters.TryGetValue(type, out var result))
55+
if (!_asyncEnumerableConverters.TryGetValue(type, out reader))
6456
{
6557
var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IAsyncEnumerable<>));
66-
Debug.Assert(enumerableType != null);
67-
68-
var enumeratedObjectType = enumerableType.GetGenericArguments()[0];
58+
if (enumerableType is null)
59+
{
60+
// Not an IAsyncEnumerable<T>. Cache this result so we avoid reflection the next time we see this type.
61+
reader = null;
62+
_asyncEnumerableConverters.TryAdd(type, reader);
63+
}
64+
else
65+
{
66+
var enumeratedObjectType = enumerableType.GetGenericArguments()[0];
6967

70-
var converter = (ReaderFunc)Converter
71-
.MakeGenericMethod(enumeratedObjectType)
72-
.CreateDelegate(typeof(ReaderFunc), this);
68+
var converter = (Func<object, Task<ICollection>>)Converter
69+
.MakeGenericMethod(enumeratedObjectType)
70+
.CreateDelegate(typeof(Func<object, Task<ICollection>>), this);
7371

74-
_asyncEnumerableConverters.TryAdd(type, converter);
75-
result = converter;
72+
reader = converter;
73+
_asyncEnumerableConverters.TryAdd(type, reader);
74+
}
7675
}
7776

78-
return result(value);
77+
return reader != null;
7978
}
8079

81-
private async Task<ICollection> ReadInternal<T>(IAsyncEnumerable<object> value)
80+
private async Task<ICollection> ReadInternal<T>(object value)
8281
{
8382
var asyncEnumerable = (IAsyncEnumerable<T>)value;
8483
var result = new List<T>();

src/Mvc/Mvc.Core/src/Infrastructure/ObjectResultExecutor.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections;
56
using System.Collections.Generic;
67
using System.Diagnostics;
78
using System.IO;
@@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
1920
/// </summary>
2021
public class ObjectResultExecutor : IActionResultExecutor<ObjectResult>
2122
{
22-
private readonly AsyncEnumerableReader _asyncEnumerableReader;
23+
private readonly AsyncEnumerableReader _asyncEnumerableReaderFactory;
2324

2425
/// <summary>
2526
/// Creates a new <see cref="ObjectResultExecutor"/>.
@@ -68,7 +69,7 @@ public ObjectResultExecutor(
6869
WriterFactory = writerFactory.CreateWriter;
6970
Logger = loggerFactory.CreateLogger<ObjectResultExecutor>();
7071
var options = mvcOptions?.Value ?? throw new ArgumentNullException(nameof(mvcOptions));
71-
_asyncEnumerableReader = new AsyncEnumerableReader(options);
72+
_asyncEnumerableReaderFactory = new AsyncEnumerableReader(options);
7273
}
7374

7475
/// <summary>
@@ -117,19 +118,19 @@ public virtual Task ExecuteAsync(ActionContext context, ObjectResult result)
117118

118119
var value = result.Value;
119120

120-
if (value is IAsyncEnumerable<object> asyncEnumerable)
121+
if (value != null && _asyncEnumerableReaderFactory.TryGetReader(value.GetType(), out var reader))
121122
{
122-
return ExecuteAsyncEnumerable(context, result, asyncEnumerable);
123+
return ExecuteAsyncEnumerable(context, result, value, reader);
123124
}
124125

125126
return ExecuteAsyncCore(context, result, objectType, value);
126127
}
127128

128-
private async Task ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, IAsyncEnumerable<object> asyncEnumerable)
129+
private async Task ExecuteAsyncEnumerable(ActionContext context, ObjectResult result, object asyncEnumerable, Func<object, Task<ICollection>> reader)
129130
{
130131
Log.BufferingAsyncEnumerable(Logger, asyncEnumerable);
131132

132-
var enumerated = await _asyncEnumerableReader.ReadAsync(asyncEnumerable);
133+
var enumerated = await reader(asyncEnumerable);
133134
await ExecuteAsyncCore(context, result, enumerated.GetType(), enumerated);
134135
}
135136

@@ -194,7 +195,7 @@ static Log()
194195
"Buffering IAsyncEnumerable instance of type '{Type}'.");
195196
}
196197

197-
public static void BufferingAsyncEnumerable(ILogger logger, IAsyncEnumerable<object> asyncEnumerable)
198+
public static void BufferingAsyncEnumerable(ILogger logger, object asyncEnumerable)
198199
=> _bufferingAsyncEnumerable(logger, asyncEnumerable.GetType().FullName, null);
199200
}
200201
}

src/Mvc/Mvc.Core/src/Infrastructure/SystemTextJsonResultExecutor.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal sealed class SystemTextJsonResultExecutor : IActionResultExecutor<JsonR
2727

2828
private readonly JsonOptions _options;
2929
private readonly ILogger<SystemTextJsonResultExecutor> _logger;
30-
private readonly AsyncEnumerableReader _asyncEnumerableReader;
30+
private readonly AsyncEnumerableReader _asyncEnumerableReaderFactory;
3131

3232
public SystemTextJsonResultExecutor(
3333
IOptions<JsonOptions> options,
@@ -36,7 +36,7 @@ public SystemTextJsonResultExecutor(
3636
{
3737
_options = options.Value;
3838
_logger = logger;
39-
_asyncEnumerableReader = new AsyncEnumerableReader(mvcOptions.Value);
39+
_asyncEnumerableReaderFactory = new AsyncEnumerableReader(mvcOptions.Value);
4040
}
4141

4242
public async Task ExecuteAsync(ActionContext context, JsonResult result)
@@ -76,10 +76,10 @@ public async Task ExecuteAsync(ActionContext context, JsonResult result)
7676
try
7777
{
7878
var value = result.Value;
79-
if (value is IAsyncEnumerable<object> asyncEnumerable)
79+
if (value != null && _asyncEnumerableReaderFactory.TryGetReader(value.GetType(), out var reader))
8080
{
81-
Log.BufferingAsyncEnumerable(_logger, asyncEnumerable);
82-
value = await _asyncEnumerableReader.ReadAsync(asyncEnumerable);
81+
Log.BufferingAsyncEnumerable(_logger, value);
82+
value = await reader(value);
8383
}
8484

8585
var type = value?.GetType() ?? typeof(object);
@@ -154,7 +154,7 @@ public static void JsonResultExecuting(ILogger logger, object value)
154154
_jsonResultExecuting(logger, type, null);
155155
}
156156

157-
public static void BufferingAsyncEnumerable(ILogger logger, IAsyncEnumerable<object> asyncEnumerable)
157+
public static void BufferingAsyncEnumerable(ILogger logger, object asyncEnumerable)
158158
=> _bufferingAsyncEnumerable(logger, asyncEnumerable.GetType().FullName, null);
159159
}
160160
}

0 commit comments

Comments
 (0)