A client's team — three developers, a six-week plan, a clear goal: move their internal operations app off .NET Framework and onto modern .NET. Two weeks in, they hit a wall. Their login screen didn't actually have a login screen. It used Windows Authentication: employees opened the app already signed in, because Windows had already identified them on the domain. Nobody had written that down as a "feature" because nobody thought of it as one — it had just always worked that way. When they looked at how to carry that into modern .NET, the answer wasn't a checkbox. It needed real design work, not a settings change.

That's usually where I get the call. Not because the upgrade itself is impossible — it almost never is — but because a team hit one of a handful of things that quietly assume .NET Framework and don't carry over on their own. I want to walk through the real list of these: what each one actually is, why it blocks a straightforward upgrade, and why every single one of them has a known path forward. None of them is a reason to stop.

System.Web and HttpContext

Classic ASP.NET applications are built on System.Web — the library that gives you HttpContext, request modules, and handlers. Modern ASP.NET Core doesn't have System.Web. It has its own, different request pipeline. If your application reaches into HttpContext.Current from deep inside business logic — and in older codebases, it often does, scattered across dozens of files — that code can't just be recompiled against modern .NET. It has to be found, understood, and rewritten to use the modern equivalent.

This is real work, but it's mechanical work. Microsoft documents adapter shims specifically for this situation, letting you run old and new code side by side while you move pieces over gradually, rather than needing a single flag-day rewrite.

WCF services

Windows Communication Foundation was Microsoft's framework for building service-oriented integrations under .NET Framework. Modern .NET doesn't include a WCF server. If an internal system, a partner integration, or a piece of your own application talks over WCF, that's a real decision point, not a settings toggle.

The practical paths are a community-maintained port that lets a WCF-shaped service run on modern .NET, or a rewrite of that specific service using gRPC or a plain HTTP API, which is often the better long-term move if the service is small enough to be worth doing properly. Either way, this needs to be identified early, because it's one of the few blockers that genuinely needs a design decision rather than a mechanical conversion.

Windows Authentication and other Windows-only APIs

This is what caught my client's team. Windows Authentication, and a handful of other Windows-specific APIs, assume IIS and Windows in ways that don't map one-to-one onto modern .NET's cross-platform model. Modern .NET still supports Windows Authentication — it is not gone — but it has to be configured and wired up deliberately, as its own piece of the migration, not assumed to "just come along" the way it did on Framework.

The fix here isn't exotic. It's planning: knowing this dependency exists before you start, so it's a scheduled piece of work instead of a two-week surprise that stalls the whole project.

Third-party libraries with no modern-.NET version

Sometimes the blocker isn't your code at all — it's a vendor's. A reporting engine, a barcode library, an old PDF generator: if it was last updated in 2016 and only ships a .NET Framework build, modern .NET can't use it directly, full stop.

Here the options are narrower but still real: find a modern-.NET alternative (often there is one, the ecosystem has matured a lot), isolate the dependency behind a small internal interface so swapping it later doesn't ripple through the whole app, or, in a minority of cases, keep that one piece running on Framework behind an internal API call from the new app — ugly, but workable as a bridge while the rest moves forward.

web.config-based configuration and single-server deployment

Less dramatic, but common: an app whose configuration lives entirely in web.config, tuned over years with settings nobody fully remembers the reason for, deployed by hand to one specific server. Modern .NET's configuration system works differently, and modern deployment assumes you can run on more than one place without special-casing each one.

This is usually the easiest blocker on the list to clear — it mostly costs discipline: documenting what each setting actually does, then moving it to the new configuration model, rather than guessing and hoping nothing breaks.

None of this is a reason to stop

Every one of these is solvable, and every one has already been solved by other teams before you. What they have in common is that none of them gets discovered by just reading the project file — they get discovered by someone actually opening the login flow, the integration layer, the deployment scripts, and asking "what does this secretly depend on?"

That's exactly what an audit is for: finding every one of these before a developer hits it mid-sprint, mapping which ones apply to your system, and turning each one into a specific decision with a specific plan — instead of a surprise that stalls the project in week two, the way it did for my client's team.

Let's talk through your situation.