Environments
IHardenedEnvironment is how an application asks where it is running and what it was started with. It is the input to configuration, to conditional registration, and to the default log level.
public interface IHardenedEnvironment : IModuleEnvironment {
string Name { get; }
IReadOnlyList<string> Arguments { get; }
T? Value<T>(string name, T? defaultValue = default);
T? CustomData<T>(string name, T? defaultValue = default);
}Registering one
A host registers an environment before handing the collection to the module:
services.AddHardenedEnvironment(args); // name from HARDENED_ENVIRONMENT
services.AddHardenedEnvironment(new EnvironmentImpl("staging")); // or an explicit oneRegister it with AddHardenedEnvironment, not AddSingleton
The environment has to be reachable as both IHardenedEnvironment and IModuleEnvironment. AddSingleton(environment) and AddTransient<IHardenedEnvironment>(...) register the first only.
The module system looks up IModuleEnvironment while deciding what to register. Finding none, it falls back to its own default, which reads ASPNETCORE_ENVIRONMENT and defaults to Production. So [IfEnvironment] answers Production while everything else in the application reads HARDENED_ENVIRONMENT and says development.
It compiles, it starts, and the only symptom is an environment-gated service quietly not being there. AddHardenedEnvironment registers the one instance under both.
A self-hosting entry point — console or Lambda — gets a generated constructor that does it for you, and another that takes one:
var application = new Application(); // EnvironmentImpl with defaults
var application = new Application(new EnvironmentImpl("qa")); // explicitEnvironmentImpl takes everything by optional argument:
new EnvironmentImpl(
name: "staging",
environmentValues: new Dictionary<string, string> { ["FEATURE_X"] = "on" },
arguments: args,
customData: new Dictionary<string, object> { ["tenant"] = tenant });The environment name
With no name given, EnvironmentImpl reads HARDENED_ENVIRONMENT, and falls back to "development":
HARDENED_ENVIRONMENT=production dotnet runTwo names carry framework behaviour: development and test both default the log level to Debug where every other name defaults to Information. Beyond that, names are yours. Name is compared case-insensitively by the helpers:
if (environment.Matches("production", "staging")) { /* … */ }
if (environment.MatchesVariable("FEATURE_X", "on")) { /* … */ }The test environment is named for you
[HardenedTest] builds an environment called test unless the test says otherwise with [EnvironmentName("…")]. See Testing.
Reading values
Value<T> looks in the dictionary the environment was constructed with, then in the process environment, and converts to T:
var region = environment.Value("AWS_REGION", "us-west-2");
var timeout = environment.Value("TIMEOUT_SECONDS", 30);
var debug = environment.Value("VERBOSE", false);The explicit dictionary takes precedence over the process, which lets a test set a value without touching the machine it runs on.
A variable an application depends on belongs in a configuration model, where it is declared once and has a typed default. Value is for the places that have no model — inside a configuration model's own construction, or in a condition that runs before configuration exists.
Custom data
CustomData<T> carries objects rather than strings, and is not backed by the process environment. It is for values a host has in hand and cannot serialise into a variable — a resolved tenant, an already-constructed client:
var tenant = environment.CustomData<Tenant>("tenant");Environments during registration
[IfEnvironment] and friends are evaluated when modules are applied, against this same environment:
[SingletonService(As = typeof(IEmailSender))]
[IfEnvironment("development", "test")]
public class ConsoleEmailSender : IEmailSender { }IHardenedEnvironment implements DependencyModules' IModuleEnvironment, so the conditional registrations, the configuration models and the application code all see the same answer — provided AddHardenedEnvironment put the instance in the container under both service types.
Log level
The default logging setup derives its minimum level from the environment:
Information, orDebugwhen the environment is nameddevelopmentortest.- Overridden by the
LOG_LEVELvariable if it parses as aLogLevel.
LOG_LEVEL=WarningMicrosoft and System are filtered to Warning regardless. To take over entirely, declare ConfigureLogging on a self-hosting entry point — see Modules.