PRD: .NET 5 → .NET 8 Cloud-Native Migration¶
Workshop Use: Practice exercise for Module 2 — Legacy Codebase Modernization. Demonstrates agy strict mode, agent self-onboarding with AGENTS.md, subagent planning, and
ctrl+gplan editing. The target repository includes a referencemodernization-prompt.mdauthored by GCP Cloud Solutions Architects — a gold-standard example of prompt engineering for migration tasks.
Problem¶
A partially-upgraded ASP.NET application (ContosoUniversity) runs on .NET 5 with Entity Framework 6 and the legacy Generic Host pattern (Startup.cs + Program.cs). .NET 5 reached end of support in May 2022. The app uses SQL Server-flavored EF6 but needs to target Cloud Run with PostgreSQL. It lacks containerization, structured logging, and graceful shutdown handling.
Business Drivers¶
| Driver | Impact |
|---|---|
| Security compliance | .NET 5 is EOL — no security patches. Blocks compliance certification. |
| Deployment speed | From manual VM deployment to 3-minute container push on Cloud Run |
| Scalability | Cloud Run auto-scales from 0 to N instances based on traffic |
| Cost | Eliminate Windows Server licensing — Linux containers on Cloud Run cost ~70% less |
| Data layer | Migrate from EF6/SQL Server to EF Core 8/PostgreSQL for managed Cloud SQL |
Target Repository¶
ContosoUniversity — Google Cloud .NET Modernization Demo
The repo provides both the legacy app (dotnet-migration-sample/) and the completed target state (dotnet-migration-sample-modernized/) for self-verification. Students work exclusively in the dotnet-migration-sample/ directory.
git clone --depth 1 https://github.com/GoogleCloudPlatform/cloud-solutions.git
cd cloud-solutions/projects/dotnet-modernization-demo/dotnet-migration-sample
Bonus resource: The repo includes
modernization-prompt.md— a 225-line production-grade migration prompt from the GCP team. Compare your agent's approach with this reference to study prompt engineering for migration tasks.
Scope¶
In Scope¶
- Upgrade target framework from .NET 5 to .NET 8
- Replace Generic Host pattern (
Startup.cs) with minimal hosting API (WebApplication.CreateBuilder()) - Migrate Entity Framework 6 to Entity Framework Core 8
- Switch database provider from SQL Server to PostgreSQL (Npgsql)
- Create a Cloud Run-compliant Dockerfile and Docker Compose configuration
- Implement structured logging, PORT binding, and SIGTERM graceful shutdown
Out of Scope¶
- Business logic changes (faithful 1:1 feature replication)
- Frontend redesign (Razor views stay as-is, just ensure they compile)
- Database schema changes (EF Core should map to equivalent schema)
Migration Checklist¶
Phase 0: Context Engineering — Agent Self-Onboarding¶
Before writing migration code, the agent should build its own understanding of the codebase.
- [ ] Set strict mode — prevent accidental file writes during investigation:
- [ ] Investigate the codebase:
Analyze the ContosoUniversity application. Map:
1. Current framework version and all NuGet dependencies
2. The Startup.cs/Program.cs hosting pattern
3. All System.Data.Entity (EF6) usage across DAL, controllers, and migrations
4. Configuration sources (appsettings.json, web.config remnants)
5. Google Cloud integrations (Diagnostics, logging)
- [ ] Generate a migration-aware AGENTS.md based on the analysis
- [ ] Review and approve the generated AGENTS.md before proceeding
- [ ] Switch to request-review mode before Phase 1:
Phase 1: TFM and Package Upgrade¶
- [ ] Update
ContosoUniversity.csproj: change<TargetFramework>net5.0</TargetFramework>→net8.0 - [ ] Update all NuGet packages to .NET 8-compatible versions:
Microsoft.AspNetCore.Mvc.NewtonsoftJson→ 8.0.xSystem.ComponentModel.Annotations→ 8.0.xSystem.Configuration.ConfigurationManager→ 8.0.xGoogle.Cloud.Diagnostics.AspNetCore→ latest 8.0-compatible- [ ] Remove the
Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzerspackage (no longer needed) - [ ] Remove
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>and deleteProperties/AssemblyInfo.cs - [ ] Run
dotnet restore— resolve any package conflicts
Phase 2: Hosting Modernization¶
- [ ] Replace
Startup.cs+Program.cs(Generic Host pattern) with minimal hosting API:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// ... service registration
var app = builder.Build();
// ... middleware pipeline
app.Run();
- [ ] Move
ConfigureServices()body into the top-levelbuilder.Servicesblock - [ ] Move
Configure()body into the top-levelapp.Use*()pipeline - [ ] Migrate
CreateHostBuildersecret config loading to the new pattern - [ ] Configure PORT binding for Cloud Run:
app.Run("http://0.0.0.0:" + port) - [ ] Add SIGTERM graceful shutdown handler
- [ ] Delete
Startup.csafter migration is complete
Phase 3: Entity Framework 6 → EF Core 8¶
- [ ] Remove
EntityFramework6.4.4 package - [ ] Add
Microsoft.EntityFrameworkCoreandNpgsql.EntityFrameworkCore.PostgreSQL8.0.x - [ ] Refactor
SchoolContext: - Replace
System.Data.Entityimports →Microsoft.EntityFrameworkCore - Replace
DbModelBuilder→ModelBuilderinOnModelCreating - Remove
PluralizingTableNameConvention→ add explicit.ToTable()calls - Remove
MapToStoredProcedures()(not supported in EF Core) - Replace constructor
SchoolContext(string connectString) : base(connectString)→SchoolContext(DbContextOptions<SchoolContext> options) : base(options) - [ ] Register DbContext in DI:
builder.Services.AddDbContext<SchoolContext>(options => options.UseNpgsql(...)) - [ ] Refactor
SchoolInitializer.cs→DbInitializer.csusingcontext.Database.EnsureCreated() - [ ] Remove
SchoolConfiguration.cs(Database.SetInitializer<T>()is EF6-only) - [ ] Remove
SchoolInterceptorLogging.csandSchoolInterceptorTransientErrors.cs(EF6 interceptors) → replace with EF Core logging viaILoggerFactory - [ ] Delete all EF6 migration files (
Migrations/2014*.cs,Migrations/Configuration.cs) - [ ] Update all controllers to remove
System.Data.Entityimports and fixRetryLimitExceededException(EF6) → EF Core equivalent - [ ] Run
dotnet build— fix all compilation errors
Phase 4: Containerization & Cloud Run¶
- [ ] Create multi-stage
Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
USER 1000
EXPOSE 8080
ENTRYPOINT ["dotnet", "ContosoUniversity.dll"]
- [ ] Add
.dockerignore(excludebin/,obj/,.git/) - [ ] Create
compose.yamlwith PostgreSQL + app services: - PostgreSQL container with
pg_isreadyhealthcheck - App container with connection string via environment variable
- No
versionattribute (deprecated in Compose Spec) - [ ] Configure structured JSON logging for Cloud Logging:
builder.Logging.AddJsonConsole() - [ ] Run
docker build --check .— pass all Docker build checks - [ ] Run
docker compose up --build --detach— verify the app starts and database initializes
Phase 5: Validation & Testing¶
- [ ]
dotnet buildcompiles without errors or warnings - [ ] Docker image builds and runs on Linux (not Windows containers)
- [ ] Container runs as non-root user (UID 1000)
- [ ] All HTTP endpoints respond correctly (GET, POST for CRUD operations)
- [ ] Database is initialized with seed data on first run
- [ ] Application handles anti-forgery tokens correctly for POST/DELETE operations
- [ ] Structured JSON logs appear in
docker compose logs - [ ] No connection strings or credentials in source code
What the Agent Should Do¶
This PRD is designed to test the agent's ability to:
- Bootstrap its own context — use strict mode + codebase investigation to write an AGENTS.md before starting (Phase 0)
- Understand legacy patterns — recognize EF6 idioms (
DbModelBuilder, interceptors,Database.SetInitializer) and map them to EF Core equivalents - Follow a phased plan — use
ctrl+gto edit and approve the plan before executing - Perform mechanical refactoring — the EF6 → EF Core migration touches every controller and model file
- Verify its own work — run
dotnet buildanddocker compose upafter each phase - Use
/rewindif a phase goes wrong — this is your checkpoint, not a failure
Acceptance Criteria¶
- [ ] An
AGENTS.mdexists in the project root encoding the migration context - [ ]
dotnet buildcompiles without errors on .NET 8 - [ ] No
System.Data.Entityimports remain anywhere in the codebase - [ ] No
Startup.cs— app uses minimal hosting API inProgram.cs - [ ]
EntityFramework6.x package fully replaced byMicrosoft.EntityFrameworkCore8.x - [ ] Docker image builds and runs on Linux with non-root user
- [ ]
docker compose upsuccessfully starts app + PostgreSQL and seeds the database - [ ] No connection strings or credentials in source code
- [ ] Application responds to health check within 2 seconds of cold start