Where We Left Off
Back in March I wrote about migrating a Classic ASP (VBScript) application to ASP.NET Core 10 — how to get the legacy codebase running in Visual Studio 2026, how to debug it, and a page-by-page migration strategy. This is the same project, and this post is how it ended.
That post described the traditional way to do this work. This one is about what actually happened when I did it with Claude Code as my pair programmer — and why I ended up breaking some of my own advice.
The mapping and assessment work from the first post came first. The two weeks in this post cover the rewrite itself: from an empty solution to a production release with the historical data migrated.
The short version: a rewrite that would realistically take 2–3 developers a couple of months was delivered by one developer in under two weeks — deployed to production, with historical data migrated, and with significantly more functionality than the application it replaced.
The Starting Point
The application is an internal Quality Control system used by manufacturing plants to generate and assign product serial numbers. It was built around 2007 in Classic ASP and had all the problems you'd expect from a system of that age:
- A separate copy of the application per plant. Every plant had its own hand-cloned set of pages and its own duplicated tables. A bug fix had to be applied several times, and the copies had drifted apart.
- Hard-coded serial formats. Prefixes, suffixes, lengths, and counters lived in a mix of tables and inline VBScript logic.
- Inline SQL built by string concatenation, few permission checks, and plain-text credentials in the code.
- English-only, desktop-only UI — fixed-width tables, frames, and charts built on Adobe Flash that no longer run in any modern browser.
- Years of historical serial data that could not be lost, and plants that could not stop working during the switch.
My original estimate, following the approach in my previous post, was a couple of months just to reach feature parity. With a traditional team, you'd assign 2–3 developers to cover the database, back end, and front end in parallel.
Changing the Plan
In the first post I recommended a conservative approach: migrate page by page, keep the existing UI, and redesign later. That advice assumes rewriting is expensive, so you limit the scope of each change.
With Claude Code, the cost of doing it properly dropped enough that the conservative plan stopped making sense. Instead of porting the per-plant pages one at a time, I:
- Redesigned the data model instead of mapping old tables one-to-one — the duplicated per-plant tables became a single normalized model.
- Redesigned the UI instead of cloning the old table-and-frames layout, since the old one couldn't run on phones and its Flash charts were already broken.
- Built on ASP.NET Core MVC with Dapper and stored procedures rather than the Razor Pages and EF Core combination I'd sketched in the first post — a better fit for a data layer built around stored procedures.
What carried over from the first post are the parts that matter most: understand every legacy page before replacing it, verify the new system against the same data as the old one, and fix every security issue instead of porting it.
What Was Actually Delivered
Instead of matching the old system, the new ASP.NET Core 10 application went well beyond it:
- Multiple manufacturers and factories in one system. The per-plant copies were replaced by a normalized manufacturer → factory → product family → item model. A new plant is now configuration, not a code fork.
- Configurable serial number templates and rules. Prefixes, suffixes, lengths, counters, and per-item/per-family formats are managed by admins in the UI instead of being baked into code. Serial generation is transaction-protected, so the old duplicate-serial race condition is gone.
- Spanish translations for the Mexico plants. Full English/Spanish localization through resource files, cookie-based culture switching, and localized validation messages.
- Time zone localization. Plants are in different time zones, so every timestamp is stored in UTC and shown in the viewer's local time — the audit trail finally means the same thing everywhere.
- A mobile PWA for the shop floor. Operators can update serial-number status from a phone or tablet, even without connectivity; changes queue locally and sync when the network returns.
- Windows single sign-on with role-based permissions and a full audit trail of who changed what.
- A reusable design system with light and dark themes, responsive layouts, server-paged lists, and CSV export.
- Migration of existing historical data — serial history and user accounts moved into the new schema with scripted, repeatable, and reversible migrations.
All of this was built, tested, and deployed by me alone in under two weeks.
How Claude Code Changed the Work
The biggest misconception about AI-assisted development is that it's "autocomplete, but bigger." What made the difference here wasn't faster typing — it was the ability to hand off entire, well-defined units of work and review the result, while I stayed focused on design decisions and business rules.
Here's the setup that made it work.
1. A CLAUDE.md That Describes the Project
The first commit after the initial scaffold was a CLAUDE.md file at the repository root. It tells Claude Code how the solution is organized, how to build and run it, how the front-end pipeline works (webpack compiled through Gulp), which folders are generated output and must never be edited, and the architecture rules:
- Presentation (MVC controllers and Razor views)
- Domain/application services with AutoMapper view models
- Dapper data access over stored procedures
- A custom Dapper-based ASP.NET Core Identity store
- A design-system library
Because this file is loaded into every session, I never had to re-explain the architecture. Every new feature landed in the right layer, following the same patterns as the one before it.
2. Skills That Encode Coding Standards
On top of CLAUDE.md, I wrote a set of project skills — focused checklists that Claude Code loads when it's doing a particular kind of work:
| Skill | What it enforces |
|---|---|
csharp-formatting |
.editorconfig-aligned formatting, naming, and parameter conventions |
repository-layer |
How a Dapper repository is written — connection handling, parameters, output values, paged results |
service-layer |
Constructor order, current-user handling, view-model-only returns, transactions for multi-repository writes |
sql-standards |
Stored procedure naming, parameter layout, paging patterns, and a mandatory SQL-injection and SARGability review |
javascript-standards |
ES modules, async/await fetch, event delegation, where page scripts live |
ui-ux-design |
Reuse existing components and design tokens, accessibility, layout shells |
This is the part I'd recommend most. Without skills, AI-generated code tends to be correct but inconsistent — every repository slightly different. With them, the code reads as if one careful developer wrote all of it, which is what it needs to look like for the next person who maintains it.
3. Direct Database Access Through MCP
I connected Claude Code to SQL Server using the DBHub MCP server. That meant Claude could inspect the legacy schema and data directly — the duplicated per-plant tables, the serial pools, the odd column names — instead of me pasting table definitions into the chat.
This was critical for two tasks:
- Understanding the old system. Claude read the
.asppages alongside the actual tables they queried and produced functional summaries and an old-vs-new comparison that I reviewed and corrected. - Writing the data migration. Migration scripts were written against the real data shape, run against a copy of the database, verified with counts and spot checks, and each came with a rollback script.
4. Versioned Migrations and Tests as a Safety Net
Speed is only useful if the result is trustworthy. Every schema change went into a versioned SQL migration — over 100 of them by the end — so any environment can be rebuilt from scratch.
The solution also ships with unit tests, integration tests using WebApplicationFactory, and Playwright browser tests — over 500 in total. Writing that many tests by hand would have taken longer than writing the features. With Claude Code, tests were produced with each feature, and a failing test was simply the next task.
5. Me as the Architect and Reviewer
My role changed. I spent much less time writing code and much more time on:
- Designing the new data model — the manufacturer/factory/family/item hierarchy and the serial template rules
- Clarifying business rules with the people who use the system every day
- Reviewing every change before committing it
- Deciding what not to build
Claude Code is very good at execution. It is not a substitute for knowing what the plant floor actually needs. The two weeks were fast because the decisions were made quickly, not because they were skipped.
Traditional vs. AI-Assisted
| Traditional estimate | AI-assisted (actual) | |
|---|---|---|
| Team | 2–3 developers | 1 developer |
| Duration | A couple of months | Under 2 weeks to production |
| Scope | Feature parity with the legacy app | Parity plus multi-manufacturer, configurable serial templates, Spanish localization, time zones, mobile PWA |
| Data | Historical data migration as a separate phase | Historical data migrated as part of the release |
| Tests | Usually the first thing cut when time runs short | 500+ automated tests delivered with the features |
What Didn't Work (At First)
It wasn't all smooth, and I think the rough edges are the most useful part to share:
- Vague prompts produce generic code. "Add a serial number page" got something that worked but ignored the project conventions. "Add a paged serial list following the repository-layer and service-layer skills" got something I could commit. Most of my early time went into writing the skills that made short prompts reliable.
- Documentation drifts. At one point
CLAUDE.mdstill said "no test projects exist yet" after hundreds of tests had been added. The AI follows the documentation you give it, so stale documentation causes confident mistakes. Keep it current. - Business rules must be verified by a human. The legacy code had edge cases — odd serial counter behavior, plant-specific fields — that looked like bugs but weren't. AI can find them; only the business can tell you whether they matter.
- Review everything touching data. Every migration script was run against a copy of production and checked before it went anywhere near the live database.
Takeaways
- Invest in context first. A good
CLAUDE.mdand a handful of focused skills pay for themselves within days. - Give the AI access to the real system. MCP database access turned guesswork about the legacy schema into facts.
- Keep a safety net. Versioned migrations, rollback scripts, and automated tests are what make moving this fast safe.
- Shift your time to design and review. The leverage comes from making decisions faster, not from typing less.
- Use the time you save to deliver more. The extra features — multi-factory support, configurable serial templates, Spanish localization, time zone handling, and a mobile PWA — were the difference between a "lift and shift" and a system the plants actually wanted to switch to.
Legacy modernization projects are often shelved because the estimate is too large to justify. AI-assisted development changes that math. A rewrite that once needed a small team and a quarter of the year can now be one developer and a couple of weeks — and the result can be better than the original, not just newer.