Skip to content

AppBuilder

AppBuilder is a fluent entry point that replaces the usual Program.cs boilerplate. It composes a host (by default a WebApplication), lets you attach modular setup containers, registers startup gates, and gives you lifecycle hooks — all before returning an AppRunner you can RunAsync.

Quick start

csharp
using Servus.Application.Startup;

var app = AppBuilder
    .Create()
    .WithSetup<GrpcSetupContainer>()
    .WithSetup<LoggingSetupContainer>()
    .Build();

await app.RunAsync();

AppBuilder.Create() uses WebApplication.CreateBuilder() by default. If you need a different host type (e.g. HostApplicationBuilder for a console worker), pass your own:

csharp
var app = AppBuilder
    .Create(Host.CreateApplicationBuilder(args), b => b.Build())
    .WithSetup<MyWorkerSetup>()
    .Build();

Attaching setup containers

WithSetup<T>() and WithSetup(instance) attach one or more Setup Containers. Each container implements one or more of the lifecycle interfaces (IServiceSetupContainer, IConfigurationSetupContainer, ILoggingSetupContainer, ApplicationSetupContainer) and AppBuilder calls them in the right order.

csharp
var app = AppBuilder
    .Create()
    .WithSetup<ConfigSetup>()       // IConfigurationSetupContainer
    .WithSetup<ServicesSetup>()     // IServiceSetupContainer
    .WithSetup<LoggingSetup>()      // ILoggingSetupContainer
    .WithSetup<MiddlewareSetup>()   // ApplicationSetupContainer<WebApplication>
    .Build();

Containers are the split-and-compose unit of a Servus app. Keep each container focused on one concern — you reuse them across projects and in unit tests.

Startup gates

Block the app from starting until something outside is ready (a database, an upstream API, a license server, a file to appear). See Startup Gates for the full flow.

csharp
var app = AppBuilder
    .Create()
    .WithSetup<ServicesSetup>()
    .WithStartupGate(async () => await db.CanConnectAsync())
    .WithStartupGate<LicenseServerGate>()
    .Build();

await app.RunAsync();

Lifecycle hooks

Run code at the three standard hosting transitions:

csharp
var app = AppBuilder
    .Create()
    .WithSetup<ServicesSetup>()
    .OnApplicationStarted(sp => sp.GetRequiredService<IMetrics>().MarkStart())
    .OnApplicationStopping(() => Log.Information("Going down…"))
    .OnApplicationStopped(() => Log.Information("Bye."))
    .Build();

Full API

csharp
public class AppBuilder
{
    public static AppBuilder Create<T>(T builder, Func<T, IHost> createHost) where T : IHostApplicationBuilder;
    public static AppBuilder Create();

    public AppBuilder WithSetup<TContainer>() where TContainer : class, ISetupContainer, new();
    public AppBuilder WithSetup(ISetupContainer container);

    public AppBuilder WithStartupGate(Func<Task<bool>> gate);
    public AppBuilder WithStartupGate<TGate>() where TGate : class, IStartupGate, new();
    public AppBuilder WithStartupGate(IStartupGate gate);

    public AppBuilder OnApplicationStarted(Action<IServiceProvider> started);
    public AppBuilder OnApplicationStopping(Action stopping);
    public AppBuilder OnApplicationStopped(Action stopped);

    public AppRunner Build();
}

public partial class AppRunner
{
    public Task RunAsync(CancellationToken token = default);
    public Task StartAsync(CancellationToken token);
}

Servus and happy coding! 🥨🍺