Skip to content

Configuring Arc

Arc reads all of its settings from a single ArcOptions object. Wherever Arc runs — an ASP.NET Core web app, an Arc.Core console or worker, or the full Cratis stack — you configure it the same way: bind ArcOptions from configuration, then optionally override it in code. This page is the map: the hosting models, the three configuration mechanisms, and the full ArcOptions tree.

HostBootstrapActivateUse it for
ASP.NET CoreWebApplication.CreateBuilder(args) → builder.AddCratisArc(...)app.UseCratisArc() → app.Run()A web API or full-stack app. The listen URL comes from Kestrel / launchSettings.json.
Arc.CoreArcApplication.CreateBuilder(args) → builder.AddCratisArc(...)app.UseCratisArc() → await app.RunAsync()A console app or worker with no ASP.NET Core. The listen URL comes from ArcOptions.Hosting.ApplicationUrl.
Cratis stackWebApplication.CreateBuilder(args) → builder.AddCratis(...)app.UseCratis() → app.Run()Arc + Chronicle in one host — see the Cratis package.

AddCratisArc takes its arguments in this order: configureOptions (an Action<ArcOptions>), configureBuilder (an Action<IArcBuilder> for optional integrations), and configSectionPath. Use the named configureBuilder: argument when you only want to add a builder feature.

For example, this startup fragment adds the standalone MongoDB provider to an existing ASP.NET Core WebApplicationBuilder. First install Cratis.Arc.MongoDB and configure its connection and database as described in MongoDB getting started. It does not require Chronicle.

using Cratis.Arc;
builder.AddCratisArc(configureBuilder: arc => arc.WithMongoDB());

For optional event sourcing, follow the host- and package-specific Arc–Chronicle registration example instead of substituting an unqualified WithChronicle() call.

For advanced wiring, IServiceCollection.AddCratisArcCore() registers Core services only; it is not equivalent to host bootstrap, configuration binding, request-context wiring, identity setup, endpoint mapping, or listener activation. IHostBuilder.AddCratisArcCore(configureOptions: ...) is a different overload. Prefer the host-specific builders above.

Configuration-bindable settings can be supplied three ways, layered in this order — later wins:

  1. appsettings.json under the Cratis:Arc section.
  2. Environment variables with the Cratis__Arc__ prefix (.NET maps the __ separator onto nested keys), for example Cratis__Arc__GeneratedApis__RoutePrefix.
  3. Code, via the configureOptions callback — it runs after binding, so it overrides the file and the environment.

Bind ArcOptions only through the host’s AddCratisArc / AddCratis registration (or IHostBuilder.AddCratisArcCore), not through an additional services.Configure<ArcOptions>(section) or section.Bind(options). Arc’s binding preserves an unset Introspection.RequireAuthentication; the stock .NET binder can write its getter’s false value back even when the key is absent, making discovery anonymous in every environment (or failing validation when Roles is set). Assigning RequireAuthentication from its own getter, including a property-by-property copy, likewise counts as an explicit opt-out. Leave the property untouched to retain the environment default, or assign an intentional override in configureOptions.

builder.AddCratisArc(options =>
{
options.GeneratedApis.RoutePrefix = "v1/api"; // overrides appsettings / env
options.Introspection.RequireAuthentication = true;
options.Introspection.Roles = "Administrator,Operator";
});
OptionTypeDefaultWhat it controls
ExposeExceptionDetailsboolRuntimeEnvironment.IsDevelopmentExpose exception messages and stack traces in serialized command/query results only in Development by default. Full detail remains in server logs when responses are redacted. Keep disabled in public production environments.
CorrelationId.HttpHeaderstringX-Correlation-IDThe header carrying the correlation ID.
Tenancy.ResolverTypeTenantResolverTypeHeaderHow the tenant is resolved: Header, Query, Claim, Subdomain, Development, or Fixed.
Tenancy.HttpHeaderstringx-cratis-tenant-idThe header used when ResolverType is Header, and the fallback header when it is Subdomain.
Tenancy.BaseDomainstringemptyRequired for Subdomain: the application-owned base domain. Exactly one preceding DNS label selects a tenant; other hosts fall back to HttpHeader. Validation checks syntax, not domain ownership or tenant membership.
Tenancy.QueryParameterstringtenantIdThe query parameter used when ResolverType is Query.
Tenancy.ClaimTypestringtenant_idThe claim used when ResolverType is Claim.
Tenancy.FixedTenantIdstringdevelopmentThe tenant every request resolves to when ResolverType is Fixed or Development.
Tenancy.DevelopmentTenantIdstringdevelopmentThe same value under its original name — reading or writing either key sets both. Supply only one; if both are present the binder’s property order decides.
Introspection.EnabledbooltrueMap both command and query catalog routes. Set to false to remove both routes without changing identity discovery.
Introspection.IdentityDiscoverybooltrueMap users, tenants and the identity-details schema. Set to false to remove these routes without changing catalogs or /.cratis/me. Set both switches to false to remove all discovery.
Introspection.RequireAuthenticationboolunset (getter returns false)Access to the discovery routes (catalogs, /.cratis/users, /.cratis/tenants, /.cratis/identity-details/schema). Not set: anonymous in Development, authenticated elsewhere. true: authenticated everywhere; requires a default ASP.NET Core authentication scheme plus AddAuthorization(), or an Arc.Core authentication handler. false: anonymous everywhere.
Introspection.Rolesstring?nullComma-separated roles; any one grants access to the discovery routes. Implies authentication; cannot be combined with RequireAuthentication: false. No named policy option is provided.
Introspection.TrustForwardedIdentityHeadersboolfalseObsolete. Setting it to true turns on TrustForwardedIdentityHeaders for the whole host. Use TrustForwardedIdentityHeaders instead.
TrustForwardedIdentityHeadersboolfalseTrust the unsigned x-ms-client-principal* identity headers on every request. Until it is true, Arc’s header handlers ignore them and requests stay anonymous. Use only behind an ingress that authenticates callers and strips client identity headers, such as EasyAuth or AuthProxy. See Microsoft Identity.
GeneratedApis.RoutePrefixstringapiBase prefix for generated command and query routes.
GeneratedApis.SegmentsToSkipForRouteint0Namespace segments to drop when building a route.
GeneratedApis.IncludeCommandNameInRoutebooltrueAppend the command name as the last route segment.
GeneratedApis.IncludeQueryNameInRoutebooltrueAppend the query name as the last route segment.
GeneratedApis.EnableQueryHttpMethodbooltrueExpose generated queries over HTTP QUERY with JSON arguments in addition to GET. Disable when infrastructure rejects that method.
Query.KeepAliveIntervalTimeSpan00:00:30Idle keep-alive cadence for observable query hub connections; zero or negative disables keep-alive.
IdentityDetailsProviderType?null (auto-discovered)The identity details provider type.
Hosting.ApplicationUrlstringhttp://+:5001/The listen URL — Arc.Core only (ignored under ASP.NET Core).
JsonSerializerOptionsJsonSerializerOptionsArc defaultsGenerated Arc endpoints use these options; manual serialization must opt in. MVC receives only the naming policy and converters, not null/number handling or other settings. Configure in code only; see the MVC serialization boundary. Add source-generated metadata with AddJsonTypeInfoResolver; see Adding Source-Generated Metadata.

For example, the equivalent appsettings.json keys are:

{
"Cratis": {
"Arc": {
"Introspection": {
"Enabled": true,
"RequireAuthentication": true,
"Roles": "Administrator,Operator"
}
}
}
}

RequireAuthentication and Roles govern mapped discovery endpoints: the command and query catalogs, users, tenants, and the identity-details schema. Enabled controls the catalogs; IdentityDiscovery controls users, tenants and the schema. Set both to false to skip discovery mapping, authentication enforcement checks and exposure warnings. Valid access settings then have no effect, but role configuration is still validated. Outside Development, hosts without authentication leave enabled discovery endpoints unmapped by default and warn once per host; explicitly requiring authentication on such a host fails startup. See introspection production access for the security boundary and startup validation.

Route generation (GeneratedApis) and JSON serialization have worked examples on the ASP.NET Core configuration page; Query.KeepAliveInterval is covered with the observable query demultiplexer.

The configureBuilder callback exposes IArcBuilder, which is where Arc’s pluggable backends attach:

The header/query/subdomain resolvers select a tenant; they do not authorize membership. See tenant resolution before accepting caller-selected tenant IDs.

Arc resolves an identity details provider automatically by type discovery. Set ArcOptions.IdentityDetailsProvider to pin a specific type, or register one explicitly:

builder.Services.AddIdentityProvider<MyIdentityDetailsProvider>();

For authenticating requests, see Authentication. When you use the Cratis package, Microsoft Identity Platform authentication is wired for you.

A singleton that takes a scoped dependency in its constructor holds that one instance forever — the classic captive dependency. In a multi-tenant application it is the difference between “the right tenant’s data” and “whichever tenant happened to be first”, and nothing about it fails loudly. .NET has a detector for exactly this: ServiceProviderOptions.ValidateScopes, which the host turns on in Development so the capture throws the moment you resolve it.

Arc keeps that detector on. Every host Arc supports settles two ServiceProviderOptions fields for you:

OptionValue Arc appliesWhy
ValidateScopesbuilder.Environment.IsDevelopment()The host’s own default, restated so it survives. On in Development, off everywhere else.
ValidateOnBuildfalseArc supplies registrations contextually — IHostApplicationBuilder, the type a convention binding is for, values only an executing command or an in-flight request can hand over. Eager validation constructs every registration up front and can resolve none of them, so leaving it on fails Build() outright.

The reason both fields have to be stated together is that UseDefaultServiceProvider and ConfigureContainer each start from a brand new options object — setting one field discards every other value the host had already applied. Turning ValidateOnBuild off without restating ValidateScopes is what silently took the captive-dependency check with it.

You own your container. State your own choice and it wins — with one ordering rule that differs by host:

// WebApplicationBuilder (ASP.NET Core): call it AFTER AddCratisArc.
builder.AddCratisArc();
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true; // on in every environment, not just Development
options.ValidateOnBuild = false; // keep this off — see the table above
});

For an IHostBuilder, call UseDefaultServiceProvider directly on the builder after its Arc registration (there is no builder.Host property). Arc calls UseDefaultServiceProvider itself on these hosts, and the last call wins, so a call placed before AddCratisArc is discarded.

Arc.Core has no such ordering rule. ArcApplicationBuilder applies its defaults while it is being constructed, so a ConfigureContainer call — your own factory, Autofac, Lamar — replaces them whether you make it before or after AddCratisArc:

var builder = ArcApplication.CreateBuilder(args);
builder.ConfigureContainer(new MyServiceProviderFactory()); // before or after — either wins
builder.AddCratisArc();

ValidateScopes follows IsDevelopment(), which is an exact match on the environment name Development. A host running under a custom name — Local, Dev, Staging — is not Development by that rule, so it gets no scope validation, exactly as a bare .NET host would. If you want the check there, ask for it explicitly with the override above.

CORS is not an Arc option. In an ASP.NET Core host, configure it with standard ASP.NET Core (builder.Services.AddCors(...) and app.UseCors(...)). Arc neither wraps nor replaces it. These ASP.NET extensions do not apply to ArcApplication; configure cross-origin handling at your trusted ingress for that host.

Generated endpoints accept the HTTP QUERY method in addition to GET by default (GeneratedApis.EnableQueryHttpMethod = true). If clients use it across origins, include QUERY in your allowed methods (policy.WithMethods("GET", "POST", "QUERY")) — it is not a simple method, so cross-origin calls preflight. This is a method-allowlist consideration, not a substitute for configuring allowed origins, headers, and credentials. A client that uses GET does not need QUERY in that allowlist.