Skip to content

Instantly share code, notes, and snippets.

@javafun
Forked from davidfowl/.NET6Migration.md
Created April 11, 2024 02:02
Show Gist options
  • Select an option

  • Save javafun/56e9f0ae91ae40906dc4111f61eee940 to your computer and use it in GitHub Desktop.

Select an option

Save javafun/56e9f0ae91ae40906dc4111f61eee940 to your computer and use it in GitHub Desktop.
.NET 6 ASP.NET Core Migration

Migration to ASP.NET Core in .NET 6

WebApplication and WebApplicationBuilder

.NET 6 introduces a new hosting model for ASP.NET Core applications. This model is streamlined and reduces the amount of boilerplate code required to get a basic ASP.NET Core application up and running.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World");

app.Run();

This model unifies Startup.cs and Program.cs into a single file experience that takes advantage of top level statements to remove any boilerplate. There should be a mostly mechanial translation from .NET 5 projects using a Startup class to the new hosting model:

Program.cs (.NET 5)

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>();
            });
}

Startup.cs (.NET 5)

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

Program.cs (.NET 6)

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
});

app.Run();

The above shows that ConfigureServices(IServiceCollection) can be configured using WebApplicationBuilder.Services and Configure(IApplicationBuilder...) can be configured by using WebApplication.

Behavior differences in the hosting model

  • The developer exception page middleware is enabled when the environment is Development.
  • The endpoint routing middleware wraps the entire middleware pipeline. This means there's no need to have explicit calls to UseEndpoints to register routes. UseRouting can still be used to move where route matching happens.
  • The final pipeline is created before any IStartupFilter runs. This means that exceptions caused while building the main pipeline won't be visible to the IStartupFilter call chain.

Building libraries for ASP.NET Core

The existing .NET ecosystem has built extensibility around IServiceCollection, IHostBuilder and IWebHostBuilder. These properties are exposed on the WebApplicationBuilder as Services, Host and WebHost.

The WebApplication implements both Microsoft.AspNetCore.Builder.IApplicationBuilder and Microsoft.AspNetCore.Routing.IEndpointRouteBuilder.

We expect library authors to continue targeting IHostBuilder, IWebHostBuilder, IApplicationBuilder and IEndpointRouteBuilder when building ASP.NET Core specific components. This will ensure that your middleware, route handler, or other extensibility points continue to work across different hosting models.

FAQ

Is the new hosting model less capable

No, it should be functionally equivalent for 98% to what you can do with the IHostBuilder and the IWebHostBuilder. There are more advanced scenarios (the 2%) that will require specific knobs on IHostBuilder but we expect those to be extremely rare.

Is the generic hosting model dead?

No, it's not. It's an alternative model that will keep working forever. The generic host still underpins the new hosting model and is still the primary way to host worker based applications.

Do I have to migrate to the new hosting model

No, you don't have to. It's the preferred way to host ASP.NET Core applications from .NET 6 and onwards but you aren't forced to change your project layout if you have built up features around the Startup class.

Does WebApplicationFactory/TestServer still work?

WebApplicationFactory<TEntryPoint> is the way to test the new hosting model with the test server.

What if I was using a custom DI container?

That still works, here's an example that uses Autofac

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

I like the Startup class, can I keep it?

Yes you can. Here's a shim you can use to keep it working as is:

Program.cs

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

// Uncomment if using a custom DI container
// builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// builder.Host.ConfigureContainer<ContainerBuilder>(startup.ConfigureContainer);

var app = builder.Build();

startup.Configure(app, app.Environment);

app.Run();

Startup.cs

class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

    }

//  Uncomment if using a custom DI container
//  public void ConfigureContainer(ContainerBuilder builder)
//  {
//  }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
    {

    }
}

There are a few differences here:

  • No more reflection magic to call the Startup class. You control the instantiation and lifetime.
  • Any additional services injected into the Configure method need to be manually resolved by your Program.cs.

Cheatsheet

Changing the content root, application name and environment

.NET 5

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseEnvironment(Environments.Staging)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>()
                      .UseSetting(WebHostDefaults.ApplicationKey, typeof(Program).Assembly.FullName);
        });

.NET 6

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");

var app = builder.Build();

Adding configuration providers

.NET 5

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(config =>
        {
            config.AddIniFile("appsettings.ini");
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

.NET 6

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Adding logging providers

.NET 5

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

.NET 6

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console
builder.Logging.AddJsonConsole();

var app = builder.Build();

Adding services

.NET 5

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Add the memory cache services
        builder.Services.AddMemoryCache();

        // Add a custom scoped service
        builder.Services.AddScoped<ITodoRepository, TodoRepository>();
    }
}

.NET 6

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services
builder.Services.AddMemoryCache();

// Add a custom scoped service
builder.Services.AddScoped<ITodoRepository, TodoRepository>();

var app = builder.Build();

Customizing the IHostBuilder

.NET 5

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

Existing extension methods on IHostBuilder can be accessed using the Host property.

.NET 6

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

Customizing the IWebHostBuilder

.NET 5

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            // Change the HTTP server implemenation to be HTTP.sys based
            webBuilder.UseHttpSys()
                      .UseStartup<Startup>();
        });

.NET 6

Existing extension methods on IWebHostBuilder can be accessed using the WebHost property.

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

Changing the web root

By default, the web root is relative to the content root in the wwwroot folder. This is where the static files middleware expects to find static files. You can change this by using the UseWebRoot method on the WebHost property:

.NET 5

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            // Look for static files in webroot
            webBuilder.UseWebRoot("webroot")
                      .UseStartup<Startup>();
        });

.NET 6

var builder = WebApplication.CreateBuilder(args);

// Look for static files in webroot
builder.WebHost.UseWebRoot("webroot");

var app = builder.Build();

Custom dependency injection container

This example uses Autofac

.NET 5

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseServiceProviderFactory(new AutofacServiceProviderFactory())
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
public class Startup
{
    public void ConfigureContainer(ContainerBuilder containerBuilder)
    {
    }
}

.NET 6

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment