Your .NET Framework 4.x monolith still works — but it is Windows-only, IIS-bound, locked out of C# 12, and maintained by a shrinking developer pool. FreedomDev migrates legacy .NET Framework applications to .NET 8 using the strangler fig pattern: module-by-module extraction behind YARP reverse proxy, zero downtime, zero feature freeze. WCF to gRPC, EF6 to EF Core, ASP.NET MVC to Razor Pages or Blazor — we handle the breaking changes that the .NET Upgrade Assistant flags but cannot fix. Zeeland, MI. 20+ years of .NET migrations across manufacturing, healthcare, financial services, and logistics.
Microsoft ended active development on .NET Framework at version 4.8.1. There will be no 4.9. There will be no new language features, no performance improvements, no new APIs, and no updates to the BCL beyond security patches. .NET Framework 4.8.1 is the terminal release. Microsoft's official guidance since 2019 has been unambiguous: all new development should target .NET (formerly .NET Core), and existing .NET Framework applications should plan migration. The framework will continue to receive security patches because it ships as a Windows component, but it is functionally frozen. Every month you stay on .NET Framework, the gap between your application and the current platform widens — and it is not a gap that shrinks on its own.
The technical debt compounds in specific, measurable ways. Your application is locked to Windows Server and IIS. That means Windows Server licensing at $1,000-6,000 per server per year depending on your edition and core count, plus IIS management overhead that disappears entirely when you deploy to Linux containers on a $20/month VPS or a managed Kubernetes cluster. Your developers cannot use C# 12 features — primary constructors, collection expressions, inline arrays, interceptors — because the Roslyn compiler targets .NET Framework 4.8 with C# 7.3 as the maximum supported version. Your NuGet package ecosystem is eroding: major libraries have stopped publishing .NET Framework-compatible versions. Newtonsoft.Json still works, but the ecosystem is consolidating around System.Text.Json with source generation. Polly v8 dropped .NET Framework support. MediatR 12+ requires .NET 6 minimum. Every library update your team evaluates now starts with the question: does this even support our framework version?
Performance is where the gap becomes undeniable. .NET 8 delivers 3-10x throughput improvements over .NET Framework 4.8 depending on the workload profile. Microsoft's own TechEmpower benchmark results show ASP.NET Core on .NET 8 handling over 7 million plaintext requests per second on standard hardware — a number that would require a rack of IIS servers to approach on .NET Framework. The improvements come from everywhere: Kestrel versus IIS pipeline overhead, Span<T> and Memory<T> reducing heap allocations, tiered JIT compilation with dynamic PGO recompiling hot paths at runtime, regions-based garbage collection cutting GC pause times, native AOT compilation for serverless cold starts measured in milliseconds instead of seconds, and System.Text.Json with source-generated serializers eliminating reflection entirely. Teams that have completed the migration consistently report 30-50% reductions in cloud compute costs from running the same business logic on fewer, smaller instances.
The talent problem is already real and it accelerates every year. The median .NET developer in 2026 has built their career on .NET Core and .NET 5+. They use Rider or VS Code, they deploy to Linux containers, they write Minimal APIs and use dependency injection that ships with the framework. When you post a job requiring .NET Framework 4.x, Web Forms, WCF, or classic MVC 5, you are filtering out the majority of the modern .NET developer market. The candidates who remain are either expensive specialists charging premium rates, or senior developers who would rather work on a modern stack and will leave as soon as they can. Your retention risk is directly tied to your framework version. Every year on .NET Framework narrows the hiring funnel and increases the cost of the developers willing to work in it.
Locked to Windows Server and IIS — $1,000-$6,000/server/year in licensing that disappears with Linux container deployment
C# 7.3 maximum language version — your developers cannot use primary constructors, pattern matching enhancements, collection expressions, or any feature from the last 7 years of C# evolution
NuGet ecosystem erosion — Polly v8, MediatR 12+, and increasingly more libraries have dropped .NET Framework support entirely
3-10x performance gap versus .NET 8 — paying for 3-5x more compute infrastructure to serve the same traffic
Shrinking developer talent pool — modern .NET developers build for .NET 8 on Linux, and your Framework 4.x job posting filters them out
.NET Framework 4.8.1 is the terminal release — no new features, no performance work, security patches only because it ships with Windows
Our engineers have built this exact solution for other businesses. Let's discuss your requirements.
The strangler fig pattern is the only migration approach that consistently works for production .NET Framework applications with real users, real traffic, and zero tolerance for downtime. Named after strangler fig trees that grow around a host tree and gradually replace it, the pattern works by placing a reverse proxy in front of your existing .NET Framework application and routing traffic to new .NET 8 implementations as each module is migrated. The legacy application keeps running in production. Users never see a maintenance window. New modules go live one at a time. Old modules get decommissioned after the replacement is validated. At no point does anyone bet the business on a big-bang cutover.
FreedomDev implements this using YARP — Yet Another Reverse Proxy — Microsoft's own high-performance reverse proxy library built specifically for .NET. YARP sits in front of both your legacy IIS-hosted .NET Framework application and the new .NET 8 services. Route configuration determines which URLs go where: /api/orders might route to the new .NET 8 OrderService while /api/legacy-reports still routes to the .NET Framework monolith. As each module is migrated, tested, and validated in production, the YARP configuration updates to route its traffic to the new implementation. The legacy surface area shrinks with every sprint until nothing routes to the old application and it can be shut down. This is not theory — it is the approach Microsoft recommends in their official migration documentation, and it is how we have executed every .NET Framework migration since 2020.
The migration sequence matters. Not every module should be migrated in the order it appears in your solution explorer. We prioritize based on three factors: business value (which modules are blocking new feature development), technical risk (which modules have the most Windows-specific dependencies that need alternatives), and dependency graph position (which modules can be extracted cleanly versus which ones are deeply entangled with shared state). Typically, the healthiest migration order is: shared libraries and data access first (because everything depends on them), then API endpoints and background services (because they have well-defined boundaries), then UI layers last (because ASP.NET MVC views and Razor Pages require the most rewrite work). We have seen teams try to migrate the UI first and get stuck for months — the controllers depend on everything, and you cannot extract them without extracting their dependencies.
Microsoft's .NET Upgrade Assistant is a useful starting point but it is not a migration tool. It is a diagnostic tool that identifies breaking changes, generates a compatibility report, and automates some mechanical transformations — updating project file formats from .csproj with packages.config to SDK-style .csproj with PackageReference, converting web.config settings to appsettings.json, and flagging API calls that do not exist in .NET 8. What it cannot do is the actual engineering: redesigning WCF service contracts as gRPC proto definitions, replacing System.Web.HttpContext with ASP.NET Core's HttpContext abstraction, converting EF6 data models to EF Core with correct navigation property configuration, or refactoring code that depends on Global.asax application lifecycle events into the ASP.NET Core middleware pipeline. The Upgrade Assistant tells you what is broken. FreedomDev fixes it.
YARP (Yet Another Reverse Proxy) is Microsoft's production-grade reverse proxy built on .NET. We deploy YARP as the front door to your application, routing each URL path to either the legacy .NET Framework backend or the new .NET 8 service. Route configuration is declarative — JSON or code-based — and can be updated at runtime without restarting the proxy. Health checks on both backends ensure that if a new service has issues, traffic automatically falls back to the legacy implementation. Session affinity, load balancing, and header-based routing are all built in. YARP handles the incremental cutover that makes strangler fig migration safe for production systems with real users.
Windows Communication Foundation is the single largest migration obstacle for most .NET Framework applications. WCF does not exist in .NET 8 — Microsoft explicitly chose not to port it. CoreWCF, the community-supported partial port, covers basic HTTP and NetTCP bindings but does not support WS-* specifications, message queuing, or the full range of WCF behaviors and extensions your application likely depends on. Our migration path is WCF to gRPC for internal service-to-service communication (binary Protocol Buffers, HTTP/2 multiplexing, bidirectional streaming, code-generated strongly-typed clients) and WCF to REST for public-facing endpoints where consumer compatibility matters. We convert DataContract and ServiceContract definitions to .proto files, rebuild service implementations using the Grpc.AspNetCore server package, and generate typed clients that replace your existing ChannelFactory<T> and proxy class usage.
EF6 to EF Core is not a drop-in replacement. The two frameworks share a name and a general philosophy but differ in meaningful ways that affect application behavior. Lazy loading is opt-in rather than default in EF Core — code that relied on transparent lazy loading will throw null reference exceptions until navigation properties are explicitly included or lazy loading proxies are configured. The LINQ-to-SQL translation engine is completely different, and queries that worked in EF6 may translate to different SQL or fail to translate entirely in EF Core. GroupBy translation was incomplete until EF Core 7. Complex type mapping, table-per-hierarchy configuration, and many-to-many relationship conventions all changed. We migrate the data layer by running both EF6 and EF Core against the same database during the transition period, validating query output parity, and converting the model configuration from fluent API calls that use EF6 syntax to EF Core equivalents — including migration history so that EF Core's migration tooling can manage the schema going forward.
ASP.NET MVC 5 runs on System.Web, the IIS-coupled HTTP pipeline that does not exist in .NET 8. The migration replaces System.Web.Mvc with Microsoft.AspNetCore.Mvc, converts Global.asax application events to middleware components in the ASP.NET Core request pipeline, migrates Razor views from the legacy view engine to the ASP.NET Core Razor engine (which has a different tag helper syntax and different model binding behavior), replaces OWIN middleware with ASP.NET Core middleware, and converts authentication from FormsAuthentication or OWIN cookie authentication to ASP.NET Core Identity or the JWT bearer handler. Bundling and minification move from System.Web.Optimization to a build-time pipeline using Vite, webpack, or LibMan. Route configuration moves from RouteConfig.cs and attribute routing with System.Web.Mvc attributes to ASP.NET Core endpoint routing. If your application uses Web Forms pages mixed with MVC controllers — a common pattern in applications that were partially modernized from Web Forms to MVC — we handle that co-existence during the transition.
Legacy .NET Framework Windows Services that handle background processing — file watchers, queue consumers, scheduled batch jobs, integration polling — migrate to the .NET 8 BackgroundService base class running as hosted services inside the ASP.NET Core host. This eliminates the separate Windows Service project, the sc.exe installation step, and the Windows-only dependency. BackgroundService implementations run on Linux, deploy as containers, and integrate with the same dependency injection, configuration, and logging infrastructure as your web endpoints. For scheduled jobs, we replace Windows Task Scheduler triggers with Hangfire or Quartz.NET running inside the application host, which gives you a dashboard, retry logic, and job history that Windows Task Scheduler never provided. For critical processing that needs supervision, we configure systemd units on Linux or Kubernetes liveness probes to ensure automatic restart on failure.
Before migration begins, we audit every NuGet package in your solution against .NET 8 compatibility. Packages fall into four categories: fully compatible (published with .NET 8 target or .NET Standard 2.0), compatible with warnings (uses APIs that trigger platform compatibility analyzers), incompatible with replacement (the package dropped .NET Framework and you need to switch to the new version or an alternative), and incompatible with no replacement (niche packages that target .NET Framework-only APIs and require custom code to replace). Common replacements we handle: Microsoft.AspNet.WebApi.Client replaced by System.Net.Http.Json, Microsoft.Owin replaced by ASP.NET Core middleware, EntityFramework 6.x replaced by Microsoft.EntityFrameworkCore, System.Web.Mvc replaced by Microsoft.AspNetCore.Mvc, and dozens of packages that published breaking major versions that changed APIs alongside their .NET 8 support.
We had a 600K-line .NET Framework 4.6 monolith that seven developers maintained full-time. FreedomDev migrated it to .NET 8 over five months using the strangler pattern. We never had a minute of downtime. Our Azure bill dropped 40% from switching to Linux containers, and we shipped more features in the first quarter after migration than we had in the previous year on Framework.
We run the .NET Upgrade Assistant and ApiPort analyzer against your entire solution to generate a machine-readable compatibility report: which APIs have direct .NET 8 equivalents, which APIs have behavioral changes, and which APIs have no equivalent and require architectural decisions. We audit every NuGet package for .NET 8 compatibility. We map the dependency graph between projects in your solution to identify which modules can be extracted independently and which are tightly coupled. We document every WCF service contract, every Entity Framework 6 model, every Windows-specific API call, every System.Web dependency, and every COM interop reference. The deliverable is a migration plan with a prioritized module sequence, estimated effort per module, identified blockers that require architectural decisions, and a YARP routing strategy for the strangler fig transition.
We set up the parallel infrastructure that enables side-by-side execution. This includes: converting the solution to SDK-style project files, setting up a new .NET 8 solution structure alongside the existing .NET Framework projects, deploying the YARP reverse proxy with initial routing that sends all traffic to the legacy application, configuring the CI/CD pipeline to build and deploy both the legacy and modern applications, setting up shared authentication so that user sessions work across both backends, and configuring the database connection so both EF6 (legacy) and EF Core (modern) can operate against the same database concurrently. At this stage, the YARP proxy is live in production but routing 100% of traffic to the legacy backend — the infrastructure is proven before any migration code ships.
The foundation layers migrate first because every other module depends on them. We convert shared class libraries to multi-target projects (.NET Framework 4.8 and .NET 8) using conditional compilation where the APIs diverge. The data access layer is rebuilt on Entity Framework Core — model configuration, navigation properties, query translation differences, and migration history are all handled. We run EF6 and EF Core side-by-side against the same database and validate query output parity with automated comparison tests that execute the same business queries through both ORMs and diff the results. Domain models, DTOs, validation logic, and business rule libraries are ported to .NET Standard 2.0 where possible (consumed by both Framework and modern projects) or to .NET 8 where .NET Standard constraints are too limiting.
Controllers, API endpoints, and background services are migrated module by module. Each module follows the same cycle: extract the bounded context into a new .NET 8 project, rewrite controllers from System.Web.Mvc or System.Web.Http to ASP.NET Core, replace WCF service references with gRPC clients or REST HttpClient calls, convert authentication middleware, wire up dependency injection using the built-in container, write integration tests that validate behavioral parity with the legacy implementation, deploy to staging behind YARP, run parallel traffic comparison (shadow mode) to verify identical responses, then flip the YARP route to send production traffic to the new service. Each module goes through this cycle independently. If a module has issues in production, the YARP route reverts to the legacy backend in seconds.
With all API endpoints and services running on .NET 8, the UI layer migrates last. Razor views are converted from the legacy view engine to ASP.NET Core Razor Pages or Blazor Server depending on the interactivity requirements. Bundling and minification pipelines are replaced with modern build tooling. JavaScript integrations are validated against the new backend. Once the UI layer is fully migrated and validated in production, the legacy .NET Framework application is decommissioned — all YARP routes now point to .NET 8 services, the IIS application pool is shut down, and the Windows Server instance can be replaced with Linux containers. Post-migration, we configure production monitoring with OpenTelemetry, set up health check endpoints for container orchestrators, and provide 30 days of hypercare support to catch any edge cases that surface under full production traffic.
| Metric | With FreedomDev | Without |
|---|---|---|
| Hosting | .NET 8: Linux containers, any cloud, $5/mo VPS to Kubernetes | .NET Framework: Windows Server + IIS only ($1K-$6K/yr licensing) |
| Language Version | C# 12 — primary constructors, collection expressions, interceptors | C# 7.3 maximum — no pattern matching enhancements, no records, no top-level statements |
| Throughput (TechEmpower) | ASP.NET Core: 7M+ plaintext req/sec, top-tier composite score | ASP.NET Framework: 200K-500K req/sec on equivalent hardware |
| Cold Start (Serverless) | .NET 8 Native AOT: <100ms cold start on AWS Lambda | .NET Framework: not supported on Lambda/Functions at all |
| NuGet Ecosystem | Full access to current packages, source generators, analyzers | Eroding — Polly v8, MediatR 12+, many libraries dropped Framework support |
| Serialization | System.Text.Json with source generation — zero reflection, AOT-safe | Newtonsoft.Json — reflection-based, slower, cannot be AOT-compiled |
| GC and Memory | Regions-based GC, Span<T>, Memory<T>, array pooling, zero-copy parsing | Gen0/1/2 GC only, excessive heap allocations, no Span<T> without polyfills |
| Developer Talent Pool | Majority of .NET developers in 2026 build on .NET 8 + Linux | Shrinking pool of Framework specialists, higher rates, retention risk |
Schedule a direct technical consultation with our senior architects.
Make your software work for you. Let's build a sensible solution.