Swashbuckle.AspNetCore: [5.0.0] Combination of XElement & relative swagger endpoint causes recursion and errors in swagger ui

When using a relative path(such as “./swagger/v1/swagger.json”) in the SwaggerEndpoint for the UI with a circular object(such as an XElement), this causes strange errors and recursion in the network tab. The same configuration worked with .NET Core 2 and v4 of Swashbuckle.AspNetCore. This is done using NewtonsoftJson, because System.Text.Json has an issue with circular objects too.

Problem: Capture

Capture

Reproduction: Nugets: Capture

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddNewtonsoftJson();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" });
    });
    services.AddSwaggerGenNewtonsoftSupport();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseSwagger();

    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("./swagger/v1/swagger.json", "Test API V1");
        c.RoutePrefix = "";
    });

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

TestController.cs:

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    private readonly ILogger<TestController> _logger;

    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public ActionResult<XElement> Test()
    {
        var rng = new Random();
        return XElement.Parse(@"<test>" + rng.Next(0, 101) + @"</test>");
    }
}

Run the test service and go to https://localhost:5001/index.html and expand the /Test method to trigger the error.


Conclusion: We are forced to do some magical things to get around this issue, any workarounds are welcome too.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 18 (6 by maintainers)

Most upvoted comments

OK interesting … it seems the UI not handling the ugly, complex schema that’s (incorrectly) being generated for XElement is somehow only happening when relative paths are in use. This needs further investigation on my side. I have a feeling it might be related to self-referencing types. I’ll try get a tighter repro for that.

Either way, you can probably avoid this issue by doing what I suggested, which would eradicate the problematic schema entirely. I imagine having an “open schema” would be preferable anyway to the complexy schema that actually looks nothing like the response that’s ultimately returned.

OK - so the root cause is that Swashbuckle is describing the response to be what it “thinks” an XElement instance would look like when serialized to JSON. It’s treating it as a plain old DTO object, where all public properties and fields pretty much map 1:1 to JSON object properties. Of course, this looks nothing like the actual JSON structure that’s returned by the service. This is because Newtonsoft treats XElement differently (like a unique snowflake). Rather than basing the JSON on public properties/fields of the class itself, it takes the XML string captured in a given instance, and then converts that to JSON.

This has two repurcusions in the context of Swashbuckle.

  1. It doesn’t currently support the nuanced Newtonsoft behavior around XElement (this can be added but with a limitation - see next point)
  2. When describing responses that are based on XElement, the best Swashbuckle can do is describe it as an “open schema”. That is, it can’t provide any information about the structure of the JSON response. This is because the structure is determined at run-time (i.e. when a given XElement is instantiated and assigned values) and therefore can’t be inferred by Swashbuckle from the code alone.

I’ll work to add some level of support for the XElement type (with the limitation mentioned above) but in the meantime, you could support it in the same way with a custom type mapping:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" });

    c.MapType<XElement>(() => new OpenApiSchema());
});