

.NET 6 ASP.NET Core Migration
source link: https://gist.github.com/davidfowl/0e0372c3c1d895c3ce195ba983b1e03d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 mechanical 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.MapRazorPages(); } }
Program.cs (.NET 6)
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { 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.MapRazorPages(); app.Run();
The above shows that ConfigureServices(IServiceCollection)
can be configured using WebApplicationBuilder.Services
and Configure(IApplicationBuilder...)
can be configured by using WebApplication
.
Differences in the hosting model
-
The developer exception page middleware is enabled when the environment is
Development
. -
The application name always defaults to the entry point assembly's name
Assembly.GetEntryAssembly().GetName().FullName
. When using theWebApplicationBuilder
in a library, you will need to explicitly change the application name to the library's assembly to allow MVC's application part discovery to work (finding controllers, views etc) (see the Cheatsheet for instructions on how to do this). -
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 theIStartupFilter
call chain. -
Some tools (like EF migrations) use
Program.CreateHostBuilder
to access the application'sIServiceProvider
to execute custom logic in the context of the application, these tools have been updated to use a new technique to achieve the same thing. We will work with the ecosystem to make sure tools are all updated to use the new model. -
It is not possible to change any host settings (application name, environment or the content root) after the creation of the
WebApplicationBuilder
(see the Cheatsheet for instructions on how to do this). The following APIs will throw an exception:WebHost
builder.WebHost.UseContentRoot(Directory.GetCurrentDirectory()); builder.WebHost.UseEnvironment(Environments.Staging); builder.WebHost.UseSetting(WebHostDefaults.ApplicationKey, "ApplicationName2"); builder.WebHost.UseSetting(WebHostDefaults.ContentRootKey, Directory.GetCurrentDirectory()); builder.WebHost.UseSetting(WebHostDefaults.EnvironmentKey, Environments.Staging);
Host
builder.Host.UseEnvironment(Environments.Staging); builder.Host.UseContentRoot(Directory.GetCurrentDirectory());
-
It is not possible to use the
Startup
class via theWebApplicationBuilder.Host
orWebApplicationBuilder.WebHost
. The following will throw an exception:var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureWebHostDefaults(webHostBuilder => { webHostBuilder.UseStartup<Startup>(); });
var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStartup<Startup>();
-
The
IHostBuilder
implementation onWebApplicationBuilder
(WebApplicationBuilder.Host
), does not defer execution ofConfigureServices
,ConfigureAppConfiguration
orConfigureHostConfiguration
methods. This allows code usingWebApplicationBuilder
to observe changes made to theIServiceCollection
andIConfiguration
. The below example will only addService1
as anIService
.using Microsoft.Extensions.DependencyInjection.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureServices(services => { services.TryAddSingleton<IService, Service1>(); }); builder.Services.TryAddSingleton<IService, Service2>(); var app = builder.Build(); // This will print Service1 Console.WriteLine(app.Services.GetRequiredService<IService>()); app.Run(); class Service1 : IService { } class Service2 : IService { } interface IService { }
Building libraries for ASP.NET Core
The existing .NET ecosystem has built extensibility around IServiceCollection
, IHostBuilder
and IWebHostBuilder
. These properties are available 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.
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/deprecated?
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. This means you can upgrade from .NET 5 to .NET 6.0 by changing the target framework in your project file from net5.0
to net6.0
.
Do I have to use top-level statements?
The new project templates all use top-level statements, but these new hosting APIs can be used in any .NET 6 application to host a webserver/web application.
Where do I put state that was stored as fields in my Program/Startup class?
There are 2 solutions to this problem:
- You can store the state on another class. Assuming this was static state that you were accessing from anywhere in the application.
- There's a
Program
class generated by top level statements that you can put this state on if you wish to keep that semantic.
This is an example of #2:
.NET 5
public class Startup { public static string ConfigurationValue { get; private set; } public Startup(IConfiguration configuration) { Configuration = configuration; ConfigurationValue = Configuration["SomeValue"]; } public IConfiguration Configuration { get; } // More configuration here }
.NET 6
var builder = WebApplication.CreateBuilder(args); ConfigurationValue = builder.Configuration["SomeValue"]; var app = builder.Build(); app.Run(); partial class Program { public static string ConfigurationValue { get; private set; } }
This would make it possible to use Program.ConfigurationValue
in your .NET 6 application.
NOTE: We recommend using dependency injection to flow state in your ASP.NET Core applications.
Does WebApplicationFactory/TestServer still work?
WebApplicationFactory<TEntryPoint>
is the way to test the new hosting model. See the Cheatsheet for an example.
What if I was using a custom dependency injection container?
This is still supported, see the Cheatsheet for an example.
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 with the new hosting model:
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:
- You control the instantiation and lifetime of the
Startup
class. - Any additional services injected into the
Configure
method need to be manually resolved by yourProgram
class.
Cheatsheet
Adding middleware
.NET 5
public class Startup { public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); } }
.NET 6
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseStaticFiles(); app.Run();
Adding routes
.NET 5
public class Startup { public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", () => "Hello World"); }); } }
.NET 6
In .NET 6, routes can be added directly to the WebApplication
without an explicit call to UseEndpoints
.
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World"); app.Run();
NOTE: Routes added directly to the WebApplication
will execute at the end of the pipeline.
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 services.AddMemoryCache(); // Add a custom scoped service 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();
Accessing additional services
.NET 5
In Startup.Configure
you can inject any service added via the IServiceCollection
.
public class Startup { // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IService, Service>(); } // Anything added to the service collection can be injected into Configure public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime, IService service, ILogger<Startup> logger) { lifetime.ApplicationStarted.Register(() => logger.LogInformation($"The application {env.ApplicationName} started in we injected {service}")); } }
.NET 6
In .NET 6, there are a few common services available as top level properties on WebApplication
and additional services
need to be manually resolved from the IServiceProvider
via WebApplication.Services
.
var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<IService, Service>(); var app = builder.Build(); IService service = app.Services.GetRequiredService<IService>(); ILogger logger = app.Logger; IHostApplicationLifetime lifetime = app.Lifetime; IWebHostEnvironment env = app.Environment; lifetime.ApplicationStarted.Register(() => logger.LogInformation($"The application {env.ApplicationName} started in we injected {service}")); app.Run();
Testing with WebApplicationFactory/TestServer
In the below samples, the test project uses TestServer
and WebApplicationFactory
. These ship as separate packages that need to explicit referenced:
WebApplicationFactory
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="{Version}" /> </ItemGroup>
TestServer
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="{Version}" /> </ItemGroup>
This sample is using xUnit and IHelloService
will be shared between both examples:
public interface IHelloService { string HelloMessage { get; } } public class HelloService : IHelloService { public string HelloMessage => "Hello World"; }
.NET 5
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IHelloService, HelloService>(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHelloService helloService) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync(helloService.HelloMessage); }); }); } }
With TestServer
[Fact] public async Task HelloWorld() { using var host = Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(builder => { // Use the test server and point to the application's startup builder.UseTestServer() .UseStartup<WebApplication1.Startup>(); }) .ConfigureServices(services => { // Replace the service services.AddSingleton<IHelloService, MockHelloService>(); }) .Build(); await host.StartAsync(); var client = host.GetTestClient(); var response = await client.GetStringAsync("/"); Assert.Equal("Test Hello", response); } class MockHelloService : IHelloService { public string HelloMessage => "Test Hello"; }
With WebApplicationFactory
[Fact] public async Task HelloWorld() { var application = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureServices(services => { services.AddSingleton<IHelloService, MockHelloService>(); }); }); var client = application.CreateClient(); var response = await client.GetStringAsync("/"); Assert.Equal("Test Hello", response); } class MockHelloService : IHelloService { public string HelloMessage => "Test Hello"; }
.NET 6
var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<IHelloService, HelloService>(); var app = builder.Build(); var helloService = app.Services.GetRequiredService<IHelloService>(); app.MapGet("/", async context => { await context.Response.WriteAsync(helloService.HelloMessage); }); app.Run();
In .NET 6, WebApplicationFactory<TEntryPoint>
is used to test application using new hosting model. The compiler produces an internal Program
class applications that use top level statements. We need to make this available to the test project by using InternalsVisibleTo
. This can be done using the project file or in any other .cs file:
MyProject.csproj
<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>
[assembly: InternalsVisibleTo("MyTestProject")]
The other solution is to make the Program
class public. You can do this with top level statements by defining a public partial Program
class anywhere in the project (or in Program.cs
):
Program.cs
var builder = WebApplication.CreateBuilder(args); // ... Wire up services and routes etc app.Run(); public partial class Program { }
[Fact] public async Task HelloWorld() { var application = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureServices(services => { services.AddSingleton<IHelloService, MockHelloService>(); }); }); var client = application.CreateClient(); var response = await client.GetStringAsync("/"); Assert.Equal("Test Hello", response); } class MockHelloService : IHelloService { public string HelloMessage => "Test Hello"; }
The .NET 5 version and .NET 6 version with the WebApplicationFactory are identical. This is by design.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK