2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections . Concurrent ;
5
6
using System . Collections . Generic ;
6
7
using System . Diagnostics ;
7
8
using System . IO ;
9
+ using System . Reflection ;
8
10
using System . Text ;
9
11
using System . Threading . Tasks ;
10
12
using Microsoft . AspNetCore . Http ;
13
+ using Microsoft . AspNetCore . Mvc . Core ;
11
14
using Microsoft . AspNetCore . Mvc . Formatters ;
15
+ using Microsoft . Extensions . Internal ;
12
16
using Microsoft . Extensions . Logging ;
17
+ using Microsoft . Extensions . Options ;
13
18
14
19
namespace Microsoft . AspNetCore . Mvc . Infrastructure
15
20
{
@@ -18,16 +23,43 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
18
23
/// </summary>
19
24
public class ObjectResultExecutor : IActionResultExecutor < ObjectResult >
20
25
{
26
+ private delegate Task < object > ReadAsyncEnumerableDelegate ( object value ) ;
27
+
28
+ private readonly MethodInfo Converter = typeof ( ObjectResultExecutor ) . GetMethod (
29
+ nameof ( ReadAsyncEnumerable ) ,
30
+ BindingFlags . NonPublic | BindingFlags . Instance ) ;
31
+
32
+ private readonly ConcurrentDictionary < Type , ReadAsyncEnumerableDelegate > _asyncEnumerableConverters =
33
+ new ConcurrentDictionary < Type , ReadAsyncEnumerableDelegate > ( ) ;
34
+ private readonly MvcOptions _mvcOptions ;
35
+
21
36
/// <summary>
22
37
/// Creates a new <see cref="ObjectResultExecutor"/>.
23
38
/// </summary>
24
39
/// <param name="formatterSelector">The <see cref="OutputFormatterSelector"/>.</param>
25
40
/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
26
41
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
42
+ [ Obsolete ( "This constructor is obsolete and will be removed in a future release." ) ]
27
43
public ObjectResultExecutor (
28
44
OutputFormatterSelector formatterSelector ,
29
45
IHttpResponseStreamWriterFactory writerFactory ,
30
46
ILoggerFactory loggerFactory )
47
+ : this ( formatterSelector , writerFactory , loggerFactory , mvcOptions : null )
48
+ {
49
+ }
50
+
51
+ /// <summary>
52
+ /// Creates a new <see cref="ObjectResultExecutor"/>.
53
+ /// </summary>
54
+ /// <param name="formatterSelector">The <see cref="OutputFormatterSelector"/>.</param>
55
+ /// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
56
+ /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
57
+ /// <param name="mvcOptions">Accessor to <see cref="MvcOptions"/>.</param>
58
+ public ObjectResultExecutor (
59
+ OutputFormatterSelector formatterSelector ,
60
+ IHttpResponseStreamWriterFactory writerFactory ,
61
+ ILoggerFactory loggerFactory ,
62
+ IOptions < MvcOptions > mvcOptions )
31
63
{
32
64
if ( formatterSelector == null )
33
65
{
@@ -47,6 +79,7 @@ public ObjectResultExecutor(
47
79
FormatterSelector = formatterSelector ;
48
80
WriterFactory = writerFactory . CreateWriter ;
49
81
Logger = loggerFactory . CreateLogger < ObjectResultExecutor > ( ) ;
82
+ _mvcOptions = mvcOptions ? . Value ?? throw new ArgumentNullException ( nameof ( mvcOptions ) ) ;
50
83
}
51
84
52
85
/// <summary>
@@ -87,16 +120,35 @@ public virtual Task ExecuteAsync(ActionContext context, ObjectResult result)
87
120
InferContentTypes ( context , result ) ;
88
121
89
122
var objectType = result . DeclaredType ;
123
+
90
124
if ( objectType == null || objectType == typeof ( object ) )
91
125
{
92
126
objectType = result . Value ? . GetType ( ) ;
93
127
}
94
128
129
+ var value = result . Value ;
130
+
131
+ if ( value is IAsyncEnumerable < object > asyncEnumerable )
132
+ {
133
+ return ExecuteAsyncEnumerable ( context , result , asyncEnumerable ) ;
134
+ }
135
+
136
+ return ExecuteAsyncCore ( context , result , objectType , value ) ;
137
+ }
138
+
139
+ private async Task ExecuteAsyncEnumerable ( ActionContext context , ObjectResult result , IAsyncEnumerable < object > asyncEnumerable )
140
+ {
141
+ var enumerated = await EnumerateAsyncEnumerable ( asyncEnumerable ) ;
142
+ await ExecuteAsyncCore ( context , result , enumerated . GetType ( ) , enumerated ) ;
143
+ }
144
+
145
+ private Task ExecuteAsyncCore ( ActionContext context , ObjectResult result , Type objectType , object value )
146
+ {
95
147
var formatterContext = new OutputFormatterWriteContext (
96
148
context . HttpContext ,
97
149
WriterFactory ,
98
150
objectType ,
99
- result . Value ) ;
151
+ value ) ;
100
152
101
153
var selectedFormatter = FormatterSelector . SelectFormatter (
102
154
formatterContext ,
@@ -138,5 +190,50 @@ private static void InferContentTypes(ActionContext context, ObjectResult result
138
190
result . ContentTypes . Add ( "application/problem+xml" ) ;
139
191
}
140
192
}
193
+
194
+ private Task < object > EnumerateAsyncEnumerable ( IAsyncEnumerable < object > value )
195
+ {
196
+ var type = value . GetType ( ) ;
197
+ if ( ! _asyncEnumerableConverters . TryGetValue ( type , out var result ) )
198
+ {
199
+ var enumerableType = ClosedGenericMatcher . ExtractGenericInterface ( type , typeof ( IAsyncEnumerable < > ) ) ;
200
+ result = null ;
201
+ if ( enumerableType != null )
202
+ {
203
+ var enumeratedObjectType = enumerableType . GetGenericArguments ( ) [ 0 ] ;
204
+
205
+ var converter = ( ReadAsyncEnumerableDelegate ) Converter
206
+ . MakeGenericMethod ( enumeratedObjectType )
207
+ . CreateDelegate ( typeof ( ReadAsyncEnumerableDelegate ) , this ) ;
208
+
209
+ _asyncEnumerableConverters . TryAdd ( type , converter ) ;
210
+ result = converter ;
211
+ }
212
+ }
213
+
214
+ return result ( value ) ;
215
+ }
216
+
217
+ private async Task < object > ReadAsyncEnumerable < T > ( object value )
218
+ {
219
+ var asyncEnumerable = ( IAsyncEnumerable < T > ) value ;
220
+ var result = new List < T > ( ) ;
221
+ var count = 0 ;
222
+
223
+ await foreach ( var item in asyncEnumerable )
224
+ {
225
+ if ( count ++ >= _mvcOptions . MaxIAsyncEnumerableBufferLimit )
226
+ {
227
+ throw new InvalidOperationException ( Resources . FormatObjectResultExecutor_MaxEnumerationExceeded (
228
+ nameof ( ObjectResultExecutor ) ,
229
+ _mvcOptions . MaxIAsyncEnumerableBufferLimit ,
230
+ value . GetType ( ) ) ) ;
231
+ }
232
+
233
+ result . Add ( item ) ;
234
+ }
235
+
236
+ return result ;
237
+ }
141
238
}
142
239
}
0 commit comments