When I created a new ASP.Net Core Empty project in .NET 8, I was surprised by how little code it had.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
That's it. Just four lines. It made me wonder
- What's actually happening behind the scenes
- What is this code really doing
1️⃣ The Entry Point of the Application
This line creates a static instance of WebApplicationBuilder
. It's the entry point of the application, and honestly, its core.
var builder = WebApplication.CreateBuilder(args);
The builder takes care of setting up
- Configuration
- Logging
- And most importantly, the Dependency Injection Container
This is where we register services like a database, HttpClient
, or your own custom services.
Let's take a look at the source code
/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
/// </summary>
/// <param name="args">The command line arguments.</param>
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
public static WebApplicationBuilder CreateBuilder(string[] args) =>
new(new() { Args = args });
The line new(new() { Args = args });
uses the C# 9 feature called target-typed new()
, which is syntactic sugar for
new WebApplicationBuilder(new WebApplicationOptions { Args = args });
2️⃣ Building the WebApplication
Once you've finished registering services and middleware, call .Build()
to finalize everything.
var app = builder.Build();
This creates a WebApplication
instance that bundles together
- All registered services
- App configuration
- Logging
- The middleware pipeline
After calling Build()
, the DI container is locked and you can no longer add or modify services.
Here's the relevant source code
public WebApplication Build()
{
// ConfigureContainer callbacks run after ConfigureServices callbacks
_hostApplicationBuilder.Services.Add(_genericWebHostServiceDescriptor);
Host.ApplyServiceProviderFactory(_hostApplicationBuilder);
_builtApplication = new WebApplication(_hostApplicationBuilder.Build());
return _builtApplication;
}
3️⃣ Define Routes (Minimal API)
This is where you define how your app responds to HTTP requests. Here's the most basic route setup
app.MapGet("/", () => "Hello World!");
This line registers an HTTP GET handler for the root path /
and returns a plain text response.
It's equivalent to the following controller-based version
[HttpGet("/")]
public IActionResult Index()
{
return Content("Hello World!");
}
Much leaner and faster.
4️⃣ Start the Server
This is where everything comes together.
The Run()
method starts the Kestrel web server and begins listening for incoming HTTP requests.
app.Run();
Here's the relevant source code
/// <summary>
/// Runs an application and blocks the calling thread until the host shuts down.
/// </summary>
/// <param name="url">The URL to listen to if the server hasn't been configured directly.</param>
public void Run(string? url = null)
{
Listen(url);
HostingAbstractionsHostExtensions.Run(this);
}
Final Thoughts
Just digging into these four lines of code took quite a bit of time, but it helped me form a mental model of how everything works—how services are registered, how routing works, how the application starts and listens for requests.
There's still more to explore, like service lifetimes, middleware ordering, route constraints, and more, but I'll save that for future posts.
Job done☑️
Thanks for reading the article
If you like it, please don't hesitate to click heart button ❤️
or follow my GitHub I'd appreciate it.
Top comments (0)