blog post image
Andrew Lock avatar

Andrew Lock

~10 min read

Exploring the new project file, Program.cs, and the generic host

Exploring ASP.NET Core 3.0 - Part 1

In this post I take a look at some of the fundamental pieces of ASP.NET Core 3.0 applications - the .csproj project file and the Program.cs file. I'll describe how they've changed in the default templates from ASP.NET Core 2.x, and discuss some of the changes in the the APIs they use.

Introduction

.NET Core 3.0 is due for release during .NET Conf on September 23rd, but there is a supported preview version (Preview 8) available now. There's unlikely to be many changes between now and the full release, so it's a great time to start poking around and seeing what it will bring.

.NET Core 3.0 is primarily focused on allowing Windows desktop applications to run on .NET Core, but there's lots of things coming to ASP.NET Core too. Probably the biggest new feature is server-side Blazor (I'm more interested in the client-side version personally, which isn't fully available yet), but there are lots of incremental changes and features included too.

In this post, I'm looking at a couple of the more "infrastructural" changes:

If you're upgrading an ASP.NET Core 2.x app to 3.0, be sure to check out the migration guidance on docs.microsoft.com.

In this post, I'm looking at the .csproj file and Program.cs when you create a new ASP.NET Core application, for example when you run dotnet new webapi. In a later post I'll compare how the Startup.cs files have changed from 2.x, and with the various different ASP.NET Core templates available (web, webapi, mvc etc).

The new project file and changes to the shared framework

After creating a new ASP.NET Core project file, open up the .csproj file, and it will look something like this:

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

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

</Project>

If you compare this to the project file for an ASP.NET Core app in 2.x, there are various similarities and differences:

  • The <TargetFramework> is netcoreapp3.0, instead of netcoreapp2.1 or 2.2. This is because we're targeting .NET Core 3.0, instead of 2.1/2.2.
  • The SDK in the <Project> element is still Microsoft.Net.Sdk.Web. This has been updated for ASP.NET Core 3.0, but the syntax in your project file is the same.
  • There is no longer a reference to the Microsoft.AspNetCore.App meta package.

This last point is the most interesting change. In ASP.NET Core 2.1/2.2, you could reference the "shared framework" metapackage, Microsoft.AspNetCore.App, as I described in a previous post. The shared framework provides a number of benefits, such as allowing you to avoid having to manually install all the individual packages in your app, and allowing roll-forward of runtimes.

With ASP.NET Core 3.0, Microsoft are no longer publishing the shared framework as a NuGet metapackage. There is no Microsoft.AspNetCore.App version 3.0.0. The shared framework is still installed with .NET Core as before, but you reference it differently in 3.0.

In ASP.NET Core 2.x, to reference the shared framework, you would add the following to your project file:

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

Instead, in 3.0, you use the <FrameworkReference> element:

<ItemGroup>
  <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

"But wait" you say, "why doesn't my ASP.NET Core project file have this?"

That's a good question. The answer is that the SDK Microsoft.Net.Sdk.Web includes it by default!

No more packages for shared framework components

Another big change in 3.0 is that you can no longer install individual NuGet packages that are otherwise part of the shared framework. For example, in ASP.NET Core 2.x, you could take a dependency on individual packages like Microsoft.AspNetCore.Authentication or Microsoft.AspNetCore.Identity, instead of depending on the whole framework:

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.1.0"/>
  <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.0"/>
</ItemGroup>

This was generally most useful for libraries, as apps would always depend on the shared framework by necessity. However, in .NET Core 3.0, this is no longer possible. Those NuGet packages aren't being produced any more. Instead, if you need to reference any of these libraries, from your class library you must add the <FrameworkReference> element to your project file.

Another thing to be aware of is that some packages, e.g. EF Core and the social authentication providers, are no longer part of the shared framework. If you need to use those packages, you'll have to manually install the NuGet package in your project.

For the full list of packages that apply, see this GitHub issue.

Changes in Program.cs from 2.x to 3.0

The Program.cs in ASP.NET Core 3.0 looks very similar to the version from .NET Core 2.x on first glance, but actually many of the types have changed. This is because in .NET Core 3.0, ASP.NET Core has been rebuilt to run on top of the Generic Host, instead of using a separate Web Host.

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

The Generic Host was introduced in 2.1, and was a nice idea, but I found various issues with it, primarily as it created more work for libraries. Thankfully this change in 3.0 should solve those issues.

For the most part, the end result is very similar to what you're used to in .NET Core 2.x, but it's broken into two logical steps. Instead of a single method WebHost.CreateDefaultBuilder(), that configures everything for your app, there are two separate method calls:

  • Host.CreateDefaultBuilder(). This configures the app configuration, logging, and dependency injection container.
  • IHostBuilder.ConfigureWebHostDefaults(). This adds everything else needed for a typical ASP.NET Core application, such as configuring Kestrel and using a Startup.cs to configure your DI container and middleware pipeline.

The generic host builder

As I've already mentioned, the generic host forms the foundation for building ASP.NET Core 3.0 applications. It provides the foundational Microsoft.Extensions.* elements you're used to in ASP.NET Core apps, such as logging, configuration, and dependency injection.

The code below is a simplified version of the Host.CreateDefaultBuilder() method. It's similar to the WebHost.CreateDefaultBuilder() method from 2.x, but there are a few interesting changes that I'll discuss shortly.

public static IHostBuilder CreateDefaultBuilder(string[] args)
{
    var builder = new HostBuilder();

    builder.UseContentRoot(Directory.GetCurrentDirectory());
    builder.ConfigureHostConfiguration(config =>
    {
        // Uses DOTNET_ environment variables and command line args
    });

    builder.ConfigureAppConfiguration((hostingContext, config) =>
    {
        // JSON files, User secrets, environment variables and command line arguments
    })
    .ConfigureLogging((hostingContext, logging) =>
    {
        // Adds loggers for console, debug, event source, and EventLog (Windows only)
    })
    .UseDefaultServiceProvider((context, options) =>
    {
        // Configures DI provider validation
    });

    return builder;
}

In summary, the differences between this method and the version in 2.x are:

  • Uses DOTNET_ prefix for environment variable hosting configuration
  • Uses command line variables for hosting configuration
  • Adds EventSourceLogger and EventLogLogger logger providers
  • Optionally enables ServiceProvider validation
  • Configures nothing specific to web hosting.

The first point of interest is how the host configuration is set up. With the web host, configuration used environment variables that are prefixed with ASPNETCORE_ by default. So setting the ASPNETCORE_ENVIRONMENT environment variable would set the Environment configuration value. For the generic host, this prefix is now DOTNET_, plus any command line arguments passed to the application at runtime.

The host configuration controls things like what Hosting Environment the application is running in, and is separate from your app configuration (which is often used with the IOptions interface).

The method to configure your app settings, ConfigureAppConfiguration() is unchanged from 2.x, so it still uses an appsettings.json file, an appsettings.ENV.json file, user secrets, environment variables, and command line arguments.

The logging section of the generic host has been expanded in 3.0. It still configures log-level filtering via your app configuration, and adds the Console and Debug logger providers. However it also adds the EventSource logging provider, which is used to interface with OS logging systems like ETW on Windows and LTTng on Linux. Additionally, on Windows only, the logger adds an Event Log provider, for writing to the Windows Event Log.

Finally, the generic host configures the dependency injection container so that it validates scopes when running in the development environment, as it did in 2.x. This aims to catch instances of captured dependencies, where you inject a Scoped service into a Singleton service. In 3.0 the generic host also enables ValidateOnBuild, which is a feature I'll be looking at in a subsequent post.

A key point of the generic host is that it's generic, that is, it has nothing specifically related to ASP.NET Core or HTTP workloads. You can use the generic host as as base for creating console apps or long lived services, as well as typical ASP.NET Core apps. To account for that, in 3.0 you have an additional method that adds the ASP.NET Core layer on top - ConfigureWebHostDefaults().

Reinstating ASP.NET Core features with ConfigureWebHostDefaults

This post is already getting pretty long, so I won't go into too much detail here, but the ConfigureWebHostDefaults extension method is used to add the ASP.NET Core "layer" on top of the generic host's features. At the simplest level, this involves adding the Kestrel web server to the host, but there are a number of other changes too. The following is an overview of what the method provides (including features provided by the GenericWebHostBuilder):

  • Adds ASPNETCORE_ prefixed environment variables to the host configuration (in addition to the DOTNET_ prefixed variables and command line arguments).
  • Adds the GenericWebHostService. This is an IHostedService implementation that actually runs the ASP.NET Core server. This is the main feature that made it possible to reuse the generic host with ASP.NET Core.
  • Adds an additional app configuration source, the StaticWebAssetsLoader for working with static files (css/js) in Razor UI class libraries.
  • Configures Kestrel using its defaults (same as 2.x)
  • Adds the HostFilteringStartupFilter (same as 2.x)
  • Adds the ForwardedHeadersStartupFilter, if the ForwardedHeaders_Enabled configuration value is true, i.e. if the ASPNETCORE_FORWARDEDHEADERS_ENABLED environment variable is true.
  • Enables IIS integration on Windows.
  • Adds the endpoint routing services to the DI container.

Much of this is the same as in ASP.NET Core 2.x, with the exception of: the infrastructure for running the app as an IHostedService; endpoint routing, which is enabled globally in 3.0 (rather than for MVC/Razor Pages only in 2.2); and the ForwardedHeadersStartupFilter.

The ForwardedHeadersMiddleware has been around since 1.0 and is required when hosting your application behind a proxy, to ensure your application handles SSL-offloading and generates URLs correctly. What's changed is that you can configure the middleware to use the X-Forwarded-For and X-Forwarded-Proto headers by just setting an environment variable.

Summary

In this post I dug into the changes from ASP.NET Core 2.x to 3.0 in just two files: the .csproj project file, and the `Program.cs file. On the face of it, there are only minimal changes to these files, so porting from 2.x to 3.0 should not be difficult. This simplicity belies the larger changes under the hood: there are significant changes to the shared framework, and ASP.NET Core has been rebuilt on top of the generic host.

The largest issue I expect people to run into is the differences in NuGet packages - some applications will have to remove references to ASP.NET Core packages, while adding explicit references to others. While not difficult to resolve, it could be confusing for users not familiar with the change, so should be the first suspect with any issues.

Andrew Lock | .Net Escapades
Want an email when
there's new posts?