Options Validation
A `MaxPoolSize` of 5000 sits in appsettings.json, binds cleanly into a typed object, and crashes nothing at startup. The app runs for hours on the bad value, then throws on the one request that finally reads it.
- ▸A
MaxPoolSizeof 5000 binds without complaint; the rule that rejects it only runs if you register one. - ▸Rules run lazily by default: bad config boots green and fails on the first request that reads it.
- ▸Same rule, two failure moments: on the first read, or at startup, one line apart.
MaxPoolSize sits at 5000 in appsettings.json. It is an integer, the property it binds to is an int, so binding succeeds: the typed object comes back with MaxPoolSize == 5000 and IOptions<DatabaseOptions>.Value hands it over without a word.
Binding only asks one question: can this string become that type? 5000 parses to an int, so the answer is yes. Whether 5000 is a *sane* pool size, whether it sits under some ceiling, whether it agrees with the other fields, binding never asks. Those are questions about meaning, and binding checks shape.
So the object is structurally valid and semantically wrong at the same time, and nothing in the pipeline notices. The app starts. Requests flow. The connection pool tries to open 5000 connections. The failure, when it comes, points at the database driver, not at the config, three layers away from the line that was actually wrong.
That gap, between a value that binds and a value that is correct, is the whole reason validation exists. The rest of this topic fills it: how to state the rule, when to run it, what the failure looks like, and how to let the compiler write the check.