Upgrading to .NET Core 3.0 – Troubleshooting the Swagger UI

Let me share a piece of my experience in the various issues I faced with setting up Swagger in my ASP.NET Core API, while upgrading it from the legacy aspnetcore2.2 to the most recent aspnetcore3.1 SDK

The third version of dotnetcore aka the ASP.NET Core 3.x has brought about significant changes in application performance. Upgrading existing dotnetcore applications from dotnetcore2.2 to dotnetcore3.x required a few changes, since the latest version has changed the way the routes are mapped, services are injected and maintained, the serialization properties – to mention a few. In our Migration to AspNetCore3.x series, we were just getting started with looking at how to migrate a typical ASP.NET Core application which was built on the dotnet core 2.2 sdk to the latest dotnet core 3.0 sdk platform, and were observing the things that need to be changed when migrating. And this article, let’s look at what happens to the swaggerUI configured when the sdk is upgraded to dotnet core 3.0 and what needs to be changed.

What is Swagger?

Swagger is an open source API documentation library, which is now called OpenAPI. The library analyses the API application for possible endpoints and generates a schema based document, along with information about how the endpoints are to be used and what the expected responses would be. This enables developers to spend less time in documenting the APIs developed and also helps in complying with a documentation standard that is understood everywhere. The swagger platform develops exportable schema in the format of yaml or json, which can be fed to any existing swaggerUI for quick documentation.

Configuring Swagger – AspNetCore3.x

Swagger is implemented in a dotnet core application by installing the available Swashbuckle.AspNetCore nuget package, through a package manager or by dotnet CLI using the command.

dotnet add package Swashbuckle.AspNetCore --version 4.0.1

To integrate Swagger, we would need to register Swagger as a services in the AspNetCore application, and also include the Swagger middleware to the application pipeline. This facilitates creating the default Swagger endpoint to the available routes and how a request to Swagger endpoint would be handled.

namespace ReadersApi
{
    public class Startup
    {
        // Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
	    // other implementations 

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info 
                    { 
                        Title = "Reader API", 
                        Version = "V1" 
                    });
            });

            // .....
        }

        // Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
	    // .........
            
	    app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });
            
            // .........
        }
    }
}

Adding Security and Specifying Binary:

For APIs which involve passing an Authorization Token aka JWT token in the Headers, we can enforce within our Swagger documentation by adding a SecurityDefinition to the services as below:

c.AddSecurityDefinition("Bearer",
    new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter JWT with Bearer into field",
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });

// Set the comments path for the Swagger JSON and UI.
//var xmlFile = $"{Assembly.GetEntryAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);

Upgrading from AspNetCore2.2

When we upgrade the sdk by changing the <TargetFramework>netcoreapp2.2</TargetFramework> tag in the csproj to netcoreapp3.x and when we try to run the application, we find the below runtime error.

System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Swashbuckle.AspNetCore.SwaggerGen.ISchemaRegistryFactory Lifetime: Transient ImplementationType: Swashbuckle.AspNetCore.SwaggerGen.SchemaRegistryFactory': Failed to compare two elements in the array.
 ---> System.InvalidOperationException: Failed to compare two elements in the array.
 ---> System.TypeLoadException: Could not load type 'Microsoft.AspNetCore.Mvc.MvcJsonOptions' from assembly 'Microsoft.AspNetCore.Mvc.Formatters.Json, Version=3.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
   at System.Signature.GetSignature(Void* pCorSig, Int32 cCorSig, RuntimeFieldHandleInternal fieldHandle, IRuntimeMethodInfo methodHandle, RuntimeType declaringType)
   at System.Reflection.RuntimeConstructorInfo.get_Signature()
   at System.Reflection.RuntimeConstructorInfo.GetParametersNoCopy()
   at System.Reflection.RuntimeConstructorInfo.GetParameters()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c.<CreateConstructorCallSite>b__16_1(ConstructorInfo a,
ConstructorInfo b)
   at System.Collections.Generic.ArraySortHelper`1.SwapIfGreater(T[] keys, Comparison`1 comparer, Int32 a, Int32 b)
   at System.Collections.Generic.ArraySortHelper`1.IntroSort(T[] keys, Int32 lo, Int32 hi, Int32 depthLimit, Comparison`1 comparer)
   at System.Collections.Generic.ArraySortHelper`1.IntrospectiveSort(T[] keys, Int32 left, Int32 length, Comparison`1 comparer)
   at System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, Comparison`1 comparer)
   --- End of inner exception stack trace ---
   at System.Collections.Generic.ArraySortHelper`1.Sort(T[] keys, Int32 index, Int32 length, Comparison`1 comparer)
   at System.Array.Sort[T](T[] array, Comparison`1 comparison)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)<---

Although the error is irrelevant and pointless for itself, this particular error line would explain about the issue.

ServiceType: Swashbuckle.AspNetCore.SwaggerGen.ISchemaRegistryFactory Lifetime: Transient ImplementationType: Swashbuckle.AspNetCore.SwaggerGen.SchemaRegistryFactory': Failed to compare two elements in the array.

After trying to search for this error, we came across the official github page of swagger where the project is being maintained and only then that we realized that the version we were using (4.0.1 of swagger) is no longer supported in dotnetcore 3.x and were recommended to upgrade to the latest available version

And so we upgraded the version to 5.4.1 as suggested by changing the version for Swashbuckle.AspNetCore in the csproj file

    <PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.4.1" />

and by trying to build.

    > dotnet clean && dotnet build

Then came the next set of errors,

Startup.cs(33,40): error CS0246: The type or namespace name 'Info' could not be found (are you missing a using directive or an assembly reference?) [E:DotNet Core Projectsdotnetcore3appdotnetcore3app.csproj]

Turns out that the namespaces have changed alot since the latest version and the class Info is no more available under the namespace Swashbuckle.AspNetCore.Swagger as it used to be. Now we add swaggerUI to the service container as shown below:

using Microsoft.OpenApi.Models;

namespace dotnetcore3app
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            ....

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", 
                new OpenApiInfo {
                    Title = "Reader API", 
                    Version = "V1",
                    Description = "The API documentation for the Reader API"
                });

                c.AddSecurityDefinition("Bearer", 
                new .OpenApiSecurityScheme {
                    In = ParameterLocation.Header,
                    Description = 
                        "The Bearer Token needed for Authorizing requests",
                    Name = "",
                    Type = SecuritySchemeType.ApiKey
                });
            });

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

            app.UseSwagger();

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

         }
    }
}

The classes are now moved under Microsoft.OpenApi.Models namespace.

When we build the project, we no more get errors and when we go to the endpoint /swagger, you would see a swagger UI with all the endpoints defined available, and there’s an option to export this schema at the endpoint /swagger/v1/swagger.json

wp-content/uploads/2022/05/swagger.png


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *