public void TryGetReader_ReturnsCachedDelegate_WhenTypeImplementsMultipleIAsyncEnumerableContracts()
    {
        // Arrange
        var options          = new MvcOptions();
        var readerFactory    = new AsyncEnumerableReader(options);
        var asyncEnumerable1 = new MultiAsyncEnumerable();
        var asyncEnumerable2 = new MultiAsyncEnumerable();

        // Act
        Assert.True(readerFactory.TryGetReader(asyncEnumerable1.GetType(), out var reader1));
        Assert.True(readerFactory.TryGetReader(asyncEnumerable2.GetType(), out var reader2));

        // Assert
        Assert.Same(reader1, reader2);
    }
    public void TryGetReader_ReturnsDifferentInstancesForDifferentEnumerables()
    {
        // Arrange
        var options       = new MvcOptions();
        var readerFactory = new AsyncEnumerableReader(options);
        var enumerable1   = TestEnumerable();
        var enumerable2   = TestEnumerable2();

        // Act
        Assert.True(readerFactory.TryGetReader(enumerable1.GetType(), out var reader1));
        Assert.True(readerFactory.TryGetReader(enumerable2.GetType(), out var reader2));

        // Assert
        Assert.NotSame(reader1, reader2);
    }
    public void TryGetReader_ReturnsCachedDelegate()
    {
        // Arrange
        var options          = new MvcOptions();
        var readerFactory    = new AsyncEnumerableReader(options);
        var asyncEnumerable1 = TestEnumerable();
        var asyncEnumerable2 = TestEnumerable();

        // Act
        Assert.True(readerFactory.TryGetReader(asyncEnumerable1.GetType(), out var reader1));
        Assert.True(readerFactory.TryGetReader(asyncEnumerable2.GetType(), out var reader2));

        // Assert
        Assert.Same(reader1, reader2);
    }
Example #4
0
        /// <inheritdoc />
        public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (selectedEncoding == null)
            {
                throw new ArgumentNullException(nameof(selectedEncoding));
            }

            // Compat mode for derived options
            _jsonOptions ??= context.HttpContext.RequestServices.GetRequiredService <IOptions <MvcNewtonsoftJsonOptions> >().Value;

            var response = context.HttpContext.Response;

            var responseStream = response.Body;
            FileBufferingWriteStream?fileBufferingWriteStream = null;

            if (!_mvcOptions.SuppressOutputFormatterBuffering)
            {
                fileBufferingWriteStream = new FileBufferingWriteStream(_jsonOptions.OutputFormatterMemoryBufferThreshold);
                responseStream           = fileBufferingWriteStream;
            }

            var value = context.Object;

            if (value is not null && _asyncEnumerableReaderFactory.TryGetReader(value.GetType(), out var reader))
            {
                _logger ??= context.HttpContext.RequestServices.GetRequiredService <ILogger <NewtonsoftJsonOutputFormatter> >();
                Log.BufferingAsyncEnumerable(_logger, value);
                value = await reader(value);
            }

            try
            {
                await using (var writer = context.WriterFactory(responseStream, selectedEncoding))
                {
                    using var jsonWriter = CreateJsonWriter(writer);
                    var jsonSerializer = CreateJsonSerializer(context);
                    jsonSerializer.Serialize(jsonWriter, value);
                }

                if (fileBufferingWriteStream != null)
                {
                    response.ContentLength = fileBufferingWriteStream.Length;
                    await fileBufferingWriteStream.DrainBufferAsync(response.BodyWriter);
                }
            }
            finally
            {
                if (fileBufferingWriteStream != null)
                {
                    await fileBufferingWriteStream.DisposeAsync();
                }
            }
        }
    public async Task Reader_ThrowsIfIAsyncEnumerableThrows()
    {
        // Arrange
        var enumerable    = ThrowingAsyncEnumerable();
        var options       = new MvcOptions();
        var readerFactory = new AsyncEnumerableReader(options);

        // Act & Assert
        Assert.True(readerFactory.TryGetReader(enumerable.GetType(), out var reader));
        await Assert.ThrowsAsync <TimeZoneNotFoundException>(() => reader(enumerable, default));
    }
    public void TryGetReader_ReturnsFalse_IfTypeIsNotIAsyncEnumerable(Type type)
    {
        // Arrange
        var options         = new MvcOptions();
        var readerFactory   = new AsyncEnumerableReader(options);
        var asyncEnumerable = TestEnumerable();

        // Act
        var result = readerFactory.TryGetReader(type, out var reader);

        // Assert
        Assert.False(result);
    }
    public async Task CachedDelegate_CanReadEnumerableInstanceMultipleTimes_ThatProduceDifferentResults()
    {
        // Arrange
        var options          = new MvcOptions();
        var readerFactory    = new AsyncEnumerableReader(options);
        var asyncEnumerable1 = TestEnumerable();
        var asyncEnumerable2 = TestEnumerable(4);

        // Act
        Assert.True(readerFactory.TryGetReader(asyncEnumerable1.GetType(), out var reader));

        // Assert
        Assert.Equal(new[] { "0", "1", "2" }, await reader(asyncEnumerable1, default));
        Assert.Equal(new[] { "0", "1", "2", "3" }, await reader(asyncEnumerable2, default));
    }
    public async Task Reader_PassesCancellationTokenToIAsyncEnumerable()
    {
        // Arrange
        var enumerable          = AsyncEnumerable();
        var options             = new MvcOptions();
        CancellationToken token = default;
        var readerFactory       = new AsyncEnumerableReader(options);
        var cts = new CancellationTokenSource();

        // Act & Assert
        Assert.True(readerFactory.TryGetReader(enumerable.GetType(), out var reader));
        await reader(enumerable, cts.Token);

        cts.Cancel();
        Assert.Equal(cts.Token, token);

        async IAsyncEnumerable <string> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
    public async Task TryGetReader_ReturnsReaderForIAsyncEnumerableOfValueType()
    {
        // Arrange
        var options         = new MvcOptions();
        var readerFactory   = new AsyncEnumerableReader(options);
        var asyncEnumerable = PrimitiveEnumerable();

        // Act
        var result = readerFactory.TryGetReader(asyncEnumerable.GetType(), out var reader);

        // Assert
        Assert.True(result);
        var readCollection = await reader(asyncEnumerable, default);

        var collection = Assert.IsAssignableFrom <ICollection <int> >(readCollection);

        Assert.Equal(new[] { 0, 1, 2, }, collection);
    }
    public async Task TryGetReader_ReturnsReaderForIAsyncEnumerable()
    {
        // Arrange
        var options         = new MvcOptions();
        var readerFactory   = new AsyncEnumerableReader(options);
        var asyncEnumerable = TestEnumerable();

        // Act
        var result = readerFactory.TryGetReader(asyncEnumerable.GetType(), out var reader);

        // Assert
        Assert.True(result);
        var readCollection = await reader(asyncEnumerable, default);

        var collection = Assert.IsAssignableFrom <ICollection <string> >(readCollection);

        Assert.Equal(new[] { "0", "1", "2", }, collection);
    }
    public async Task Reader_ThrowsIfBufferLimitIsReached()
    {
        // Arrange
        var enumerable = TestEnumerable(11);
        var expected   = $"'AsyncEnumerableReader' reached the configured maximum size of the buffer when enumerating a value of type '{enumerable.GetType()}'. " +
                         "This limit is in place to prevent infinite streams of 'IAsyncEnumerable<>' from continuing indefinitely. If this is not a programming mistake, " +
                         $"consider ways to reduce the collection size, or consider manually converting '{enumerable.GetType()}' into a list rather than increasing the limit.";
        var options = new MvcOptions {
            MaxIAsyncEnumerableBufferLimit = 10
        };
        var readerFactory = new AsyncEnumerableReader(options);

        // Act
        Assert.True(readerFactory.TryGetReader(enumerable.GetType(), out var reader));
        var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => reader(enumerable, default));

        // Assert
        Assert.Equal(expected, ex.Message);
    }
    public async Task Reader_ReadsIAsyncEnumerable_ImplementingMultipleAsyncEnumerableInterfaces()
    {
        // This test ensures the reader does not fail if you have a type that implements IAsyncEnumerable for multiple Ts
        // Arrange
        var options         = new MvcOptions();
        var readerFactory   = new AsyncEnumerableReader(options);
        var asyncEnumerable = new MultiAsyncEnumerable();

        // Act
        var result = readerFactory.TryGetReader(asyncEnumerable.GetType(), out var reader);

        // Assert
        Assert.True(result);
        var readCollection = await reader(asyncEnumerable, default);

        var collection = Assert.IsAssignableFrom <ICollection <object> >(readCollection);

        Assert.Equal(new[] { "0", "1", "2", }, collection);
    }
Example #13
0
    /// <summary>
    /// Executes the <see cref="JsonResult"/> and writes the response.
    /// </summary>
    /// <param name="context">The <see cref="ActionContext"/>.</param>
    /// <param name="result">The <see cref="JsonResult"/>.</param>
    /// <returns>A <see cref="Task"/> which will complete when writing has completed.</returns>
    public async Task ExecuteAsync(ActionContext context, JsonResult result)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (result == null)
        {
            throw new ArgumentNullException(nameof(result));
        }

        var jsonSerializerSettings = GetSerializerSettings(result);

        var response = context.HttpContext.Response;

        ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
            result.ContentType,
            response.ContentType,
            (DefaultContentType, Encoding.UTF8),
            MediaType.GetEncoding,
            out var resolvedContentType,
            out var resolvedContentTypeEncoding);

        response.ContentType = resolvedContentType;

        if (result.StatusCode != null)
        {
            response.StatusCode = result.StatusCode.Value;
        }

        Log.JsonResultExecuting(_logger, result.Value);

        var responseStream = response.Body;
        FileBufferingWriteStream?fileBufferingWriteStream = null;

        if (!_mvcOptions.SuppressOutputFormatterBuffering)
        {
            fileBufferingWriteStream = new FileBufferingWriteStream();
            responseStream           = fileBufferingWriteStream;
        }

        try
        {
            var value = result.Value;
            if (value != null && _asyncEnumerableReaderFactory.TryGetReader(value.GetType(), out var reader))
            {
                Log.BufferingAsyncEnumerable(_logger, value);
                try
                {
                    value = await reader(value, context.HttpContext.RequestAborted);
                }
                catch (OperationCanceledException) when(context.HttpContext.RequestAborted.IsCancellationRequested)
                {
                }
                if (context.HttpContext.RequestAborted.IsCancellationRequested)
                {
                    return;
                }
            }

            await using (var writer = _writerFactory.CreateWriter(responseStream, resolvedContentTypeEncoding))
            {
                using var jsonWriter           = new JsonTextWriter(writer);
                jsonWriter.ArrayPool           = _charPool;
                jsonWriter.CloseOutput         = false;
                jsonWriter.AutoCompleteOnClose = false;

                var jsonSerializer = JsonSerializer.Create(jsonSerializerSettings);

                jsonSerializer.Serialize(jsonWriter, value);
            }

            if (fileBufferingWriteStream != null)
            {
                await fileBufferingWriteStream.DrainBufferAsync(response.Body);
            }
        }
        finally
        {
            if (fileBufferingWriteStream != null)
            {
                await fileBufferingWriteStream.DisposeAsync();
            }
        }
    }