2

I have an endpoint that returns an IAsyncEnumerable

[HttpPost("GetByDates")]
[ProducesResponseType(typeof(IAsyncEnumerable<DayModel>), StatusCodes.Status200OK)]
public async IAsyncEnumerable<DayModel> GetByDates([FromBody] DayModelGetByDatesRequest request)
{
    await foreach (var dayModel in _dayService.GetAsync(request.channelGuid, request.dates.ToArray(), request.onlyPublished, request.IncludeDiscardedScheduledItems))
    {
        yield return dayModel;
    };

}

The generated .json schema looks like this:

    "/Private/Days/GetByDates": {
  "post": {
    "tags": [
      "Days"
    ],
    "operationId": "Days_GetByDates",
    "requestBody": {
      "x-name": "request",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/DayModelGetByDatesRequest"
          }
        }
      },
      "required": true,
      "x-position": 1
    },
    "responses": {
      "200": {
        "description": "",
        "content": {
          "application/json": {
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Day"
              }
            }
          }
        }
      }
    }
  }
}

and the Nswag is configured like this:

        services.AddOpenApiDocument(configure =>
    {
        configure.Title = "MyAppName (Private)";
        configure.DocumentName = "private";
        configure.SchemaType = SchemaType.OpenApi3;
        configure.SchemaNameGenerator = new CustomNameGenerator();
        configure.AddOperationFilter(new RequireUserHeaderParameterFilter().Process);
        configure.AddSecurity("Bearer", new OpenApiSecurityScheme()
        {
            In = OpenApiSecurityApiKeyLocation.Header,
            Description = "Please enter the word \"Bearer\" followed by space and token",
            Name = "Authorization",
            Type = OpenApiSecuritySchemeType.ApiKey,
        });
        configure.ApiGroupNames = new string[] { "Private" };
    });

And another project uses the .json schema to genereate a client of its own that seems to use Newtonsoft Json instead of System.Text.Json

[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.18.0.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class TablaApiClient 
{
    private string _baseUrl = "https://localhost:5102";
    private System.Net.Http.HttpClient _httpClient;
    private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;

    public TablaApiClient(System.Net.Http.HttpClient httpClient)
    {
        _httpClient = httpClient;
        _settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
    }

    private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
    {
        var settings = new Newtonsoft.Json.JsonSerializerSettings();
        UpdateJsonSerializerSettings(settings);
        return settings;
    }

    public string BaseUrl
    {
        get { return _baseUrl; }
        set { _baseUrl = value; }
    }

    protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } }

    partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);

The endpoint doesn't serialize the IAsyncEnumerable and return an ICollection instead:

enter image description here

The Swagger is configured like so:

        services.AddOpenApiDocument(configure =>
    {
        configure.Title = "My App";
        configure.SchemaType = NJsonSchema.SchemaType.OpenApi3;
        configure.AddSecurity("AzureAsIdentityProvider", new OpenApiSecurityScheme
        {
            Type = OpenApiSecuritySchemeType.OAuth2,
            Flows = new OpenApiOAuthFlows
            {
                AuthorizationCode = new OpenApiOAuthFlow
                {
                    AuthorizationUrl = $"{settings.Instance}/{settings.TenantId}/oauth2/v2.0/authorize",
                    TokenUrl = $"{settings.Instance}/{settings.TenantId}/oauth2/v2.0/token",
                }
            }
        });

        configure.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("AzureAsIdentityProvider"));

    });

Is there a way for the generated client to properly serialize and understand it is working towards an IAsyncEnumerable endpoint so that I can work with the stream instead of fully buffered collection?

I read that System.Text.Json serializes IAsyncEnumerable out of the box. Is there a way to get Swagger to use that instead of Newtonsoft?

Olaf Dlugosz
  • 81
  • 1
  • 4
  • `IAsyncEnumerable` is being serialised - to a JSON array. The JSON array is then deserialised to an `ICollection`. – SBFrancies Jan 30 '23 at 09:58
  • Sure it works but I don't want it to buffer. I'd like to send the stream through the 2 projects and use the ICollection as IAsyncEnumerable – Olaf Dlugosz Jan 30 '23 at 11:06
  • What version of .Net / .Net Core are you using? – SBFrancies Jan 30 '23 at 12:22
  • Not with JSON in between. JSON knows about Arrays and such but thats it. It has no idea of the different approaches to list presentation in different source/target languages. There is nothing here that supports "streaming" even the server side serializer does not really know anything about IAsyncEnumerable. It will just enumerate everything and create a JSON from that. – Ralf Jan 30 '23 at 12:24
  • The issue is going to be with the code the generates the JSON or the code which generates the client from the JSON - I don't think you've shown the latter. It looks like NSwag doesn't currently support IAsyncEnumerator out of the box (https://github.com/RicoSuter/NSwag/issues/3909) but it may be possible to generate the correct JSON using a filter. It would really depend on what's converting the JSON to the C# client though. – SBFrancies Jan 30 '23 at 13:09
  • I'm using .NET 7. And this indeed is NSwag that does the conversion to the C# client and I think the problem lies in that i uses Newtonsoft.Json instead System.Text.Json which serializes IAsyncEnumerable out of the box. Does anyone know how to configure it use System.Text.Json ? – Olaf Dlugosz Jan 30 '23 at 14:26
  • It's not really serialisation - it's the model/return type of your NSwag generated C# client. You haven't posted the code which generates the client (or are you using the NSwag UI?). In the UI it is a dropdown option but it looks like support is not great: https://github.com/RicoSuter/NSwag/issues/4083. If you have the option you could manually update the `Days_GetByDatesAsync` method in the generated code. – SBFrancies Jan 30 '23 at 14:42
  • Sorry, just noticed that you have provided the `services.AddOpenApiDocument` method. Apologies. – SBFrancies Jan 30 '23 at 14:45
  • This may help: https://stackoverflow.com/a/71867717/8532748 – SBFrancies Jan 30 '23 at 15:07

0 Answers0