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 advantage 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 were 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

 Target Framework

Or you can search for tag <TargetFramework> in the .csproj file and change its value to "netcoreapp3.1" as shown below.

Project sdk

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

Remove "Microsoft.AspNetCore.App" from your project Dependencies or .csproj file.

<PackageReference Include="Microsoft.AspNetCore.App" />

Remove "Microsoft.AspNetCore.App.Razor.Design" from your project Dependencies or .csproj file.

<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

<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.&

<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" />

KestrelWeb 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).

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

services.AddMvc(options =>

To

services.AddControllers(options =>

Replace the .Net Core MVC Compatability version from 2.xto 3.0 in SetCompatibilityVersion

From

services.AddControllers(options =>
{
    options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());
    options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

To

services.AddControllers(options =>
{
    options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());
    options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

Replace AddJsonOptions with AddNewtonsoftJson It is inbuilt faster JSON Serializer as covered in #3.b

From

services.AddMvc(options => {
    options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());
    options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddJsonOptions(options => {
    options.SerializerSettings.ContractResolver = new DefaultContractResolver();
    options.SerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
});

To

services.AddControllers(options =>
{
    options.Conventions.Add(new AddAuthorizeFiltersControllerConvention());
    options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ContractResolver = new DefaultContractResolver();
    options.SerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
});

If you allow HTTP requests from cross origin or cross domains, then replace WithOrigins("*")with AllowAnyOrigin()

From

services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", builder => builder.WithOrigins("*")

To

services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin()

If you are using Swagger for API documentation, then change the below bold text:

From

{
    c.SwaggerDoc("v1", new Info {
        Title = "Your API Title",
        Version = "API Version Number",
        Description = "Description of your API"
    });
    c.AddSecurityDefinition("Bearer", new ApiKeyScheme {
        In = "header",
        Description = "Please enter JWT with Bearer into field",
        Name = "Authorization",
        Type = "apiKey"
    });
    c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> {
        {
            "Bearer",
            Enumerable.Empty<string>()
        },
    });
    c.CustomSchemaIds(i => i.FullName);
    c.OperationFilter<SwaggerParametersFilter>();
}

To

services.AddSwaggerGen(c => {
    c.SwaggerDoc("v1", new OpenApiInfo {
        Title = " Your API Title ",
        Version = "API Version Number",
        Description = "Description of your API "
    });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme {
        Description = "Please enter JWT with Bearer into field”,
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
        {
            new OpenApiSecurityScheme {
                Reference = new OpenApiReference {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    },
                Scheme = "oauth2",
                Name = "Bearer",
                In = ParameterLocation.Header,
            },
            new List<string>()
        }
    });
    c.CustomSchemaIds(i => i.FullName);
    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

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)

To

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider services)

Use your CORS Policy name define in #4.d, instead of defining rules here.

From

app.UseCors(builder => builder.WithOrigins("*")

To

app.UseCors("CorsPolicy");

Here CorsPolicy is the name of Cors policy.

Replace app.UseMvc() with EndPoint Routing, as below.

From

app.UseMvc();

To

app.UseRouting();
app.UseEndpoints(endpoints => {
    endpoints.MapDefaultControllerRoute();
    endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

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 that 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

public class SwaggerParametersFilter : IOperationFilter {
    public void Apply(Operation operation, OperationFilterContext context) {
        if (operation.Parameters == null) operation.Parameters = new List<IParameter>();
        operation.Parameters.Add(new NonBodyParameter {
            Name = "HeaderName",
            In = "header",
            Type = "string",
            Required = true
        });
    }
}

To

public class SwaggerParametersFilter : IOperationFilter {
    public void Apply(OpenApiOperation operation, OperationFilterContext context) {
        if (operation.Parameters == null) operation.Parameters = new List<OpenApiParameter>();
        operation.Parameters.Add(new OpenApiParameter {
            Name = " HeaderName ",
            In = ParameterLocation.Header,
            Required = true
        });
    }
}

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

return new WebHostBuilder().UseKestrel(opt => {
    int Httpsport = serviceContext.CodePackageActivationContext.GetEndpoint("1HttpsEndpoint").Port;
    opt.Listen(IPAddress.IPv6Any, Httpsport, listenOptions => {
        listenOptions.UseHttps(GetCertificateFromStore());
        listenOptions.NoDelay = true;
    });
});

To (Remove the bold blue text and add configuration of "UseLibuvas below)

return new WebHostBuilder().UseKestrel(opt => {
    int Httpsport = serviceContext.CodePackageActivationContext.GetEndpoint("1HttpsEndpoint").Port;
    opt.Listen(IPAddress.IPv6Any, Httpsport, listenOptions => {
        listenOptions.UseHttps(GetCertificateFromStore());
    });
}).UseLibuv(opts => {
    opts.NoDelay = true;
});

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 that 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!