Migrate From ASP.NET Core 2.x Web API To .Net Core 3.1

Introduction 

 
In this article, I walk you through updating an existing .NET Core 2.x Web API application to .NET Core 3.1. Migrating to .NET core 3.1 will help you take the advantages of .NET core 3.1.
 
Here you will find the new features of .NET core 3.1.
 
I will explain all the steps I did to migrate one of my projects. There was a lot of breaking changes in the newer version. You can find all the breaking changes here.
 
Prerequisites
 
Visual Studio 2019 (As .NET core 3.x works on VS 2019) 
 

Update Target Framework from .net core 2.x to 3.1

 
Go to your project properties and change the Target Framework:
 
 
Or you can search for tag <TargetFramework> in the .csproj file and change its value to "netcoreapp3.1" as shown below:
 

Delete the NuGet Package References of which Have Been Removed and Moved to the Shared Library

 
Remove "Microsoft.AspNetCore.App"from your project Dependencies or .csproj file:
  1. <PackageReference Include="Microsoft.AspNetCore.App" />  
Remove "Microsoft.AspNetCore.App.Razor.Design" from your project Dependencies or .csproj file:
  1. <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" />  

Update NuGet package references (If applicable, Either in .csproj file or directly in your project Dependencies)

 
Swagger
 
It is used for API Documentation. If you are using then update to version “Swashbuckle.AspNetCore” to version 5.5.1There are many changes in Swagger configuration in .NET core 3.1
  1. <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />   
NewtonSoft / Any JSON Serializer 
 
If you are using any JSON serializer, then add the reference of “Microsoft.AspNetCore.Mvc.NewtonsoftJson” of version 3.1.5  Because in .NET core 3.1, Microsoft has introduced an in-build faster JSON serializer. 
  1. <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />   
Kestrel Web Server
 
Kestrel is a preferred web server that is implemented on basis of Libuv library (Which is also used in node.js), If you are using it in your application, Then update “Microsoft.ServiceFabric.AspNetCore.Kestrel” to version 4.1.417
 
Note
If you are using Nagle algorithm (NoDelay) in Kestrel webserver to reduce the number of packets over the TCP network to improve the efficiency, like below (in your kestrel configuration).
  1. listenOptions.NoDelay = true;  
Then, you need to add following Nuget package as it is moved to separate package
 
“Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv” of version 3.1.5
 

Modify ConfigureServices method of Startup.cs

 
Replace AddMvc with AddControllers, Microsoft replaced AddMVC with two options:
  1. Web Application (MVC) - We should replace AddMVC with AddControllersWithViews
  2. Web API - We should replace AddMVC with AddControllers, The motivation here is to not load the libraries or components which are related to views 
From:
  1. services.AddMvc(options =>  
To:
  1. services.AddControllers(options =>  
Replace the .Net Core MVC Compatability version from 2.xto 3.0 in SetCompatibilityVersion
 
From:
  1. services.AddControllers(options => {  
  2.     options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());  
  3.     options.OutputFormatters.RemoveType < HttpNoContentOutputFormatter > ();  
  4. }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2)  
To:
  1. services.AddControllers(options => {  
  2.     options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());  
  3.     options.OutputFormatters.RemoveType < HttpNoContentOutputFormatter > ();  
  4. }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0)  
Replace AddJsonOptions with AddNewtonsoftJson It is inbuilt faster JSON Serializer as covered in #3.b
 
From:
  1. services.AddMvc(options => {  
  2.     options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());  
  3.     options.OutputFormatters.RemoveType < HttpNoContentOutputFormatter > ();  
  4. }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddJsonOptions(options => {  
  5.     options.SerializerSettings.ContractResolver = new DefaultContractResolver();  
  6.     options.SerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;  
  7. });  
To:
  1. services.AddControllers(options => {  
  2.     options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());  
  3.     options.OutputFormatters.RemoveType < HttpNoContentOutputFormatter > ();  
  4. }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddNewtonsoftJson(options => {  
  5.     options.SerializerSettings.ContractResolver = new DefaultContractResolver();  
  6.     options.SerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;  
  7. });  
If you allow HTTP requests from cross origin or cross domains, then replace WithOrigins("*")with AllowAnyOrigin()
 
From:
  1. services.AddCors(options => {  
  2.             options.AddPolicy("CorsPolicy", builder => builder.WithOrigins("*")  
To:
  1. services.AddCors(options => {  
  2.             options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()  
If you are using Swagger for API documentation, then change the below bold text:
 
From:
  1. {  
  2.     c.SwaggerDoc("v1"new Info {  
  3.         Title = "Your API Title",  
  4.             Version = "API Version Number",  
  5.             Description = "Description of your API"  
  6.     });  
  7.     c.AddSecurityDefinition("Bearer"new ApiKeyScheme {  
  8.         In = "header",  
  9.             Description = "Please enter JWT with Bearer into field",  
  10.             Name = "Authorization",  
  11.             Type = "apiKey"  
  12.     });  
  13.     c.AddSecurityRequirement(new Dictionary < string, IEnumerable < string >> {  
  14.         {  
  15.             "Bearer",  
  16.             Enumerable.Empty < string > ()  
  17.         },  
  18.     });  
  19.     c.CustomSchemaIds(i => i.FullName);  
  20.     c.OperationFilter < SwaggerParametersFilter > ();  
To:
  1. services.AddSwaggerGen(c => {  
  2.             c.SwaggerDoc("v1"new OpenApiInfo {  
  3.                 Title = " Your API Title ",  
  4.                     Version = "API Version Number",  
  5.                     Description = "Description of your API "  
  6.             });  
  7.             c.AddSecurityDefinition("Bearer"new OpenApiSecurityScheme {  
  8.                 Description = "Please enter JWT with Bearer into field”  
  9.                 Name = "Authorization",  
  10.                     In = ParameterLocation.Header,  
  11.                     Type = SecuritySchemeType.ApiKey,  
  12.                     Scheme = "Bearer"  
  13.             });  
  14.             c.AddSecurityRequirement(new OpenApiSecurityRequirement() {  
  15.                 {  
  16.                     new OpenApiSecurityScheme {  
  17.                         Reference = new OpenApiReference {  
  18.                                 Type = ReferenceType.SecurityScheme,  
  19.                                     Id = "Bearer"  
  20.                             },  
  21.                             Scheme = "oauth2",  
  22.                             Name = "Bearer",  
  23.                             In = ParameterLocation.Header,  
  24.                     },  
  25.                     new List < string > ()  
  26.                 }  
  27.             });  
  28.             c.CustomSchemaIds(i => i.FullName);  
  29.             c.OperationFilter < SwaggerParametersFilter > ();  
I will cover SwaggerParametersFilter in #6. 
 

In Configure Method of Startup.cs

 
Replace, IHostingEnvironment with IWebHostEnvironment HostingEnviorment is obsolete now and it will be removed from future version of .net core. It is injected in controller and gives the information of applications web hosting enviornment.
 
From:
  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)  
To:
  1. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider services)  
Use your CORS Policy name define in #4.d, instead of defining rules here:
 
From:
  1. app.UseCors(builder => builder.WithOrigins("*")  
To:
  1. app.UseCors("CorsPolicy");  
Here CorsPolicy is the name of cors policy. 
 
Replace app.UseMvc() with EndPoint Routing, as below,
 
From:
  1. app.UseMvc();  
To:
  1. app.UseRouting();  
  2. app.UseEndpoints(endpoints => {  
  3.     endpoints.MapDefaultControllerRoute();  
  4.     endpoints.MapControllerRoute("default""{controller=Home}/{action=Index}/{id?}");  
  5. });  
In .NET core, The EndPoint routing is the new feature introduced, It provides the routing information in the middleware of your request pipeline.
 
So, the difference between AddMVC and EndPoint routing is, The AddMVC provides the routing information after the completion of middleware of your request pipeline, So we did not have route information in middleware. In the Endpoint routing, we are configuring it and getting it inside the middleware request pipeline.
 
If you are using Swagger Operation Filter to apply some logic globally on your all actions method or endpoints, then change the bold text below:
 
From:
  1. public class SwaggerParametersFilter: IOperationFilter {  
  2.         public void Apply(Operation operation, OperationFilterContext context) {  
  3.                 if (operation.Parameters == null) operation.Parameters = new List < IParameter > ();  
  4.                 operation.Parameters.Add(new NonBodyParameter {  
  5.                     Name = "HeaderName",  
  6.                         In = "header",  
  7.                         Type = "string",  
  8.                         Required = true  
  9.                 });  
To:
  1. public class SwaggerParametersFilter: IOperationFilter {  
  2.         public void Apply(OpenApiOperation operation, OperationFilterContext context) {  
  3.                 if (operation.Parameters == null) operation.Parameters = new List < OpenApiParameter > ();  
  4.                 operation.Parameters.Add(new OpenApiParameter {  
  5.                     Name = " HeaderName",  
  6.                         In = ParameterLocation.Header,  
  7.                         Required = true  
  8.                 });  
If you are usingStatelessService with Kestrel, Then in theCreateServiceInstanceListenersmethod, if you are usinglistenOptions.NoDelay =true; (It is using Nagle which was covered in #3.c section for efficiency)
 
From:
  1. return new WebHostBuilder().UseKestrel(opt => {  
  2.             int Httpsport = serviceContext.CodePackageActivationContext.GetEndpoint("1HttpsEndpoint").Port;  
  3.             opt.Listen(IPAddress.IPv6Any, Httpsport, listenOptions => {  
  4.                 listenOptions.UseHttps(GetCertificateFromStore());  
  5.                 listenOptions.NoDelay = true;  
  6.             });  
To (Remove the bold blue text and add configuration of "UseLibuvas below),
  1. return new WebHostBuilder().UseKestrel(opt => {  
  2.     int Httpsport = serviceContext.CodePackageActivationContext.GetEndpoint("1HttpsEndpoint").Port;  
  3.     opt.Listen(IPAddress.IPv6Any, Httpsport, listenOptions => {  
  4.         listenOptions.UseHttps(GetCertificateFromStore());  
  5.     });  
  6. }).UseLibuv(opts => {  
  7.     opts.NoDelay = true;  
  8. })  
If you are using Service Fabric to host your API / Microservice, then please update the Service Fabric cluster with the latest version.
 

Conclusion

 
I tried to cover almost all the basic things which people generally use in API implementation. The good thing is that .NET Core 3.1 has long term support (until December 3, 2022). 
 
Thanks for reading this article!