Migrating from .NET Framework to .NET 10: An AI-Assisted Approach with Claude Code
Migrating from .NET Framework 4.x to .NET 10 is the most common modernisation path for enterprise .NET applications. This guide walks through the process step by step: setting up the harness before the first prompt, running the Upgrade Assistant for project conversion, using Claude Code for code-level migration with hooks for automated feedback, and running parallel agents for large solutions.
- Set up the harness first:
Directory.Build.props,.claude/settings.jsonwith pre-approved commands, and aPostToolUsehook runningdotnet buildafter every file edit. - Use the .NET Upgrade Assistant for project file conversion. Use Claude Code for the code-level migration.
- Pre-approve the migration command set before starting. Without it, prompt fatigue breaks the agent’s flow.
- Migrate project by project, starting with shared libraries. Each project should compile and pass tests before moving on.
- Parallel agents with
--worktreecompress timelines significantly: one developer can supervise three or four concurrent migrations. - Budget 30-50% of your time for human review, architectural decisions, and edge cases.
You have a .NET Framework 4.x solution with 15 projects. Where do you start?
The worst approach is opening every .csproj file and changing target frameworks. That produces a broken solution, hundreds of compiler errors, and no way to separate migration issues from pre-existing bugs.
The right approach is systematic: set up the harness, analyse first, migrate in dependency order, test at each step, and use AI tooling for the mechanical work while reserving human attention for the decisions that matter.
This guide assumes a typical enterprise .NET Framework 4.x application: ASP.NET MVC or Web API, Entity Framework 6, some class libraries, a test project or two, and possibly WCF services. If your application uses Web Forms, see the .NET modernisation playbook for that path.
Phase 1: Set up the harness and analyse
Set up the harness before the first migration prompt
The single most important step before writing any migration prompt is configuring the feedback loop. Without it, build errors accumulate silently and the agent runs ahead of problems you cannot see.
Directory.Build.props at the solution root:
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
</Project>
With TreatWarningsAsErrors, the PostToolUse hook’s dotnet build fails on any warning the agent introduces. The agent reads the failure and self-corrects before moving to the next file.
.claude/settings.json with the migration command set pre-approved:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(dotnet build*)",
"Bash(dotnet test*)",
"Bash(dotnet format*)",
"Bash(dotnet restore*)",
"Bash(dotnet clean*)",
"Bash(dotnet sln list*)",
"Bash(dotnet list package*)",
"Bash(dotnet ef migrations*)",
"Bash(dotnet ef database update*)",
"Bash(dotnet ef dbcontext*)",
"Bash(dotnet tool*)",
"Bash(upgrade-assistant*)",
"Bash(git status)",
"Bash(git diff*)",
"Bash(git log*)",
"Bash(git branch*)",
"Read(**/*)"
],
"ask": [
"Bash(dotnet sln*)",
"Bash(dotnet add package*)",
"Bash(dotnet remove package*)",
"Bash(dotnet new*)"
],
"deny": [
"Bash(rm -rf bin*)",
"Bash(rm -rf obj*)",
"Bash(rm -rf .git*)",
"Bash(dotnet ef database drop*)",
"Bash(dotnet nuget add source*)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(curl*)",
"Bash(wget*)",
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/appsettings.Production.json)",
"Read(**/secrets.json)"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"if": "Edit(*.cs)|Write(*.cs)",
"command": "bash",
"args": ["-c", "FILE=$(echo '$INPUT' | jq -r '.tool_input.file_path // .tool_input.path // empty'); DIR=$(dirname \"$FILE\"); PROJ=$(find \"$DIR\" -maxdepth 4 -name '*.csproj' -print -quit 2>/dev/null); if [ -n \"$PROJ\" ]; then dotnet format \"$PROJ\" --include \"$FILE\" --no-restore 2>&1; dotnet build \"$PROJ\" --no-restore -v minimal 2>&1; fi"],
"timeout": 30000
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash",
"args": ["-c", "echo '=== .NET Environment ==='; dotnet --version; echo; echo '=== Solution ==='; dotnet sln list 2>/dev/null; echo; echo '=== Git status ==='; git status -sb 2>/dev/null"]
}
]
}
]
},
"env": {
"DOTNET_NOLOGO": "1",
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
}
Commit this file. The whole team shares the approved command set and the hooks.
Migration sessions hammer dotnet build, dotnet test, dotnet format, dotnet sln, dotnet add package, and upgrade-assistant repeatedly. Without the allow list, every one of those is a prompt. With it, the agent works uninterrupted.
The deny block prevents the agent running dotnet ef database drop (even with --force), adding NuGet sources, or touching production configuration files. These are the commands that cause data loss or security exposure if the agent makes a mistake.
For the full command catalogue, advanced hooks, and autoMode prose rules, see the .NET configuration guide.
Set up AGENTS.md and CLAUDE.md
AGENTS.md at the repository root carries the migration rules. This is the portable instruction surface: it works in Cursor, Claude Code, and any other tool that follows the Agent Skills standard.
# Project: OrderManagement Migration
## Target
- Source: .NET Framework 4.8
- Target: .NET 10 (LTS)
- Hosting: Azure Container Apps
- CI: GitHub Actions
## Conventions
- File-scoped namespaces
- Primary constructors for services with injected dependencies
- ILogger<T> (replace log4net calls)
- IOptions<T> pattern for configuration sections
- TimeProvider (injected) for all date/time operations (not DateTime.Now or DateTime.UtcNow)
- Nullable reference types: enabled
- Implicit usings: enabled
- IExceptionHandler for cross-cutting error handling
## API migration rules
- HttpWebRequest / WebClient -> HttpClient via IHttpClientFactory
- ConfigurationManager.AppSettings -> IConfiguration
- ConfigurationManager.ConnectionStrings -> IConfiguration.GetConnectionString()
- Thread.Sleep -> await Task.Delay
- Global.asax -> Program.cs with WebApplicationBuilder
- web.config appSettings -> appsettings.json
- web.config transforms -> appsettings.{Environment}.json
- System.Web.HttpContext.Current -> inject IHttpContextAccessor
- FormsAuthentication -> ASP.NET Core Identity or JWT
- DateTime.Now / DateTime.UtcNow -> TimeProvider.GetUtcNow()
## Data access
- EF6 DbContext -> EF Core DbContext
- Keep all table names and column names identical
- Fluent API configuration only (no data annotations)
- Lazy loading: OFF (use explicit .Include())
- ObjectContext: not available in EF Core, rewrite using DbContext
## Testing
- MSTest -> xUnit
- Moq -> NSubstitute
- FluentAssertions for all assertions
- Test class naming: {ClassUnderTest}Tests
- Test method naming: {Method}_{Scenario}_{ExpectedResult}
## Do not
- Change any business logic during migration
- Rename public API methods or parameters
- Remove or modify XML doc comments
- Add async to synchronous methods unless required for API compatibility
- Change database schema
- Use lazy loading in EF Core
Keep this file to the point. The agent reads it at the start of each session. Long files are applied less reliably under context pressure.
Note on Cursor users: if the team also uses Cursor, create .cursor/rules/migration.mdc with globs: "**/*.cs" containing the migration-specific rules. The portable AGENTS.md carries them in Claude Code; the Cursor rule file carries them in the IDE.
Run the .NET Upgrade Assistant
Install and run the Upgrade Assistant to get a baseline analysis:
dotnet tool install -g upgrade-assistant
upgrade-assistant analyze <path-to-solution.sln>
The analysis tells you which projects can migrate directly, which NuGet packages have .NET 10 equivalents, and which APIs are unavailable.
Do not use the Upgrade Assistant to perform the actual code migration yet. Its automated migration produces code that compiles but does not follow modern .NET 10 patterns. Use it for analysis and project file conversion, then use Claude Code for code-level migration.
Map the dependency graph
Before migrating any code, understand the dependency order:
Shared libraries (no project references)
↓
Domain / business logic projects
↓
Data access projects (EF context, repositories)
↓
Service layer projects
↓
Web / API projects (ASP.NET MVC, Web API)
↓
Test projects
Migrate bottom-up. Each project should compile and pass tests before you move on. This prevents cascading errors and makes it clear which failures are migration-caused versus pre-existing.
Phase 2: Project-by-project migration
Step 1: Project file conversion
Use the Upgrade Assistant to convert packages.config and old-format .csproj files:
upgrade-assistant upgrade <path-to-project.csproj> --target-tfm net10.0
This handles:
- Converting to SDK-style
.csproj - Updating the target framework moniker to
net10.0 - Migrating
packages.configto<PackageReference>elements - Removing references to
System.Weband other framework assemblies
Review the converted .csproj carefully. The Upgrade Assistant sometimes adds unnecessary package references or misses transitive dependencies.
Step 2: Use a worktree per project
Give each migrated project its own branch with --worktree:
claude --worktree migrate-domain "Invoke /migrate-dotnet-project on src/OrderManagement.Domain/"
The original working tree stays buildable while the migration runs. Review the diff when the agent reports back, then merge the branch if it is clean.
For a 15-project solution, start the next leaf in the dependency tree the moment its predecessor is merged. One developer can supervise three or four parallel migrations at once.
Step 3: Use the migration Skill
Use the /migrate-dotnet-project skill rather than a free-form prompt. The skill codifies the analyse-then-execute loop with the rules from AGENTS.md, making the workflow reproducible across every project.
> Invoke /migrate-dotnet-project on src/OrderManagement.Domain/
The skill produces a migration plan, waits for confirmation, then migrates file by file with a build check after each file. See the .NET configuration guide for the skill definition.
For teams without the skill set up, the equivalent free-form prompt:
> Read the project at src/OrderManagement.Domain/ and identify all .NET Framework APIs
> that need migration to .NET 10. Follow the rules in AGENTS.md. List each API, its
> location, and the recommended replacement. Wait for confirmation, then migrate one
> file at a time and show me the dotnet build output after each file.
Step 4: Common migration patterns
Configuration access:
// BEFORE: .NET Framework
var connectionString = ConfigurationManager
.ConnectionStrings["OrderDb"].ConnectionString;
var maxRetries = int.Parse(
ConfigurationManager.AppSettings["MaxRetries"]);
// AFTER: .NET 10 with IOptions<T>
public class OrderService(
IConfiguration configuration,
IOptions<RetryOptions> retryOptions)
{
private readonly string _connectionString =
configuration.GetConnectionString("OrderDb")
?? throw new InvalidOperationException(
"OrderDb connection string not configured");
private readonly int _maxRetries = retryOptions.Value.MaxRetries;
}
HTTP client:
// BEFORE: .NET Framework
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
using var response = (HttpWebResponse)request.GetResponse();
using var reader = new StreamReader(response.GetResponseStream());
var json = reader.ReadToEnd();
// AFTER: .NET 10 with IHttpClientFactory
public class ExternalApiClient(IHttpClientFactory httpClientFactory)
{
public async Task<string> GetAsync(
string url,
CancellationToken cancellationToken = default)
{
using var client = httpClientFactory.CreateClient("ExternalApi");
var response = await client.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken);
}
}
DateTime to TimeProvider:
// BEFORE: .NET Framework
public class OrderService
{
public Order CreateOrder(int customerId)
{
return new Order
{
CustomerId = customerId,
CreatedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddDays(30)
};
}
}
// AFTER: .NET 10 with TimeProvider (testable)
public class OrderService(TimeProvider timeProvider)
{
public Order CreateOrder(int customerId)
{
var now = timeProvider.GetUtcNow().UtcDateTime;
return new Order
{
CustomerId = customerId,
CreatedAt = now,
ExpiresAt = now.AddDays(30)
};
}
}
Injecting TimeProvider makes time-dependent code fully testable with FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing.
Step 5: Dependency injection registration
// BEFORE: Autofac registration in Global.asax
var builder = new ContainerBuilder();
builder.RegisterType<OrderRepository>().As<IOrderRepository>();
builder.RegisterType<OrderService>().As<IOrderService>();
var container = builder.Build();
DependencyResolver.SetResolver(
new AutofacDependencyResolver(container));
// AFTER: .NET 10 built-in DI in Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
Claude Code handles this reliably when AGENTS.md specifies the DI convention. The decision for humans: whether to stay with the third-party container (Autofac has a .NET 10 integration) or migrate to built-in DI. For most applications, built-in DI is sufficient and removes a dependency.
Step 6: Configuration migration
// appsettings.json (replaces web.config appSettings)
{
"RetryOptions": {
"MaxRetries": 3
},
"ExternalApi": {
"BaseUrl": "https://api.example.com"
}
}
// Strongly-typed options class
public class RetryOptions
{
public int MaxRetries { get; init; } = 3;
}
// Registration in Program.cs
builder.Services.Configure<RetryOptions>(
builder.Configuration.GetSection("RetryOptions"));
Phase 3: Testing and verification
Migrate the test projects
Test project migration follows the same pattern. MSTest to xUnit:
// BEFORE: MSTest
[TestClass]
public class OrderServiceTests
{
[TestMethod]
public void CalculateTotal_WithDiscount_AppliesDiscount()
{
var service = new OrderService();
var result = service.CalculateTotal(100m, 0.1m);
Assert.AreEqual(90m, result);
}
}
// AFTER: xUnit with FluentAssertions
public class OrderServiceTests
{
[Fact]
public void CalculateTotal_WithDiscount_AppliesDiscount()
{
var service = new OrderService(TimeProvider.System);
var result = service.CalculateTotal(100m, 0.1m);
result.Should().Be(90m);
}
}
MSTest [TestInitialize] and [TestCleanup] become constructor and IDisposable.Dispose() in xUnit. This changes the lifecycle model. Claude Code knows the mapping, but review the converted tests to ensure setup and teardown order is correct.
Lock module boundaries with ArchUnitNET
After migration, add ArchUnitNET tests to enforce the module boundaries you expect. The agent cannot quietly leak a domain entity into an infrastructure project if a test fails when it tries.
public class ArchitectureTests
{
private static readonly Architecture Architecture =
new ArchLoader()
.LoadAssembliesInDirectory(
new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory),
"OrderManagement.*.dll")
.Build();
[Fact]
public void Domain_Should_Not_Reference_Infrastructure()
{
var domain = Slices().MatchingAssemblies("OrderManagement.Domain*");
var infrastructure = Slices().MatchingAssemblies("OrderManagement.Infrastructure*");
domain.Should().NotDependOnAny(infrastructure).Because(
"the domain layer must not reference infrastructure");
}
}
These tests catch regression immediately. The agent cannot break the module boundary without a test failure.
Verify test quality with Stryker.NET
After migration, run Stryker.NET to confirm the migrated tests still catch what they should:
dotnet tool install -g dotnet-stryker
dotnet stryker --project src/OrderManagement.Application/OrderManagement.Application.csproj
A mutation score below 70% on a migrated project suggests the tests were passing but not actually covering the logic. Address this before moving on.
Add a test-run hook
Add a PostToolUse hook to .claude/settings.json that runs the test project after each test file edit:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"if": "Edit(*Tests.cs)|Write(*Tests.cs)",
"command": "bash",
"args": ["-c", "PROJ=$(find . -maxdepth 5 -name '*.Tests.csproj' | head -1); if [ -n \"$PROJ\" ]; then dotnet test \"$PROJ\" --no-restore -v minimal 2>&1; fi"],
"timeout": 60000
}
]
}
]
}
}
Categorise test failures
After migrating all projects, run the full test suite:
dotnet test --no-restore -v minimal
Categorise failures:
- Migration errors: API mismatches, missing using statements. Claude Code can fix these.
- Behavioural differences: logic that behaves differently on .NET 10 (EF Core lazy loading, string comparison differences). These need human investigation.
- Pre-existing failures: tests that were already failing before migration. Track them separately. Do not fix them during migration.
Phase 4: Program.cs and startup
Replace Global.asax, Startup.cs, and web.config with the .NET 10 Program.cs entry point:
var builder = WebApplication.CreateBuilder(args);
// Configuration
builder.Services.Configure<RetryOptions>(
builder.Configuration.GetSection("RetryOptions"));
// Data access
builder.Services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("OrderDb")));
// Services
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();
// Time provider (enables testable date/time throughout the application)
builder.Services.AddSingleton(TimeProvider.System);
// HTTP clients
builder.Services.AddHttpClient("ExternalApi", client =>
{
client.BaseAddress = new Uri(
builder.Configuration["ExternalApi:BaseUrl"]
?? throw new InvalidOperationException(
"ExternalApi:BaseUrl not configured"));
});
// Error handling
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
// MVC
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseExceptionHandler();
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
app.Run();
The middleware order matters. Authentication before authorisation; CORS before routing in API projects. Getting it wrong causes subtle bugs.
The IExceptionHandler pattern (registered via AddExceptionHandler<T> and UseExceptionHandler()) replaces custom error middleware and exception filter approaches from .NET Framework. Claude Code generates this when AGENTS.md specifies it.
What to watch for
String comparison behaviour. .NET 10 defaults to ordinal string comparison in some contexts where .NET Framework used culture-sensitive comparison. This can cause subtle sorting and equality bugs. If your application depends on culture-sensitive string behaviour, add explicit StringComparison parameters.
DateTime handling. DateTime.Now behaves identically, but migrate to TimeProvider during the migration rather than after. The behaviour change is a bonus: injected TimeProvider makes time-dependent code fully testable.
IHttpClientFactory and socket exhaustion. Direct new HttpClient() instantiation causes socket exhaustion under load in .NET Framework applications. The migration is the right time to fix this: IHttpClientFactory registration in Program.cs and injected factory in all classes. Claude Code handles this well; the deny rule on curl* in the permissions file reinforces the pattern.
System.Text.Json versus Newtonsoft.Json. .NET 10’s default serialiser (System.Text.Json) handles dates, polymorphic types, and missing properties differently from Newtonsoft.Json. Either configure System.Text.Json to match your existing wire format, keep Newtonsoft.Json during migration, or accept the breaking change explicitly. For polymorphic types, [JsonPolymorphic] and [JsonDerivedType] replace Newtonsoft’s TypeNameHandling.Auto.
Assembly loading. .NET Framework’s AppDomain and assembly loading model is different from .NET 10’s. If your application loads plugins or assemblies dynamically, this requires manual redesign.
Windows-specific APIs. Registry access, Windows services, and COM interop require the Microsoft.Windows.Compatibility NuGet package on .NET 10. Claude Code adds this when it encounters Windows-specific APIs, but verify the package is present after migration.
Running migration with cloud agents
Parallel agents for large solutions
With the bottom-up dependency order from Phase 1, the moment a project is migrated and green, the next leaf can be picked up by a parallel agent in its own worktree:
# After Domain is merged, start Infrastructure and Application in parallel
claude --worktree migrate-infrastructure \
"Invoke /migrate-dotnet-project on src/OrderManagement.Infrastructure/. The Domain project is already migrated."
claude --worktree migrate-application \
"Invoke /migrate-dotnet-project on src/OrderManagement.Application/. Domain is migrated."
One developer can supervise three or four parallel migrations. The job shifts from writing migration code to reviewing agent output and merging clean branches.
Practical note: each worktree runs its own dotnet restore. Set NUGET_PACKAGES in settings.json to a shared path to warm the cache once and avoid the overhead multiplying.
Managed Agents environments for overnight runs
For large solutions (150k+ lines), provision a cloud container via Anthropic’s Managed Agents API:
{
"name": "dotnet10-migration",
"config": {
"type": "cloud",
"packages": {
"apt": ["dotnet-sdk-10.0", "git", "jq"]
},
"networking": {
"type": "limited",
"allowed_hosts": [
"api.nuget.org",
"api.anthropic.com"
],
"allow_package_managers": true
}
}
}
Post-install the global tools in the agent’s first instruction:
dotnet tool install -g upgrade-assistant && dotnet tool install -g dotnet-ef && dotnet restore
Start a migration session before leaving the office. Review the resulting branches at your desk, or from your phone via the web UI at claude.ai/code.
This requires the managed-agents-2026-04-01 beta header today. The Anthropic SDK sets it automatically.
Exploring on the go
The web UI at claude.ai/code and the desktop app share the same account. Useful for migration planning:
- Kick off a read-only analysis from your phone: “Analyse src/OrderManagement.Services/ and estimate the migration effort. List every .NET Framework API, its location, and the replacement.”
- The agent writes a plan to
.claude/migration-plan-services.md. - Review the plan at your desk, add notes in the chat, and the next terminal session picks up where you left off.
In practice
Three representative examples from TTD engagements, anonymised.
Professional services firm. 150k-line .NET Framework 4.7.2 MVC application with EF6 and Autofac. Migrated to .NET 10 in seven weeks. Claude Code handled approximately 65% of code changes. The biggest challenge was EF6 to EF Core: the application used lazy loading extensively across 40+ navigation properties. Behavioural differences caused 23 test failures that required manual investigation. ArchUnitNET tests were added mid-migration to lock the layer boundaries and prevent regression as subsequent projects were migrated. Total test pass rate after migration: 98.5%.
Logistics company. 85k-line .NET Framework 4.8 Web API with heavy HttpWebRequest usage across 40 integration classes. Migrated to .NET 10 in four weeks. The migration rules in AGENTS.md for HttpWebRequest -> IHttpClientFactory were applied consistently across every integration class without human intervention. The PostToolUse hook’s build check caught four type mismatches during migration that would otherwise have appeared as a batch of failures at the end. Total AI-authored code in this project: approximately 70%.
Energy sector. 220k lines across 18 projects, a mix of .NET Framework 4.6 and 4.7.2. Required a phased approach: shared libraries and domain projects first (weeks one to three), data access layer (weeks four and five), service and API projects (weeks six to nine), test migration and verification (weeks ten and eleven). Parallel agents with --worktree compressed the service layer phase from four weeks to two. /compact was essential for maintaining context across the large solution. Total AI-authored code across the migration: approximately 70%. Delivery was 40-50% faster than a fully manual migration at comparable scale.
In our Q1 2026 AI Velocity Report, 84% of production code is AI-authored and delivery is measurably 40-50% faster. The patterns in this guide, including the harness setup before the first prompt, are how those numbers are achieved.
Verify the numbers
For the full stack, methodology, and tools behind the Q1 2026 delivery metrics, see the Q1 2026 AI Velocity Report.
Next steps
For the full configuration reference covering the complete command catalogue, advanced hooks, MCP servers, skills, subagents, cloud environments, and CI integration, see the Claude Code .NET configuration guide.
For the getting-started guide covering installation, CLAUDE.md setup, and IDE integration, see Claude Code for .NET Developers.
If you are planning a migration, start with an assessment. The Legacy .NET Assessment Framework provides a scoring model to evaluate modernisation readiness before committing resources.
For the broader strategic context, including when modernisation is not the right answer, see the .NET modernisation playbook.
For WCF-to-gRPC migration, see the WCF to gRPC migration guide.
For the harness engineering framing behind the configuration choices in this guide, see Harness Engineering for Coding Agents.
For model-driven templates that make migration patterns structurally impossible to violate, see Harness Templates for AI Coding Agents.
Ready to plan your migration? Book a free Legacy .NET Assessment consultation.
Frequently asked questions
Can Claude Code migrate an entire .NET Framework solution automatically?
Should I use the .NET Upgrade Assistant or Claude Code?
How do hooks help during migration?
Can I run the migration in a Claude Code cloud environment?
What about ASP.NET Web Forms migration?
How do I handle EF6 to EF Core migration?
What if my application uses WCF?
How does this compare with using the .NET Upgrade Assistant on its own?
Related guides
Legacy .NET Application Assessment: A Scoring Framework for Modernisation Readiness
A practical scoring framework to assess legacy .NET applications for modernisation readiness. Score across six dimensions and prioritise your migration programme.
Modernising Legacy .NET Applications with AI: The Enterprise Playbook (UK, 2026)
A structured approach to modernising legacy .NET Framework applications using AI-assisted development. Four pillars, real migration patterns, and practical guidance for enterprise teams.
Xamarin & PhoneGap Are Dead: The Enterprise Mobile Migration Playbook (2026)
Both Xamarin and PhoneGap have reached end of life. This playbook covers migration paths, framework choices, and practical timelines for enterprise mobile teams.