Classic ASP to ASP.NET Core Migration: Guide
Matteo Migliore

Matteo Migliore is an entrepreneur and software architect with over 27 years of experience developing .NET-based solutions and evolving enterprise-grade application architectures.

He has led enterprise projects, trained hundreds of developers, and helped companies of all sizes simplify complexity by turning software into profit for their business.

The ordering system of an auto-parts company near Bergamo is 214 .asp files written between 2003 and 2011, running on a Windows Server 2012 nobody dares to switch off. The owner asked one thing: "What does it cost to move it to something that will still exist in five years, without stopping the twenty-two agents who use it every day?"

The honest answer has three numbers. A classic ASP site of that size moves to ASP.NET Core in about 45-60 person-days, 27,000-36,000 euro at a reference rate of 600 euro a day. It is done page by page, and the old site stays up until the last page is moved. Cost depends not on the number of files but on how much VBScript sits inside pages, how many COM components exist and how clean the database is.

This guide covers what classic ASP is, the risks of leaving it alone, when migration pays off, the four routes with costs, the inventory, the proxy method (YARP in front of both sites), translating VBScript and ADO to C#, sessions and login, COM components, going live and the mistakes I see repeated. I have been writing software since 1999 and worked on many classic ASP sites. If your site is WebForms, see ASP.NET WebForms to Core.

What is classic ASP and why is it still around?

Classic ASP is Microsoft's 1996 web technology where VBScript sits inside .asp pages interpreted by IIS on every request. It is not .NET, not compiled and has not been developed since 2000.

It survives because it works. A well-made 2005 site lived twenty years untouched. The problem is that "while it works" has an expiry date you do not choose: an operating system going out of support, a 32-bit COM component, or the one person who knows it leaving.

What risks come with leaving classic ASP running?

Four: security (SQL built by string concatenation, no modern protections), infrastructure lock-in, people (few developers know it) and a hidden cost in every change.

Most pages I read build SQL by pasting browser input into the query, which is an open door to SQL injection. Adding protections by hand to 214 files costs almost as much as migrating. The hidden cost is typically 6,000-12,000 euro a year. See what legacy software really costs.

When does migration pay off, and when not?

When the site is used daily, is exposed to the Internet or changes often. Not when it is a closed intranet used by four people and never modified.

A quick test: count the .asp files, daily users and changes per year. Above 100, 10 and 6, migration pays back in under three years. Below 30, 5 and 2, keep it and secure the machine: a full copy, a spare virtual machine and a reinstall document, two days of work.

Which routes exist from classic ASP to ASP.NET Core?

Four: keep and secure, migrate page by page behind a proxy (strangler), rewrite from scratch, or buy a product. For a site used daily the second usually wins.

A proxy in front of both sites: migrated pages go to ASP.NET Core, the rest stay on classic ASP

Securing costs 2,000-4,000 euro and only postpones the problem. The strangler route costs 27,000-36,000 euro over four to six months with no downtime, as described in the strangler fig pattern. A full rewrite costs 45,000-70,000 euro and carries a launch-day risk, see rewrite or refactor. A ready-made product starts around 8,000 euro a year and loses your custom rules.

How much does a classic ASP to ASP.NET Core migration cost?

About 45-60 person-days for 200 files, 27,000-36,000 euro. A small 40-file site costs 8,000-12,000 euro.

Cost comparison of the four routes: security 3,000 euro, strangler 31,000, rewrite 55,000, ready-made product 8,000 euro a year

The estimate has four items: inventory and analysis 4-6 days, foundations (project, proxy, data access, login) 6-8, pages and logic 28-38, testing and shutdown 7-10. COM components without source, hidden database logic and no test environment raise the price. A clean database and an internal person who knows the rules lower it. Compare with .NET Framework to .NET 10, where code is already C# and costs are much lower.

How do you migrate page by page with a proxy in front?

Create an ASP.NET Core project as the single entry point with YARP. Migrated pages are served by the new code, everything else is forwarded to the old site. Users keep the same address.

// dotnet new web -n NewSite
// dotnet add package Yarp.ReverseProxy
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

// Pages already migrated win over the proxy.
app.MapGet("/customers/list", () => Results.Content("<h1>Customers (new)</h1>", "text/html"));

// Everything else goes to the old classic ASP site.
app.MapReverseProxy();

app.Run();

The old site moves to an internal port and is no longer reachable from outside. Day zero changes nothing for anyone. Each week a function moves, is compared with the old one on the same data, and only then gets a route. Rollback is removing the route: one second.

How do you translate VBScript with ADO to modern C#?

ADO becomes Dapper with parameterized queries, recordsets become typed lists and Response.Write becomes Razor views. The hard part is hidden rules, not syntax.

using Dapper;
using Microsoft.Data.SqlClient;

public record OrderRow(int Id, DateTime Date, decimal Total);

public class OrdersRepository(IConfiguration configuration)
{
    private readonly string _connection = configuration.GetConnectionString("Erp")
        ?? throw new InvalidOperationException("Missing connection string.");

    public async Task<IReadOnlyList<OrderRow>> ByCustomerAsync(int customerId)
    {
        const string sql = "SELECT Id, Date, Total FROM dbo.Orders WHERE CustomerId = @customerId ORDER BY Date DESC";
        await using var conn = new SqlConnection(_connection);
        var rows = await conn.QueryAsync<OrderRow>(sql, new { customerId });
        return rows.AsList();
    }
}

The parameter is typed as an integer: text from the browser fails before touching the database. Watch dates, nulls, global variables in included files and untyped variants. An AI assistant gives a good first draft, a human writes the test, and the test decides. Dapper or Entity Framework: Dapper vs Entity Framework.

What about sessions, login, COM components and the database?

Replace Session with cookie authentication, migrate the login page first and pass the user to the old site through a header only the proxy can send. COM components with source are rewritten in C#, without source they are isolated in a small 32-bit service. The database usually stays as is.

Old passwords stored in plain text or weak hashes are rehashed with ASP.NET Core's PasswordHasher at the first successful login. Hidden jobs (a scheduled task sending invoices, a page called every five minutes by another program) must be found before switching off the old server. Details for VB6 and Delphi components: VB6 to .NET, Delphi to .NET. If data is still in Access, move it first: Access to SQL Server.

How do you go live without stopping the business, and what mistakes recur?

There is no cutover day: each page goes live alone after a comparison on the same data. Switch the old site off only after four weeks with zero requests to it.

The five common mistakes: rewriting everything in one go, translating line by line without understanding the rules, forgetting hidden jobs, redoing look and logic together, and not measuring. In the parts company the order list went from 4.8 seconds to 0.6, and a simple change from two days to three hours. Choosing who does the work: how to choose a software house.

If you want the whole picture of how I approach these projects, it is on the page about legacy software modernization. It starts with five numbers: used files, daily users, changes per year, COM components and hidden jobs. Classic ASP served a generation of companies for twenty years. Retiring it without stopping anyone is the right way to thank it.

Frequently asked questions

A site of about 200 files takes 45-60 person-days, 27,000-36,000 euro at a reference rate of 600 euro a day. A small 40-file site costs 8,000-12,000 euro.

Yes, as an optional IIS feature. But Microsoft has not developed it since 2000 and VBScript is flagged for removal in future Windows versions.

Yes, with a proxy such as YARP in front of both sites. Migrated pages go to ASP.NET Core, the rest to the old site. Rollback is removing a route.

Rebuild the behaviour from input and output tests, or isolate the DLL in a small 32-bit service called over HTTP as a temporary measure.

No, if the login page is migrated first: the new site creates the cookie and passes the user to the old one through a header only the proxy can send.

For a first draft yes, it saves about half of the writing time. Testing stays human, because nulls, dates and hidden rules are where it fails silently.

When the site is a small intranet used by a few people and unchanged for years. A machine copy, a spare VM and a reinstall document are two days of work.

Leave your details in the form below

Matteo Migliore

Matteo Migliore is an entrepreneur and software architect with over 27 years of experience developing .NET-based solutions and evolving enterprise-grade application architectures.

Throughout his career, he has worked with organizations such as Cotonella, Il Sole 24 Ore, FIAT and NATO, leading teams in developing scalable platforms and modernizing complex legacy ecosystems.

He has trained hundreds of developers and supported companies of all sizes in turning software into a competitive advantage, reducing technical debt and achieving measurable business results.

Stai leggendo perché vuoi smettere di rattoppare software fragile.Scopri il metodo per progettare sistemi che reggono nel tempo.