# Migration to ASP.NET Core in .NET 6 - [WebApplication and WebApplicationBuilder](#webapplication-and-webapplicationbuilder) - [Differences in the hosting model](#differences-in-the-hosting-model) - [Building libraries for ASP.NET Core](#building-libraries-for-aspnet-core) - [FAQ](#faq) - [Cheatsheet](#cheatsheet) ## 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. ```C# 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)** ```C# 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.cs (.NET 5)** ```C# 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)** ```C# 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.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 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. - Some tools (like EF migrations) use `Program.CreateHostBuilder` to grab the application's `IServiceProvider` 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`. - It is not possible to use the `Startup` class via the `WebApplicationBuilder.Host` or `WebApplicationBuilder.WebHost`. The following will throw an exception: ```csharp var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureWebHostDefaults(webHostBuilder => { webHostBuilder.UseStartup(); }); ``` OR ```csharp var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStartup(); ``` ## 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` 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 ```C# 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(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** ```C# 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(startup.ConfigureContainer); var app = builder.Build(); startup.Configure(app, app.Environment); app.Run(); ``` **Startup.cs** ```C# 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 your `Program.cs`. ## Cheatsheet ### Adding middleware **.NET 5** ```csharp public class Startup { public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); } } ``` **.NET 6** ```csharp var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseStaticFiles(); app.Run(); ``` ### Adding routes **.NET 5** ```csharp 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`. ```csharp 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** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseContentRoot(Directory.GetCurrentDirectory()) .UseEnvironment(Environments.Staging) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup() .UseSetting(WebHostDefaults.ApplicationKey, typeof(Program).Assembly.FullName); }); ``` **.NET 6** ```csharp 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** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration(config => { config.AddIniFile("appsettings.ini"); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); ``` **.NET 6** ```csharp var builder = WebApplication.CreateBuilder(args); builder.Configuration.AddIniFile("appsettings.ini"); var app = builder.Build(); ``` ### Adding logging providers **.NET 5** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => { logging.AddJsonConsole(); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); ``` **.NET 6** ```csharp var builder = WebApplication.CreateBuilder(args); // Configure JSON logging to the console builder.Logging.AddJsonConsole(); var app = builder.Build(); ``` ### Adding services **.NET 5** ```csharp public class Startup { public void ConfigureServices(IServiceCollection services) { // Add the memory cache services services.AddMemoryCache(); // Add a custom scoped service services.AddScoped(); } } ``` **.NET 6** ```csharp var builder = WebApplication.CreateBuilder(args); // Add the memory cache services builder.Services.AddMemoryCache(); // Add a custom scoped service builder.Services.AddScoped(); var app = builder.Build(); ``` ### Customizing the IHostBuilder **.NET 5** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30)); .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); ``` Existing extension methods on `IHostBuilder` can be accessed using the `Host` property. **.NET 6** ```csharp 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** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { // Change the HTTP server implemenation to be HTTP.sys based webBuilder.UseHttpSys() .UseStartup(); }); ``` **.NET 6** Existing extension methods on `IWebHostBuilder` can be accessed using the `WebHost` property. ```csharp 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** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { // Look for static files in webroot webBuilder.UseWebRoot("webroot") .UseStartup(); }); ``` **.NET 6** ```csharp 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](https://autofac.readthedocs.io/en/latest/integration/aspnetcore.html) **.NET 5** ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); ``` ```csharp public class Startup { public void ConfigureContainer(ContainerBuilder containerBuilder) { } } ``` **.NET 6** ```csharp 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(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`. ```csharp 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(); } // Anything added to the service collection can be injected into Configure public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime, IService service, ILogger 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`. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton(); var app = builder.Build(); var service = app.Services.GetRequiredService(); var logger = app.Logger; var lifetime = app.Lifetime; var env = app.Environment; lifetime.ApplicationStarted.Register(() => logger.LogInformation($"The application {env.ApplicationName} started in we injected {service}")); app.Run(); ``` ### Testing with TestServer This sample is using xUnit and `IHelloService` will be shared between both examples: ```csharp public interface IHelloService { string HelloMessage { get; } } public class HelloService : IHelloService { public string HelloMessage => "Hello World"; } ``` **.NET 5** ```csharp public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); } 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); }); }); } } ``` ```csharp [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(); }) .ConfigureServices(services => { // Replace the service services.AddSingleton(); }) .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"; } ``` **.NET 6** ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton(); var app = builder.Build(); var helloService = app.Services.GetRequiredService(); app.MapGet("/", async context => { await context.Response.WriteAsync(helloService.HelloMessage); }); app.Run(); ``` In .NET 6, `WebApplicationFactory` is used to test application using new hosting model. Top level programs expose an `internal` `Program` class and we need to expose this to the test project by using `InternalsVisibleTo`. This can be done using the project file or in any other .cs file: **MyProject.csproj** ```xml ``` OR ```C# [assembly: InternalsVisibleTo("MyTestProject")] ``` The other technique for exposing the `Program` class is by making it 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** ```csharp var builder = WebApplication.CreateBuilder(args); // ... Wire up services and routes etc app.Run(); // This will expose the Program class public partial class Program { } ``` ```csharp [Fact] public async Task HelloWorld() { var application = new WebApplicationFactory() .WithWebHostBuilder(builder => { builder.ConfigureServices(services => { services.AddSingleton(); }); }); var client = application.CreateClient(); var response = await client.GetStringAsync("/"); Assert.Equal("Test Hello", response); } class MockHelloService : IHelloService { public string HelloMessage => "Test Hello"; } ```