# 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 mechanical 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>(); }); } ``` **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.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 the `WebApplicationBuilder` 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](#changing-the-content-root-application-name-and-environment) 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 the `IStartupFilter` call chain. - Some tools (like EF migrations) use `Program.CreateHostBuilder` to access 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` (see the [Cheatsheet](#changing-the-content-root-application-name-and-environment) for instructions on how to do this). The following APIs will throw an exception: **WebHost** ```csharp 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** ```csharp builder.Host.UseEnvironment(Environments.Staging); builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); ``` - 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<Startup>(); }); ``` OR ```csharp var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStartup<Startup>(); ``` - The `IHostBuilder` implementation on `WebApplicationBuilder` (`WebApplicationBuilder.Host`), does not defer execution of `ConfigureServices`, `ConfigureAppConfiguration` or `ConfigureHostConfiguration` methods. This allows code using `WebApplicationBuilder` to observe changes made to the `IServiceCollection` and `IConfiguration`. The below example will only add `Service1` as an `IService`. ```csharp 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. ## 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/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: 1. You can store the state on another class. Assuming this was static state that you were accessing from anywhere in the application. 2. 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** ```C# 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** ```C# 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](#testing-with-webapplicationfactorytestserver) for an example. ### What if I was using a custom dependency injection container? This is still supported, see the [Cheatsheet](#custom-dependency-injection-container) 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** ```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<ContainerBuilder>(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` class. ## 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<Startup>() .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<Startup>(); }); ``` **.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<Startup>(); }); ``` **.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<ITodoRepository, TodoRepository>(); } } ``` **.NET 6** ```csharp 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** ```csharp 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** ```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<Startup>(); }); ``` **.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<Startup>(); }); ``` **.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<Startup>(); }); ``` ```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<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`. ```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<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`. ```csharp 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** ```xml <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="{Version}" /> </ItemGroup> ``` **TestServer** ```xml <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="{Version}" /> </ItemGroup> ``` 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<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** ```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<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** ```csharp [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** ```csharp 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** ```xml <ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup> ``` OR ```C# [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** ```csharp var builder = WebApplication.CreateBuilder(args); // ... Wire up services and routes etc app.Run(); public partial class Program { } ``` ```csharp [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.