Legacy .NET That Still Pays
9 parts, in reading order.
Legacy .NET That Still Pays, Part 1: Don't Build the Log Line You Won't Write
An interpolated debug log line gets built, formatted and thrown away even when debug logging is off. The framework I work in has a boring fix that's older than most of the modern alternatives, and it still earns its keep.
Legacy .NET That Still Pays, Part 2: Dispose in Two Phases
The framework I work in splits disposal into two phases: release the things that can fire, then break the references. It's a small idea that stops shutdown crashes, and it only works if every class in the hierarchy plays along.
Legacy .NET That Still Pays, Part 3: Change Config Without a Restart
A support engineer changes a timeout in the web UI and it takes effect on the next read, with no restart. Under the hood it's an attribute, some reflection, a cache and one hard-coded value that stops the whole thing reading itself forever.
Legacy .NET That Still Pays, Part 4: Keep Async Ordering Without Blocking on .Result
Two async steps had to happen in a fixed order, and the call site was synchronous. The obvious fix was .Result. The better one chains the second step as a continuation, so the order holds and no thread sits waiting.
Legacy .NET That Still Pays, Part 5: Forty Endpoints, Five Shapes
Review asked me to finish a whole controller async, so I did, and then took on another with 39 endpoints of its own. Converting them one by one would have meant 39 diffs to review. Grouping them by shape first meant five.
Legacy .NET That Still Pays, Part 6: Prove It in the App, Then Promote It
A timespan validator started life as a private helper in one service, and only moved into our shared library once it had proved itself. Proving it first turned up a parsing surprise that would otherwise have shipped to every consumer of the library.
Legacy .NET That Still Pays, Part 7: Your Singleton Is Shared State
A singleton in the dependency-injection container is a global variable with better manners. Every write to it affects every reader at once. That matters for a shared in-memory list read by several background workers, and for deciding when to snapshot instead of share.
Legacy .NET That Still Pays, Part 8: The Exception That Became an Empty List
Catching an exception and returning an empty list turns 'we couldn't find out' into 'there's nothing there', and the second one is much harder to debug. A null in a fetch became a quiet 'no data'. A small result type now says which it was.
Legacy .NET That Still Pays, Part 9: The Typo That Locks a Role Out
Role names as raw strings in attributes fail silently when misspelled. A controller gets locked to nobody, or opened to the wrong people, and the compiler says nothing. The fix is a move to constants, combined by concatenation instead of naming every combination, with a test per role to back it up.