A Technology Older Than Me
I'm currently working on a project to migrate a Classic ASP application — written in VBScript — to ASP.NET Core 10. For context, Classic ASP was released in 1996 and effectively replaced by ASP.NET in 2002. This technology was deprecated before I was born.
When I first got assigned to this project, my initial reaction was: "Can I even open this?" The codebase is a collection of .asp files with inline VBScript, #include directives, and ADO database calls. No solution file. No project structure in any modern sense. Just folders full of .asp pages, some shared include files, and a global.asa.
I'd worked on ASP.NET MVC 5 to ASP.NET Core migrations before — that's a well-documented path with clear patterns. But Classic ASP to .NET Core? That's jumping across 25+ years of web development evolution. There's no migration tool for this. No automated converter. Every page has to be understood and rewritten.
Step 1: Can We Even Run It?
The first challenge was simply getting the existing application running locally so I could understand what it does. Classic ASP runs on IIS, and I wasn't sure if Visual Studio 2026 would have any idea what to do with a folder full of .asp files.
It turns out — it works. Here's how:
Opening the Project
Since there's no .sln or .csproj file, you can't use "Open Project." Instead:
- File → Open → Web Site... (not "Open Project")
- Select the folder containing the
.aspfiles - Visual Studio opens it as a "Web Site" project
This is a feature that's been in Visual Studio for years, originally designed for ASP.NET Web Forms "Web Site" projects (as opposed to "Web Application" projects). It also works for Classic ASP.
Running on IIS Express
Once opened, the site runs on Visual Studio's built-in IIS Express. Classic ASP support is included in IIS Express — no extra configuration needed. Hit F5 (or Ctrl+F5 for without debugging) and the .asp pages load in the browser.
You might need to make sure the IIS Express applicationhost.config has Classic ASP enabled:
<section name="asp" overrideModeDefault="Allow" />
And in the site's web.config or IIS configuration, ensure the ASP module is loaded:
<system.webServer>
<handlers>
<add name="ASPClassic" path="*.asp" verb="*"
modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll"
resourceType="File" />
</handlers>
</system.webServer>
In most cases, IIS Express handles this automatically — but if you get 404 errors on .asp pages, check these settings.
Debugging
This was the biggest surprise. You can actually debug Classic ASP — but it requires a specific setup through IIS, not just IIS Express.
Step 1: Enable Remote Debugging on IIS
First, you need to enable server-side debugging in IIS for your Classic ASP site:
- Open IIS Manager (
inetmgr) - Select your site, then open ASP under the IIS section
- Expand Compilation and set Debugging Properties:
- Enable Server-side Debugging →
True - Enable Client-side Debugging →
True(optional, helps with client script issues)
- Enable Server-side Debugging →
- Click Apply
Important: Server-side debugging significantly impacts performance. Only enable it on your development machine, never on a production server.
Step 2: Attach Visual Studio to the IIS Worker Process
Once debugging is enabled on IIS, you attach Visual Studio to the running IIS process:
- Make sure your site is running in IIS (browse to it first so the worker process starts)
- In Visual Studio, go to Debug → Attach to Process... (Ctrl+Alt+P)
- In the Attach to field, click Select... and check Script
- Find w3wp.exe in the process list (this is the IIS worker process)
- If you don't see it, check Show processes from all users
- If there are multiple
w3wp.exeprocesses, identify the correct one by its application pool (check the User Name column or useappcmd list wpin a command prompt)
- Select the process and click Attach
Now you can set breakpoints directly in .asp files in the VBScript code. Visual Studio will break on your breakpoints, show local variables, and let you step through VBScript line by line. It's not as polished as debugging C# — the variable inspection is more limited, and IntelliSense doesn't exist for VBScript — but it works. Being able to step through the legacy code while figuring out what it does is invaluable.
Understanding the Classic ASP Codebase
Before rewriting anything, I spent time reading and mapping the existing application. Classic ASP codebases tend to share certain patterns:
Inline Everything
There's no separation of concerns. HTML, VBScript logic, SQL queries, and presentation are all mixed together in the same .asp file:
<%
Dim conn, rs, sql
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open Application("ConnectionString")
sql = "SELECT * FROM Products WHERE CategoryId = " & Request.QueryString("catId")
Set rs = conn.Execute(sql)
%>
<html>
<body>
<table>
<% Do While Not rs.EOF %>
<tr>
<td><%= rs("ProductName") %></td>
<td><%= FormatCurrency(rs("Price")) %></td>
</tr>
<%
rs.MoveNext
Loop
rs.Close
conn.Close
%>
</table>
</body>
</html>
Yes, that's a SQL injection vulnerability on line 5. Classic ASP code is full of them. This alone is a compelling reason to migrate.
Include Files as "Components"
Classic ASP doesn't have real components or classes (well, VBScript has Class, but it's rarely used well). Instead, shared code lives in include files:
<!-- #include file="includes/db.asp" -->
<!-- #include file="includes/auth.asp" -->
<!-- #include file="includes/functions.asp" -->
These includes are essentially copy-pasted into the page at runtime. There's no namespace, no scope isolation. A variable defined in functions.asp is visible in every page that includes it. This makes it tricky to understand where a variable or function comes from.
Session and Application State
Classic ASP uses Session and Application objects heavily — for user authentication, shopping carts, configuration, cached data. The global.asa file (similar to Global.asax) handles application and session start/end events:
<SCRIPT LANGUAGE="VBScript" RUNAT="Server">
Sub Application_OnStart
Application("ConnectionString") = "Provider=SQLOLEDB;Data Source=..."
Application("SiteName") = "My Application"
End Sub
Sub Session_OnStart
Session("IsLoggedIn") = False
End Sub
</SCRIPT>
The Migration Strategy
You can't automate this migration. There's no tool that converts VBScript to C# or .asp pages to Razor Pages. Every page needs to be rewritten. But you can be systematic about it.
1. Map the Application
Before writing any C# code, I document every .asp page:
- What URL does it serve?
- What database queries does it run?
- What does it display?
- Does it handle form submissions?
- What session/application state does it depend on?
- What include files does it use?
This gives you a complete picture of the application's surface area. For a medium-sized app, this might be 50-100 pages, but many of them follow the same patterns.
2. Identify Shared Patterns
Most Classic ASP apps have a handful of patterns repeated across every page:
- Database connection — open connection, execute query, loop through results, close connection
- Authentication check —
If Session("IsLoggedIn") = False Then Response.Redirect "login.asp" - Form handling —
Request.Form("fieldName")→ validate → INSERT/UPDATE → redirect - Pagination — manual record counting and
OFFSET/TOPlogic
Each of these becomes a clean abstraction in ASP.NET Core: EF Core or Dapper for data access, ASP.NET Core Identity or cookie authentication middleware, model binding with validation, and built-in pagination helpers.
3. Build the ASP.NET Core Shell First
Set up the new ASP.NET Core 10 project with:
- The data access layer (EF Core + Dapper for performance-critical queries)
- Authentication and authorization
- The shared layout (master page equivalent)
- A CSS framework or custom styles that match the existing site's look
Don't try to improve the UI during migration — match the existing look first, redesign later. Users should see the same interface after migration, even though the technology underneath is completely different.
4. Migrate Page by Page
For each .asp page, create the equivalent Razor Page (or Controller + View):
- Read the
.aspfile and understand what it does - Extract the SQL queries → create a repository method or stored procedure call
- Create the Razor Page with the same URL route
- Implement the same logic in C# with proper parameterized queries
- Build the view to produce the same HTML output
- Test against the same database
The key is to run both applications side by side during migration — the Classic ASP site on one port and the new ASP.NET Core site on another — so you can compare the output page by page.
5. Fix Security Issues Along the Way
Classic ASP applications almost always have security vulnerabilities:
- SQL injection — inline string concatenation for queries → parameterized queries in Dapper/EF Core
- XSS —
<%= variable %>doesn't encode by default → Razor encodes by default with@variable - No CSRF protection — Classic ASP has no anti-forgery tokens → ASP.NET Core includes them automatically
- Weak authentication — often custom session-based auth with plain-text password comparison → ASP.NET Core Identity with proper password hashing
Don't just port the vulnerabilities. Use the migration as an opportunity to fix every security issue. This is one of the strongest arguments for doing the migration in the first place.
Classic ASP to ASP.NET Core — A Translation Guide
Here's a quick reference for translating common Classic ASP patterns:
| Classic ASP (VBScript) | ASP.NET Core (C# / Razor) |
|---|---|
Request.QueryString("id") |
Request.Query["id"] or model binding |
Request.Form("name") |
Request.Form["name"] or [BindProperty] |
Response.Redirect "page.asp" |
return RedirectToPage("Page") |
Response.Write "text" |
@text in Razor |
Server.HTMLEncode(str) |
Razor auto-encodes; @Html.Raw() for unencoded |
Session("key") = value |
HttpContext.Session.SetString("key", value) |
Server.CreateObject("ADODB.Connection") |
Dependency injection + EF Core / Dapper |
<!-- #include file="..." --> |
Partial views, Tag Helpers, DI services |
Server.MapPath("/uploads") |
IWebHostEnvironment.ContentRootPath |
FormatCurrency(val) |
val.ToString("C") |
global.asa |
Program.cs (service registration, middleware) |
Lessons So Far
A few things I've learned working on this migration:
Don't underestimate the domain knowledge embedded in the code. The VBScript might be ugly, but it encodes years of business rules and edge cases. Read it carefully. That weird If statement on line 47 is probably handling a real scenario that someone discovered the hard way.
Classic ASP's simplicity is also its documentation. Because everything is inline — SQL, HTML, logic — you can read a single .asp file and understand the entire request lifecycle for that page. There are no layers of abstraction to trace through. Use this to your advantage during the mapping phase.
Run both versions in parallel. Having the old and new apps side by side, pointing at the same database, is the best way to verify that the migration is correct. Open the same page in both browsers and compare the output.
Visual Studio 2026 is surprisingly capable with Classic ASP. I expected the experience to be painful, but being able to open, run, and debug the legacy codebase in the same IDE where I'm building the replacement makes the whole process much smoother.
The migration is ongoing, but the approach is working. One page at a time, the VBScript is becoming C#, the inline SQL is becoming parameterized queries, and the security holes are getting closed. It's not glamorous work, but it's satisfying to see a 25-year-old codebase get a modern foundation.