Configuration

WebApiStartup is the abstract base class you derive from to bootstrap an ASP.NET Core host: it runs one standard, opinionated sequence — configuration loading, controllers, the…

WebApiStartup is the abstract base class you derive from to bootstrap an ASP.NET Core host: it runs one standard, opinionated sequence — configuration loading, controllers, the invalid-model-state envelope, Swagger, the standard middlewares and controller mapping — and leaves you a single method to implement, ConfigureServices, for your own services. The sequence is tuned through WebApiStartupOptions, and every step in it is also a public extension method, so the same setup can be composed on a plain WebApplicationBuilder without the base class.

The lifecycle

flowchart TB
    New["new Startup(args)<br/><i>parses WebApiParameters, creates the WebApplicationBuilder, applies the options callback</i>"] --> Build["Build()"]
    subgraph Services["Services — on the WebApplicationBuilder"]
        Load["builder.LoadConfiguration(Parameters)"] --> Controllers["Services.AddControllers()"]
        Controllers --> Envelope["Services.AddInvalidModelStateEnvelope()<br/><i>if Options.UseInvalidModelStateEnvelope</i>"]
        Envelope --> SwaggerGen["builder.AddWebApiSwagger(Parameters, Options.Swagger)<br/><i>only where Swagger is allowed</i>"]
        SwaggerGen --> Configure["ConfigureServices(builder)<br/><i>abstract — your services</i>"]
    end
    subgraph Pipeline["Pipeline — on the built WebApplication"]
        Standard["app.UseStandardMiddlewares(Options.CorsPolicy)<br/><i>trace, exceptions, Swagger, CORS, authentication</i>"] --> Extra["app.UseWebApiMiddlewares(Options.Middlewares)"]
        Extra --> Map["app.MapControllers()"]
    end
    Build --> Load
    Configure --> AppBuild["builder.Build()"]
    AppBuild --> Standard
    Map --> Return["returns the WebApplication<br/><i>map more endpoints, then Run()</i>"]

Build() runs these steps in this order, every time:

#StepWhat it doesTuned by
1Builder.LoadConfiguration(Parameters)Loads appsettings and/or the .env file per WebApiParameters; registers WebApiParameters, ConfigurationLoader and SettingsProvider (always — AuthenticationMiddleware needs it), plus EnvironmentProvider when the env file is loaded.UseAppSettings: / UseEnvFile: args
2Builder.Services.AddControllers()Registers MVC controllers.—
3Builder.Services.AddInvalidModelStateEnvelope()Answers invalid model state with a DataOutput<string>-shaped 400 (see Invalid model state).Options.UseInvalidModelStateEnvelope
4Builder.AddWebApiSwagger(Parameters, Options.Swagger)Registers the Swagger generator, only in environments where Swagger is allowed, and sets the Swagger:Enabled marker.Options.Swagger, EnableSwaggerDocs: / SwaggerEnvironments: args
5ConfigureServices(builder)Your services: data access, handlers, AddTokenAuthentication, AddCors, hosted services, logging, and so on.your override
6Builder.Build()Builds the WebApplication.—
7app.UseStandardMiddlewares(Options.CorsPolicy)UseForwardedHeaders (a no-op until ForwardedHeadersOptions is configured), TraceActivityMiddleware, ExceptionMiddleware, Swagger (only if step 4 registered the generator), UseCors (only if a policy is named), then AuthenticationMiddleware (only if AddTokenAuthentication was called). Swagger and CORS run before authentication, so the Swagger UI and CORS preflight requests never need a token.Options.CorsPolicy, whether AddTokenAuthentication was registered
8app.UseWebApiMiddlewares(Options.Middlewares)Your extra WebApiMiddleware types, in order, after the standard ones.Options.Middlewares
9app.MapControllers()Maps the controller endpoints.—

The members of WebApiStartup are:

MemberKindPurpose
WebApiStartup(string[] args)protected constructorParses args into Parameters and creates Builder, with the default options.
WebApiStartup(string[] args, Action<WebApiStartupOptions> configure)protected constructorThe same, then tunes the sequence through configure.
Parametersprotected propertyThe WebApiParameters parsed from args.
Optionsprotected propertyThe WebApiStartupOptions tuning the sequence.
Builderprotected propertyThe WebApplicationBuilder; its environment is Parameters.EnvironmentName when one was supplied.
ConfigureServices(WebApplicationBuilder builder)abstractThe one method you implement: register your own services. Runs after the standard services, before the app is built.
Build()publicRuns the standard sequence and returns the built WebApplication. Can be called only once.
Run() / RunAsync()publicCalls Build() and runs the app until it shuts down.

A minimal Startup

public class Startup(string[] args) : WebApiStartup(args, options =>
{
    options.Swagger.JwtAuthentication = true;
    options.Middlewares.Add(typeof(MyMiddleware));
})
{
    protected override void ConfigureServices(WebApplicationBuilder builder)
    {
        builder.Services.AddTokenAuthentication(_ => { }); // see Security
        builder.Services.AddHealthChecks();
        builder.Services.AddScoped<IMyService, MyService>();
    }
}

var app = new Startup(args).Build();
app.MapHealthChecks("/health");
app.Run();

Build() returns the WebApplication, so endpoints beyond the controllers — health checks, minimal APIs — are mapped on it before it runs. When there’s nothing extra to map, new Startup(args).Run() (or await new Startup(args).RunAsync()) builds and runs in one call.

There is no need to list TraceActivityMiddleware, ExceptionMiddleware or AuthenticationMiddleware: the standard sequence adds them, AuthenticationMiddleware as soon as AddTokenAuthentication has been called. Options.Middlewares is only for additional WebApiMiddleware types; a type that doesn’t derive from WebApiMiddleware makes Build() throw an ArgumentException. Pipeline steps that aren’t WebApiMiddlewares (ASP.NET Core’s own UseRateLimiter, …) can be added on the returned app, where they run after the standard and extra middlewares and before the endpoints; when you need them anywhere else, compose the pipeline yourself, as below. CORS is the exception, since it must run before authentication: register the policy with AddCors in ConfigureServices and name it in Options.CorsPolicy.

WebApiStartupOptions

OptionTypeDefaultEffect
Swagger.AllowedEnvironmentsEnvironmentType[][]The environments in which Swagger is generated and served. When empty, the SwaggerEnvironments:[...] arg decides, falling back to Development and Local (see Swagger).
Swagger.JwtAuthenticationboolfalseDeclares a JWT bearer security scheme in the document, so Swagger UI offers an “Authorize” button.
Swagger.ConfigureGeneratorAction<SwaggerGenOptions>?nullFurther configures the Swagger generator (documents, filters, XML comments).
TraceActivity.LogClientIpbooltrueWhether TraceActivityMiddleware includes the client’s IP address in its “Started request” log entry (see The client IP address).
TraceActivity.TagClientAddressbooltrueWhether TraceActivityMiddleware tags the client’s IP address on the request’s activity as client.address.
UseInvalidModelStateEnvelopebooltrueWhether invalid model state is answered with the DataOutput envelope; false keeps ASP.NET Core’s ProblemDetails.
CorsPolicystring?nullName of a CORS policy registered with AddCors in ConfigureServices; applied before authentication so preflight requests pass.
MiddlewaresList<Type>emptyExtra WebApiMiddleware types, run in order after the standard middlewares and before the endpoints.

Without the base class

Every step of the sequence is a public extension method in ArturRios.Util.WebApi.Configuration, so the same application can be set up on a plain WebApplicationBuilder — useful when you need a different order, or to skip or replace a step:

var parameters = new WebApiParameters(args);
var builder = WebApplication.CreateBuilder(parameters.ToWebApplicationOptions());

builder.LoadConfiguration(parameters);
builder.Services.AddControllers();
builder.Services.AddInvalidModelStateEnvelope();
builder.AddWebApiSwagger(parameters, swagger => swagger.JwtAuthentication = true);

builder.Services.AddTokenAuthentication(_ => { }); // see Security
builder.Services.AddHealthChecks();
builder.Services.AddScoped<IMyService, MyService>();

var app = builder.Build();

app.UseStandardMiddlewares(); // pass a CORS policy name to apply one before authentication
app.UseWebApiMiddlewares(typeof(MyMiddleware));
app.MapControllers();
app.MapHealthChecks("/health");

app.Run();
ExtensionOnPurpose
LoadConfiguration(WebApiParameters)WebApplicationBuilderLoads appsettings/the env file per the parameters and registers WebApiParameters, ConfigurationLoader, SettingsProvider and (when the env file is loaded) EnvironmentProvider.
AddWebApiSwagger(WebApiParameters, Action<WebApiSwaggerOptions>?)WebApplicationBuilderRegisters the Swagger generator only when Swagger is allowed in the current environment, and sets the Swagger:Enabled marker for the AuthenticationMiddleware bypass.
AddInvalidModelStateEnvelope()IServiceCollectionReplaces the default invalid-model-state response with a DataOutput<string>-shaped 400.
UseStandardMiddlewares(corsPolicy?)IApplicationBuilderUseForwardedHeaders (no-op until configured), TraceActivityMiddleware, ExceptionMiddleware, Swagger (if the generator was registered), UseCors (if a policy is named), then AuthenticationMiddleware if AddTokenAuthentication was registered.
UseWebApiSwagger()IApplicationBuilderServes the Swagger JSON and UI only if the generator was registered, so the generator and the UI can never disagree. Already part of UseStandardMiddlewares; call it alone only when composing the pipeline piece by piece.
UseWebApiMiddlewares(params IEnumerable<Type>)IApplicationBuilderRegisters each WebApiMiddleware type, in order; throws an ArgumentException for any other type, before registering anything.

Pass parameters.ToWebApplicationOptions() to WebApplication.CreateBuilder, as above — that is what applies an Environment:<name> argument as the host environment. UseWebApiMiddlewares replaces the extension of the same name that used to live in the ArturRios.Util.WebApi.Extensions namespace.

WebApiParameters — command-line startup args

WebApiParameters parses the process’s args into a small set of properties, without any code changes required at the call site. Each argument is a Key:Value pair with a case-insensitive key; unrecognized or malformed entries are silently ignored, leaving the default in place:

ArgumentPropertyDefaultEffect
Environment:<name>EnvironmentName""Sets the environment name, if it’s a valid EnvironmentType value, and applies it as the host environment through ToWebApplicationOptions() (overriding ASPNETCORE_ENVIRONMENT).
UseAppSettings:<bool>UseAppSettingstrueWhether LoadConfiguration loads appsettings.json. The legacy spelling UseAppSetting is still accepted.
UseEnvFile:<bool>UseEnvFiletrueWhether LoadConfiguration loads a .env file.
EnableSwaggerDocs:<bool>EnableSwaggerDocstruefalse disables Swagger (generation and UI) in every environment, whatever else is configured.
SwaggerEnvironments:[A,B]SwaggerEnvironments[]The environment names in which Swagger is served (see below).

For example:

Environment:Production UseAppSettings:false UseEnvFile:false SwaggerEnvironments:[Development,Staging]

Besides the parsed properties, WebApiParameters exposes:

  • Args — the original arguments it was parsed from.
  • ToWebApplicationOptions() — the WebApplicationOptions to pass to WebApplication.CreateBuilder: the original Args, and EnvironmentName as the environment when one was supplied. WebApiStartup creates its builder this way.
  • IsSwaggerAllowed(environmentName, allowedEnvironments?) — whether Swagger is allowed in the given environment, by the rules below.
  • GetSwaggerEnvironments() — the parsed SwaggerEnvironments, falling back to Development and Local.

Swagger — enabled per environment

Swagger is gated by environment name, not by a simple on/off flag. AddWebApiSwagger decides whether to register the generator with WebApiParameters.IsSwaggerAllowed, using this precedence:

  1. The EnableSwaggerDocs:false CLI arg, if supplied, is a kill switch: Swagger is off in every environment and nothing below is consulted.
  2. WebApiSwaggerOptions.AllowedEnvironments (options.Swagger.AllowedEnvironments on WebApiStartup), if non-empty.
  3. Otherwise, WebApiParameters.GetSwaggerEnvironments() — the SwaggerEnvironments:[...] CLI arg, if it parsed to at least one valid environment name.
  4. Otherwise, the built-in default: Development and Local.
public class Startup(string[] args) : WebApiStartup(args, options =>
{
    options.Swagger.AllowedEnvironments = [EnvironmentType.Development, EnvironmentType.Staging]; // explicit override
    options.Swagger.JwtAuthentication = true;
    options.Swagger.ConfigureGenerator = generator => generator.SupportNonNullableReferenceTypes();
})
{
    // ...
}

Because GetSwaggerEnvironments() always falls back to [Development, Local] rather than returning an empty list, in practice Swagger’s on/off state is always decided by the kill switch and the environment rules above — there is no separate appsettings.json toggle you need to flip to turn Swagger on or off.

The decision is made once, by AddWebApiSwagger. UseStandardMiddlewares (through UseWebApiSwagger) doesn’t re-evaluate it: it serves the Swagger JSON and UI exactly when the generator was registered, so the document and the UI are always both on or both off. It serves them before AuthenticationMiddleware, so in the standard pipeline Swagger never needs a token.

AppSettingsKeys.SwaggerEnabled ("Swagger:Enabled") is a separate internal marker. AddWebApiSwagger sets it whenever it registers the generator, whichever rule allowed it. AuthenticationMiddleware reads this marker to recognize Swagger routes — /swagger and anything under /swagger/, matched by path segment — and skip authentication on them. The marker only matters when you compose the pipeline yourself and put Swagger after AuthenticationMiddleware; the standard pipeline serves Swagger before authentication runs.

Logging

WebApiStartup doesn’t configure logging — that is the derived class’s responsibility. Because the library’s logging types depend only on Microsoft.Extensions.Logging.ILogger<T>, any backend works: the default ASP.NET Core providers (already registered by WebApplication.CreateBuilder), Serilog (builder.Host.UseSerilog(...)), or a custom ILogger. Configure whichever you prefer inside your ConfigureServices override, which runs before the app is built.

Invalid model state

AddInvalidModelStateEnvelope() replaces ASP.NET Core’s built-in [ApiController] validation response with a DataOutput<string>-shaped 400: each invalid model-binding parameter is turned into an error message of the form Parameter: <name> | Error: <message>, collected on the envelope’s Errors list, so validation failures come back in the same shape as any other failed ToActionResult call. WebApiStartup registers it by default; set options.UseInvalidModelStateEnvelope = false to keep ASP.NET Core’s ProblemDetails (application/problem+json) response instead.

Where to next

  • Architecture — how the pipeline, security model and response envelopes fit together.
  • Security — AddTokenAuthentication, JWT validation modes, and role-based authorization.
  • Middleware & Diagnostics — the built-in middlewares UseStandardMiddlewares adds, and how extra ones are registered.
  • Responses — ToActionResult and the invalid-model-state envelope shape.
  • Endpoint Toggling — reading appsettings.json/environment values to enable or disable individual endpoints.