- WebApplication and WebApplicationBuilder
- Differences in the hosting model
- Building libraries for ASP.NET Core
- FAQ
- Cheatsheet
.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
.
-
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>(); });
OR
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 { }
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.
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.
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.
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
.
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.
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.
WebApplicationFactory<TEntryPoint>
is the way to test the new hosting model. See the Cheatsheet for an example.
This is still supported, see the Cheatsheet for an example.
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.
.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();
.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.
.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();
.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();
.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();
.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();
.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();
.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();
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();
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();
.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();
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>
OR
[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.
@odesuk Thanks your suggestion. I'm use this way.
builder.Services.AddControllers().AddJsonOptions(x =>
{
x.JsonSerializerOptions.PropertyNamingPolicy = null;
});