TLDR; In an ASP.NET web application, the Program.cs file contains code that configures, runs and manages the lifetime of your app. In this post we explore what happens when you run a new ASP.NET Core web application.
When working on an existing ASP.NET application it might be daunting to know where to begin with understanding the codebase. With a new ASP.NET Core 3.1 or .NET 5.0 web application you're met with two files with boilerplate code the Startup.cs
and Program.cs
. What do they do?
The entry point
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>();
});
}
In .NET applications, it is convention to have a Program.cs
file with a public static method called Main
inside the Program
class1. Similar to command-line applications, the Program.Main
method is the entry point of our application, where the execution chain begins when the application starts running.
When you run the application using dotnet run
on the command line, the command looks in the current folder or project file, builds the project, launches a process and looks for the entry point to execute your code.
When the Main
method is executed, the program creates a host builder, builds the host and then runs the application. The host is responsible for the application's startup and lifetime management.
The host is also responsible for configuring the necessary pieces your web application needs to communicate with incoming HTTP requests and to respond in the appropriate way. This includes: an HTTP server implementation, middleware components, logging, dependency injection (DI) services and configuration. The configuration and instantiation consists of the following steps:
- creating a builder
- configuring services
- creating the application host
- running the application
The next few sections expand on the above steps.
Creating a builder
The HostBuilder
knows how to create your application and also configures the necessary default pieces your web application needs to determine how to operate. If we look at the definition of CreateDefaultBuilder
method we see that it:
- sets the base root of the project to the current directory. This is how the application is able to locate the folder containing static files, Razor pages, and configuration files such as
appsettings.json
. - loads configuration. The different configuration sources include
appsettings.json
, environment variables, and command-line arguments. - adds logging logging providers such as console and debug.
The next method to be called is the ConfigureWebHostDefaults
which unsurprisingly, configures web specific defaults. This includes setting the HTTP server as Kestrel server and adds the host filtering middleware, which in combination with the AllowedHosts
key in appsettings.json
, specifies the allowable host names.
Configuring services
The call to UseStartup
looks for the ConfigureServices
and Configure
methods in the Startup
class. The ConfigureServices
method allows us to register services with the dependency injection container. This gives us the ability to put our custom services into the ASP.NET Core, which can be injected into the application where needed. An example would be registering a database, whether that is an in-memory or a SQL database.
The Configure
method allows us to customise the HTTP request pipeline or middleware pipeline.
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Creating the application
We call .Build()
on the HostBuilder
which initialises the host. This then:
- starts the web host instance so it can listen, receive and process HTTP messages.
- loads configuration from
appsettings.json
, environment variables and other configuration sources. - sends logging output to the console. This what we see in the console as we interact with the web application.
Run
We then Run
the application. The Run
method starts the application and blocks the calling thread until the host is shut down.
.NET 6.0
With .NET 6.0 we're given a Startup.cs
file with the following:
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
WebApplication? app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapGet("/hi", () => "Hello!");
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
There is a significant visual difference between the new boilerplate we are now given and the first example. However, the execution of steps remain the same:
- we create a builder which registers default services
- we register our services
- we build the web application
- we run the web application
.NET 6 simplifies the boiler plate code and introduces types WebApplicationBuilder
and WebApplication
that replaces the HostBuilder
and Host
respectively.
.NET WebApplication
Host
This is the new and default way of creating a web application. There are a few major differences in how the WebApplication
host is configured but the underlying steps to configure and manage the application is similar to the Host
we saw in the previous section.
First of all, the Startup
class has been removed. Instead of registering services with ConfigureServices
, they are registered directly on the Services
property. The same applies when configuring the HTTP pipeline. Instead of registering middleware with Configure
we call the UseXyz
methods directly on the app of type WebApplication
(which implements IApplicationBuilder
).
For new applications, this is the preferred way of getting started. However, this doesn't mean you should migrate every ASP.NET application to using the new template. The previous instantiation using the HostBuilder
and Startup
class still works in .NET 6.
With the simplified .NET 6.0 achieving the same result as the first example, is it necessary to know about HostBuilder
and the separation of concerns with the Program.cs
and Startup.cs
? Well, yes. The next section covers why we still have the HostBuilder
and briefly comments on the original way of creating a default web host (that was introduced in ASP.NET Core 2.1).
ASP.NET Core: Host
and WebHost
ASP.NET Core WebHost
Before ASP.NET Core 3.x, this was the default way of configuring and instantiating your web application. The decision to move away from using the WebHost
and WebHostBuilder
was to decouple the web specific configuration (server and middleware) from application specific configuration (logging, configuration and dependency injection services).
Creating a WebHostBuilder
involves:
- Configuring DI services, logging, configuration, default filtering middleware and the HTTP server
UseStartup<Startup>()
to specify the startup class to configure our dependency injection services and HTTP request pipeline.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
For a web application using the generic HostBuilder
, WebHost.CreateDefaultBuilder
is split across two methods Host.CreateDefaultBuilder
and ConfigureWebHostDefaults
as we saw in the first code snippet. The WebHost
is no longer recommended for creating new web applications. However, it is still used for backwards compatibility with ASP.NET Core 2.x.
.NET Generic Host
This is the default way of creating a web host in ASP.NET Core 3.x and .NET 5.0. The generic host separates the registering of configuration, dependency injection services and logging from the web specific resources such as the middleware components and the HTTP server implementation. This allows non-HTTP based applications to be able to use dependency injection services, logging and configuration.
The interfaces IHost
and IHostBuilder
are found in Microsoft.Extensions.Hosting
, emphasising that we can now have hosted services that are not dependent on AspNetCore
namespace.
Conclusion
In this post we looked at understanding what the boilerplate code does in a new ASP.NET Core application. We first looked at the ASP.NET Core 3.x and 5.0 example and then covered the new ASP.NET 6.0 web application template. We saw that despite the visual difference between template code, both do similar jobs and achieve the same outcome. We ended by talking about the differences between startup code across the different ASP.NET Core version, from 2.1 to 6.0.
-
With .NET 6 this is no longer mandatory. Some of the new changes also include not requiring the
namespace
keyword and the removal of commonly usedusing
statements. ↩