| File: Infrastructure\AsyncEnumerableReader.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Collections; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.Extensions.Internal; #if JSONNET namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson; #else using Microsoft.AspNetCore.Mvc.Core; namespace Microsoft.AspNetCore.Mvc.Infrastructure; #endif /// <summary> /// Type that reads an <see cref="IAsyncEnumerable{T}"/> instance into a /// generic collection instance. /// </summary> /// <remarks> /// This type is used to create a strongly typed synchronous <see cref="ICollection{T}"/> instance from /// an <see cref="IAsyncEnumerable{T}"/>. An accurate <see cref="ICollection{T}"/> is required for XML formatters to /// correctly serialize. /// </remarks> internal sealed class AsyncEnumerableReader { private readonly MethodInfo Converter = typeof(AsyncEnumerableReader).GetMethod( nameof(ReadInternal), BindingFlags.NonPublic | BindingFlags.Instance)!; private readonly ConcurrentDictionary<Type, Func<object, CancellationToken, Task<ICollection>>?> _asyncEnumerableConverters = new(); private readonly MvcOptions _mvcOptions; /// <summary> /// Initializes a new instance of <see cref="AsyncEnumerableReader"/>. /// </summary> /// <param name="mvcOptions">Accessor to <see cref="MvcOptions"/>.</param> public AsyncEnumerableReader(MvcOptions mvcOptions) { _mvcOptions = mvcOptions; } /// <summary> /// Attempts to produces a delegate that reads an <see cref="IAsyncEnumerable{T}"/> into an <see cref="ICollection{T}"/>. /// </summary> /// <param name="type">The type to read.</param> /// <param name="reader">A delegate that when awaited reads the <see cref="IAsyncEnumerable{T}"/>.</param> /// <returns><see langword="true" /> when <paramref name="type"/> is an instance of <see cref="IAsyncEnumerable{T}"/>, otherwise <see langword="false"/>.</returns> public bool TryGetReader(Type type, [NotNullWhen(true)] out Func<object, CancellationToken, Task<ICollection>>? reader) { if (!_asyncEnumerableConverters.TryGetValue(type, out reader)) { var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IAsyncEnumerable<>)); if (enumerableType is null) { // Not an IAsyncEnumerable<T>. Cache this result so we avoid reflection the next time we see this type. reader = null; _asyncEnumerableConverters.TryAdd(type, reader); } else { var enumeratedObjectType = enumerableType.GetGenericArguments()[0]; var converter = (Func<object, CancellationToken, Task<ICollection>>)Converter .MakeGenericMethod(enumeratedObjectType) .CreateDelegate(typeof(Func<object, CancellationToken, Task<ICollection>>), this); reader = converter; _asyncEnumerableConverters.TryAdd(type, reader); } } return reader != null; } private async Task<ICollection> ReadInternal<T>(object value, CancellationToken cancellationToken) { var asyncEnumerable = ((IAsyncEnumerable<T>)value).WithCancellation(cancellationToken); 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; } }