Upgrading an Existing Web API Project to ASP.NET Core 2.1 Preview 1

In late February 2018, .NET Core 2.1 Preview 1, ASP.NET Core 2.1 Preview 1, and Entity Framework 2.1 Preview 1 were announced and released to the public. According to the documentation, there are several themes influencing the feature set of the final release. Here are the themes:

· Faster Build Performance

· Close gaps in ASP.NET core and EF Core

· Improve compatibility with .NET Framework

· GDPR and Security

· Microservices and Azure

· More capable Engineering System

All of the themes listed are important to the evolution, maturity, and adoption of the platform. Starting a new project with .NET/ASP.NET Core 2.1 Preview is a simple process and follows the familiar workflow of creating a new project in Visual Studio. Upgrading an existing .NET/ASP.NET Core 1.x/2.0 to .NET/ASP.NET Core 2.1 Preview 1 is a little more involved and this walkthrough is aimed at assisting you with the process by providing a breakdown of my experience.

When I present on the topic of web development, I typically use a reference application I created that is used to demonstrate technologies, tools, and software engineering techniques to the attendees. As these topics evolve, so does the reference application. In preparation for the upcoming release of .NET/ASP.NET Core 2.1, I spent sometime recently upgrading a Web API project that is a part of the reference application.

The first step in upgrading the Web API project was to download and install the NET Core 2.1 Preview 1 SDK. You can download it here: .NET Core 2.1 Preview 1 SDK . It is a simple and painless install but there are two things to note:

  • The SDK installs side-by-side with other versions of the SDK but your default SDK will be the latest version which is the preview.  If you have problems with other projects and the preview SDK, you can force a project to use a specific version of the SDK using a global.json file as documented here.
  • The 64-bit installer version is 128MB. Not a huge download but make sure you have a decent connection to the internet.

Once I had the .NET Core 2.1 Preview 1 SDK installed, I began the process of updating the Web API project. This process is manual and involved editing several files to get everything updated. The first file I edited was the .csproj file. I changed the TargetFramework element to reference .NET Core 2.1:

Original element: <TargetFramework>netcoreapp2.0</TargetFramework>

Updated element: <TargetFramework>netcoreapp2.1</TargetFramework>

Next, I updated all Microsoft.AspNetCore and Microsoft.Extensions package reference elements to version “2.1.0-preview1-final”. Below is an example of one of the updated package references elements:

Original element: <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />

Updated element: <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.0-preview1-final" />

Since I was modifying the .csproj file, I thought it was a great opportunity to perform some cleanup. (This step is not required.) This Web API has existed since the .NET Core 1.0 RC days and at that time the practice was to list each of the needed packages from Microsoft.AspNetCore and Microsoft.Extensions individually. The result in this project; a .csproj file with 15+ package reference elements. Now, there is nothing technically wrong with this but later versions of ASP.NET Core offer a way to reduce the number of package reference elements in your .csproj file.

ASP.NET Core 2.0 introduced the metapackage Microsoft.AspNetCore.All. This package includes all packages supported by the ASP.NET Core Team, all Entity Framework Core packages, and any third-party package dependencies of ASP.NET Core and Entity Framework Core. All features of ASP.NET Core and Entity Framework Core are included. In addition, the default project template used this package. This approach takes advantage of the .NET Runtime Store which contains all the runtime assets needed to run ASP.NET Core 2.x applications. Since all the assets are part of the .NET Runtime Store and the assets are precompiled, no ASP.NET Core nuget packages are deployed with the application (Except in the case of self-contained deployments) and application startup time is reduced. If there is an overall concern of the deployment size, a package trimming process can be used to remove any packages that are not used and therefore not deployed. More information on package trimming can be located here: Package Trimming

ASP.NET Core 2.1 introduces a new metapackage Microsoft.AspNetCore.App. The concept is the same as in Microsoft.AspNetCore.All except the new metapackage reduces the number of dependencies on packages not own/supported by the ASP.NET and .NET teams. Only the packages deemed necessary to ensure major framework features are included. More information on the new metapackage Microsoft.AspNetCore.App can be located here: Microsoft.AspNetCore.App

To reduce the number of package reference elements in the .csproj file, I removed all package reference elements for Microsoft.AspNetCore.* and Microsoft.Extensions.* and added a single package reference element for the new metapackage. Here is the new entry:

<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-preview1-final" />

Here is the final .csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <PreserveCompilationContext>true</PreserveCompilationContext>
    <AssemblyName>sregister_webapi</AssemblyName>
    <OutputType>Exe</OutputType>
    <PackageId>sregister_webapi</PackageId>    
    <PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
    <TrimUnusedDependencies>true</TrimUnusedDependencies>
  </PropertyGroup>

  <ItemGroup>
    <None Update="wwwroot\**\*">
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </None>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-preview1-final" />    
    <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.4.0" />
    <PackageReference Include="Dapper" Version="1.50.2" />
    <PackageReference Include="Microsoft.Packaging.Tools.Trimming" Version="1.1.0-preview1-25818-01" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\sregister_core\sregister_core.csproj" />
    <ProjectReference Include="..\sregister_infrastructure\sregister_infrastructure.csproj" />
  </ItemGroup>

</Project>

The next set of changes were made in the program.cs file. Here is the before and after:

BEFORE:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;

namespace sregister_webapi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

AFTER:

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace sregister_webapi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }
               
        private static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

As I stated earlier, this Web API project was originally created in the .NET/ASP.NET Core 1.0 RC days and things are done differently now. As you can see, the recommended approach is to have main call a method that returns IWebHostBuilder which is configured to know which class to use during startup. Main calls the Build and Run methods (in that order) on the returned IWebHostBuilder which starts the application.

The last step in upgrading the Web API to .NET/ASP.NET Core 2.1 Preview was to modify the Startup.cs file. The main changes were:

In the ConfigurationServices method:

· Changed the call to add MVC services to services.AddMvcCore().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)

In the Configure method:

· Added app.UseHsts() – More information on this can be found here: HSTS

· Added app.UseHttpsRedirection() – This redirects HTTP traffic to HTTPS

Both changes in the Configure method were made to take advantage of the features of ASP.NET Core 2.1 Preview to secure web applications using HTTPS during development and production. A detailed explanation of the features can be found here: ASP.NET Core 2.1.0-preview1: Improvements for using HTTPS

Here is the before and after of Startup.cs:

BEFORE

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using sregister_core.Interfaces;
using sregister_infrastructure.Repositorities;
using sregister_core.Models;

namespace sregister_webapi
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            services.AddCors();
            services.AddOptions();
            services.Configure<SpeakerRegisterOptions>(Configuration.GetSection("Options"));

            // adding custom services
            services.AddTransient<ISpeakerRepository, SpeakerRepository>();
            services.AddTransient<IConferenceRepository, ConferenceRepository>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

            app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
            {
                Authority = "http://localhost:9440",
                AllowedScopes = { "sregisterAPI" },
                RequireHttpsMetadata = false
            });
            
            app.UseMvc();
        }
    }
}

AFTER

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using sregister_core.Interfaces;
using sregister_infrastructure.Repositorities;
using sregister_core.Models;
using System;

namespace sregister_webapi
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvcCore()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddAuthorization()
                .AddJsonFormatters()
                .AddCors();

            services.AddHsts(options => {
                options.MaxAge = TimeSpan.FromDays(90);
                options.IncludeSubDomains = false;
                options.Preload = false;
            });

            services.AddAuthentication("Bearer").AddIdentityServerAuthentication(options => {
                options.Authority = "http://localhost:9440";
                options.RequireHttpsMetadata = false;
                options.ApiName = "sregisterAPI";
                options.LegacyAudienceValidation = true;    //temporary until token service is updated
            });

            services.AddOptions();
            services.Configure<SpeakerRegisterOptions>(Configuration.GetSection("Options"));
            
            // adding custom services
            services.AddTransient<ISpeakerRepository, SpeakerRepository>();
            services.AddTransient<IConferenceRepository, ConferenceRepository>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());                   

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

            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseMvc();
        }
    }
}

 

Finally, I was able to build and run the upgraded Web API that is now using .NET/ASP.NET Core 2.1 Preview 1. I haven’t explored any of the new features of .NET/ASP.NET Core 2.1 Preview 1 (except for the HTTPS enhancements) but I’m looking forward to leveraging what the new platform has to offer.

If you want to explore more about ASP.NET Core 2.1 Preview 1, here is information provided from the ASP.NET Team regarding the announcement of the release, new features, and upgrade steps. ASP.NET Core 2.1 Preview 1 Now Available

Keep Right’in Code!

Richard