Skip to content

Make SystemTextJsonInputFormatter IAsyncEnumerable aware #43489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
cmeyertons opened this issue Aug 23, 2022 · 6 comments
Open

Make SystemTextJsonInputFormatter IAsyncEnumerable aware #43489

cmeyertons opened this issue Aug 23, 2022 · 6 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-formatting
Milestone

Comments

@cmeyertons
Copy link

In ASP.NET Core 6, preventing buffering of output results was completed

Can we achieve the same benefit on the Input / Request side as well via the SystemTextJsonInputFormatter?

@javiercn javiercn added the old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Aug 23, 2022
@captainsafia
Copy link
Member

Triage: this work would depend on adding support for Stream parameters in MVC actions, see #41540.

@captainsafia captainsafia added this to the .NET 8 Planning milestone Aug 25, 2022
@cmeyertons
Copy link
Author

@captainsafia im sure there’s aspects I’m not considering, but we did get this working inserting a new InputFormatter into the pipeline.

Would it be helpful to post the approach here?

@CheloXL
Copy link

CheloXL commented Apr 1, 2023

@captainsafia im sure there’s aspects I’m not considering, but we did get this working inserting a new InputFormatter into the pipeline.

Would it be helpful to post the approach here?

I would like to see your approach, as currently I'm doing that by reading the Body in the action method and would like to have an argument in the method instead...

@cmeyertons
Copy link
Author

I am on vacation now away from my computer, back on April 6th. Can post example then!

@cmeyertons
Copy link
Author

@CheloXL As promised (a day late :) )

using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using System.Text;
using System.Text.Json;

namespace Example;

internal class AsyncEnumerableJsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy
{
    private static readonly Type _asyncEnumerableType = typeof(IAsyncEnumerable<>);

    public InputFormatterExceptionPolicy ExceptionPolicy => InputFormatterExceptionPolicy.MalformedInputExceptions;

    public JsonSerializerOptions SerializerOptions { get; }

    public AsyncEnumerableJsonInputFormatter()
    {
        SerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);

        SupportedEncodings.Add(UTF8EncodingWithoutBOM);

        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
    }

    protected override bool CanReadType(Type type)
    {
        return type.IsGenericType && type.GetGenericTypeDefinition() == _asyncEnumerableType;
    }

    public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
    {
        var modelReaderType = typeof(AsyncEnumerableStreamReader<>).MakeGenericType(context.ModelType.GetGenericArguments()[0]);
        var modelReader = (IAsyncEnumerableStreamReader)Activator.CreateInstance(modelReaderType)!;

        var result = InputFormatterResult.Success(modelReader.ReadModel(context.HttpContext.Request.Body, SerializerOptions));

        return Task.FromResult(result);
    }

    private interface IAsyncEnumerableStreamReader
    {
        object ReadModel(Stream inputStream, JsonSerializerOptions serializerOptions);
    }


    private class AsyncEnumerableStreamReader<T> : IAsyncEnumerableStreamReader
    {
        public object ReadModel(Stream inputStream, JsonSerializerOptions serializerOptions)
        {
            return JsonSerializer.DeserializeAsyncEnumerable<T>(inputStream, serializerOptions);
        }
    }
}

Bind to your MvcOptions during AddMvc (or calling Configure<MvcOptions>, etc)

builder.Services.AddMvc(options =>
{
    options.InputFormatters.Insert(0, new AsyncEnumerableJsonInputFormatter())
});

Controller methods now can accept IAsyncEnumerable<T> directly from the body

@CheloXL
Copy link

CheloXL commented Apr 7, 2023

Excellent, thank you very much!

@captainsafia captainsafia added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates and removed old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Jun 20, 2023
@captainsafia captainsafia modified the milestones: .NET 8 Planning, Backlog Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-mvc-formatting
Projects
No open projects
Status: No status
Development

No branches or pull requests

5 participants