Search code examples
c#asp.net-coreswaggerasp.net-core-5.0swashbuckle.aspnetcore

Timeout error in trying to access swagger.json in IHostedService through a httpclient call


I wanted to access the swagger.json file and deserialized it and store it into the cache.

I am making the async call in the IHostedService.

public class InitializeCacheService : IHostedService
{
    private IMemoryCache _cache;
    public static readonly string swaggerJson = "swagger.json";
    private readonly HttpClient _httpClient;
    private readonly IServiceProvider _serviceProvider;

    public InitializeCacheService(IMemoryCache cache, HttpClient client, IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        _httpClient = client;

        _cache = cache;
    }
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        if (!_cache.TryGetValue(swaggerJson, out SwaggerDocumentCache cacheEntry))
        {
            _httpClient.BaseAddress = new Uri("https://localhost:44397");

            var response = await _httpClient.GetAsync("swagger/v1/swagger.json");

            response.EnsureSuccessStatusCode();

            using var responseStream = await response.Content.ReadAsStreamAsync();

            cacheEntry = await JsonSerializer.DeserializeAsync
                <SwaggerDocumentCache>(responseStream);

            var cacheEntryOptions = new MemoryCacheEntryOptions()
                  .SetSlidingExpiration(TimeSpan.FromSeconds(3));

            _cache.Set(swaggerJson, cacheEntry, cacheEntryOptions);
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

But the application is spinning and spinning and it is getting time out in var response = await _httpClient.GetAsync("swagger/v1/swagger.json");

The error it is giving is:

IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request.

What I am missing here? What is the correct way of accessing the swagger.json to build a custom API playground application?


Solution

  • SwaggerMiddleware handles serving OpenAPI documents. We can use that as reference to build the document ourselves.

    First register SwaggerGenerator with DI:

    // Startup.Configure
    services.AddTransient<SwaggerGenerator>();
    

    Then inject it inside a class, here I'm using an endpoint to serve it directly:

    // Startup.Configure
    app.UseEndpoints(e =>
    {
        // ...
        e.MapGet("/openapi.json", context =>
        {
            // inject SwaggerGenerator
            var swaggerGenerator = context.RequestServices.GetRequiredService<SwaggerGenerator>();
            // document name is defined in Startup.ConfigureServices method inside the AddSwaggerGen call
            var doc = swaggerGenerator.GetSwagger("v1");
    
            // serialize the document as json
            using var writer = new StringWriter(CultureInfo.InvariantCulture);
            var serializer = new OpenApiJsonWriter(writer);
            doc.SerializeAsV3(serializer);
            var json = writer.ToString(); // this is the openapi document
            
            // serve it as json
            context.Response.ContentType = MediaTypeNames.Application.Json;
            return context.Response.WriteAsync(json, new UTF8Encoding(false));
        });
    });