Love it or hate it, The minimal templates in .NET 6 are here to stay. However, while some applaud minimal templates for their simplicity and clarity, some in the community find the change potentially confusing. I can see both sides of the argument, and in my opinion, we’ve probably not seen the last iteration of .NET templates. In this post, I hope to propose a potential addition to the .NET minimal hosting interface that could help add clarity while keeping lines of code to a minimum.
What Is Minimal Hosting?
Minimal hosting generally takes advantage of C# 9 and C# 10 features to reduce the boilerplate necessary to write, maintain, and extend a .NET Application. Features like global using statements, file-scoped namespaces, and top-level statements are the most visible when first looking at a minimally hosted app. Other features include changes to WebApplicationBuilder and implicit casting from Delegate
and RequestDelegate
. Andrew Lock does an excellent job explaining the changes to WebApplicationBuilder in his blog post.
While I could sit here and explain all the changes that led us to minimal hosting, I think it’s most evident when looking at a sample codebase.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
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();
That’s it! The code represents a fully functional ASP.NET Core web application.
One of the complaints leveled against this approach is it’s not clear when and where to make middleware registration, dependency injection additions, or register endpoints. Of course, the criticism is valid since even the default template has comments guiding developers on when they should make those additions. But what if we could keep the “minimalism” in the sample code and make it more straightforward when and where to make calls?
Proposed Minimal Enhancement
What if there was a way to define interfaces that would only let you register middleware, dependencies, or endpoints independently. What if it looked like this?
var (builder, services) = WebApplication.CreateBuilder(args);
services.AddHttpClient();
var (app, middleware, endpoints)
= builder.Build();
middleware.UseAuthentication();
endpoints.MapGet("/", () => "Hello World!");
app.Run();
The deconstruction of WebApplicationBuilder
and WebApplication
gives us direct handles to elements that clarify when and where to add dependencies, middleware, and endpoints. The good news is, it’s relatively easy to add to any existing .NET 6 ASP.NET Core application. For those willing to try, add the following code into a file named WebApplicationExtensions.cs
to your existing ASP.NET Core application and try it out.
public static class WebApplicationExtensions
{
public static void Deconstruct(this WebApplication application, out IApplicationRunner app, out IApplicationBuilder middleware, out IEndpointRouteBuilder endpoints)
{
app = new ApplicationRunner(application);
middleware = application;
endpoints = application;
}
public static void Deconstruct(this WebApplicationBuilder builder, out WebApplicationBuilder b,
out IServiceCollection services)
{
b = builder;
services = builder.Services;
}
}
public interface IApplicationRunner : IAsyncDisposable
{
void Run();
Task RunAsync();
void Start();
Task StartAsync();
Task StopAsync();
}
public class ApplicationRunner : IApplicationRunner
{
private readonly WebApplication _applicationBuilder;
public ApplicationRunner(WebApplication applicationBuilder)
{
_applicationBuilder = applicationBuilder;
}
public void Run()
{
_applicationBuilder.Run();
}
public Task RunAsync()
{
return _applicationBuilder.RunAsync();
}
public void Start()
{
_applicationBuilder.Start();
}
public Task StartAsync()
{
return _applicationBuilder.StartAsync();
}
public Task StopAsync()
{
return _applicationBuilder.StopAsync();
}
public ValueTask DisposeAsync()
{
return _applicationBuilder.DisposeAsync();
}
}
Conclusion
While some might see the change as a minor one, I think it adds a bit more clarity without sacrificing the minimalism the .NET team is aiming to achieve. Surprisingly, it takes very little code to tweak the minimal hosting approach. Please let me know your thoughts on Twitter at @buhakmeh, whether you love it or hate it. Also, share the idea with your team members and communities for discussion. As always, thank you for reading.