Introduction
With the release of .NET 8, Microsoft takes a significant stride forward, introducing the native Ahead-of-Time (AOT) compilation for ASP.NET Core. This advancement not only enhances application performance but also simplifies the development process, marking a new era in the .NET ecosystem.
The emergence of native AOT in .NET 8
The introduction of native AOT in .NET 8 is a game-changer for web developers. This technology compiles .NET code directly into native code, bypassing the need for Just-In-Time (JIT) compilation at runtime. The result? Faster startup times, reduced memory footprint, and overall improved application performance, especially crucial for high-traffic web APIs and microservices.
Exploring the ASP.NET Core Web API (native AOT) project template
.NET 8 introduces a new project template specifically designed for native AOT — the ASP.NET Core Web API (native AOT) project template. This template, identified by the short name ‘webapiaot’, comes with AOT publish enabled by default. It’s tailored for developers looking to leverage the full potential of AOT compilation right from the onset of their project. Two new features in this update is the CreateSlimBuilder()
and CreateEmptyBuilder()
method.
The CreateSlimBuilder method: optimizing for efficiency
CreateSlimBuilder
method is a testament to Microsoft’s commitment to efficient development. It initializes the WebApplicationBuilder with only the essential ASP.NET Core features needed to run an application. This approach not only streamlines the development process but also ensures that the applications are lightweight and high-performing. Key features included in the CreateSlimBuilder method are:
- JSON file configuration for appsettings.json and appsettings.{EnvironmentName}.json, enabling robust and flexible configuration management.
- Integration of User secrets configuration, enhancing security in development environments.
- Built-in Console logging, facilitating straightforward debugging and monitoring.
- Comprehensive Logging configuration, providing developers with critical insights into application behavior.
var builder = WebApplication.CreateSlimBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Yet, the CreateSlimBuilder
method's ascetic approach omits the traditional Startup.cs file, necessitating explicit configuration by developers. It also strips out the EventLog, Debug providers, and EventSource host—components that must be manually included if needed. Crucially, there's no out-of-the-box support for IIS, HTTPS, HTTP3, or the full suite of Kestrel server configurations, calling for deliberate additions to fortify communication security and server robustness.
These can be explicitly added to your configuration. For instance, see the following example for how to implement these customizations.
using Microsoft.AspNetCore.Routing.Constraints;
var builder = WebApplication.CreateSlimBuilder(args);
//http3 customization
builder.WebHost.UseQuic();
//Https customization
builder.WebHost.UseKestrelHttpsConfiguration();
//Regex customization
builder.Services.AddRouting().Configure<RouteOptions>(x =>
{
x.SetParameterPolicy<RegexInlineRouteConstraint>("Regex");
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
The CreateEmptyBuilderMethod: customization in its purest form
.NET 8’s CreateEmptyBuilder
method is a testament to customization, offering developers a blank slate to craft bespoke, small-scale applications. It's the epitome of simplicity and autonomy, where only the selected components find a place.
var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions
{
Args =args
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
To ensure the application functions correctly, each component must be configured manually. Attempting to execute the above code without the necessary configurations will result in errors.
using Microsoft.AspNetCore.Routing.Constraints;
var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions
{
Args = args
});
builder.WebHost.UseKestrelCore();
builder.Services.AddRoutingCore();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Benchmark conclusion: assessing Builder methods in .NET 8
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Routing.Constraints;
using System.Collections.Generic;
namespace NewAppTypes
{
[MemoryDiagnoser]
public class BenchMarks
{
public string[]? Args { get; private set; }
[Benchmark]
public void CreateBuilder()
{
var builder = WebApplication.CreateBuilder(Args);
var app = builder.Build();
builder.WebHost.UseUrls("http://*:80", "https://*.443");
app.MapGet("/", () => "Hello World!");
}
[Benchmark]
public void CreateSlimBuilder()
{
var builder = WebApplication.CreateSlimBuilder(Args);
//http3 customization
builder.WebHost.UseQuic();
//Https customization
builder.WebHost.UseKestrelHttpsConfiguration();
//Regex customization
builder.Services.AddRouting().Configure<RouteOptions>(x =>
{
x.SetParameterPolicy<RegexInlineRouteConstraint>("Regex");
});
var app = builder.Build();
builder.WebHost.UseUrls("http://*:80", "https://*.443");
app.MapGet("/", () => "Hello World!");
}
[Benchmark]
public void CreateEmptyBuilder()
{
var builder = WebApplication.CreateEmptyBuilder(new WebApplicationOptions
{
Args = Args
});
builder.WebHost.UseKestrelCore();
builder.Services.AddRoutingCore();
var app = builder.Build();
builder.WebHost.UseUrls("http://*:80", "https://*.443");
app.MapGet("/", () => "Hello World!");
}
}
}
Benchmark results
The benchmark tests reveal insightful data on their performance. From the results, we observe the following:
- CreateBuilder method: This standard method for initializing a web application shows a mean execution time of 2,682.3 microseconds (us) with an allocation of approximately 536.26 KB. This method sets up a complete web hosting environment with all default services and configurations. The execution time and memory allocation reflect this comprehensiveness.
- CreateSlimBuilder method: The optimized CreateSlimBuilder method records a faster mean execution time of 1,604.4 us, around 40% quicker than the
CreateBuilder
method. It also allocates less memory, approximately 428.34 KB. This performance gain can be attributed to the reduced number of default services and configurations, which aligns with the method’s design to provide a more streamlined startup.
- CreateEmptyBuilder method: The most minimalistic approach,
CreateEmptyBuilder
, showcases the fastest mean execution time of 121.3 us, which is substantially quicker than the other two methods. It also has the lowest memory footprint with just over 107.78 KB allocated. This highlights the method’s bare-bones initialization strategy, where only explicitly defined services and configurations are included.
The standard deviations indicate the variability in execution times across multiple runs, with CreateBuilder
having the highest variability. This could be due to the larger number of components being loaded and configured, which may introduce more fluctuation in initialization time.
In terms of application development, these benchmarks suggest that CreateSlimBuilder
and CreateEmptyBuilder
offer significant performance advantages over the traditional CreateBuilder
method. Developers should consider these options when performance is a critical factor, especially in environments where startup time and memory efficiency are paramount.
However, it is important to note that these performance improvements come with trade-offs in functionality. While CreateEmptyBuilder
provides the fastest startup and the lowest resource consumption, it requires developers to manually configure all required services, which could increase development complexity and time. CreateSlimBuilder
offers a middle ground, with some default configurations provided while still allowing for a leaner application setup.
Conclusion
The selection of a builder method in .NET 8 is not merely a technical decision but a strategic one, influenced by the project’s unique demands. The benchmarks provide a quantitative foundation for this choice, ensuring that developers are well-equipped to make decisions that align with their performance targets and development philosophies.