
Legacy Aspnet
- 18 installs
- 466 repo stars
- Updated July 25, 2026
- managedcode/dotnet-skills
Helps with ai & agent building tasks.
About
legacy-aspnet is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- legacy-aspnet
- AI & Agent Building
- AI-coding skill
Legacy Aspnet by the numbers
- 18 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #10,736 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/managedcode/dotnet-skills --skill legacy-aspnetAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 466 |
| Last updated | July 25, 2026 |
| Repository | managedcode/dotnet-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Legacy ASP.NET
Trigger On
- working in Web Forms, legacy MVC, or classic ASP.NET applications
- reviewing old IIS-centric configuration and lifecycle behavior
- planning migration toward ASP.NET Core without breaking core business flows
Workflow
1. Treat classic ASP.NET as a distinct stack with different hosting, lifecycle, and configuration rules from ASP.NET Core. 2. Stabilize behavior first: routing, session, auth, server controls, configuration transforms, and deployment assumptions. 3. Plan modernization in seams: isolate domain and service logic, then move replaceable edges instead of rewriting the whole app at once. 4. Use wcf or entity-framework6 when the legacy app depends on those subsystems rather than flattening them into generic web work. 5. Be careful with guidance copied from ASP.NET Core because middleware, DI, and hosting assumptions do not transfer directly. 6. Validate in an environment that resembles real IIS and configuration transforms.
Deliver
- practical maintenance guidance for classic ASP.NET
- stabilized legacy behavior and modernization seams
- a migration path that avoids unnecessary risk
Validate
- classic and Core guidance are not mixed
- legacy runtime assumptions are preserved deliberately
- migration steps are incremental and testable
References
- Migration Paths: strategies for migrating from ASP.NET to ASP.NET Core, including incremental migration, strangler fig pattern, and component-specific guidance
- Maintenance Patterns: stabilization and maintenance patterns for legacy ASP.NET code, including abstraction layers, testing seams, and deployment practices
{
"version": "1.0.0",
"category": "Legacy"
}
Migration Paths from ASP.NET to ASP.NET Core
This document outlines practical migration strategies for moving classic ASP.NET applications to ASP.NET Core.
Migration Strategy Selection
Incremental Migration (Recommended for Large Applications)
Use incremental migration when:
- The application has significant business logic that must remain stable
- Full rewrites carry unacceptable risk
- The team needs to maintain production stability during migration
Approach: 1. Identify natural seams in the application architecture 2. Extract shared business logic into .NET Standard libraries 3. Create a new ASP.NET Core host alongside the legacy application 4. Use YARP or a reverse proxy to route traffic between old and new endpoints 5. Migrate one vertical slice at a time 6. Decommission legacy endpoints as Core equivalents stabilize
Strangler Fig Pattern
Use the strangler fig approach when:
- The application can tolerate running both stacks in parallel
- You can route at the edge (load balancer, reverse proxy)
- Migration will span multiple release cycles
Implementation: 1. Deploy an ASP.NET Core application alongside the legacy application 2. Configure routing rules to send new or migrated endpoints to Core 3. Gradually move functionality from legacy to Core 4. Remove legacy application once all endpoints are migrated
Full Rewrite
Consider full rewrite only when:
- The legacy application is small and well-understood
- Business requirements have changed significantly
- Technical debt makes incremental migration more expensive than rebuilding
- The team has capacity for parallel development and testing
Component Migration Paths
Web Forms to ASP.NET Core
Web Forms has no direct equivalent in ASP.NET Core. Migration options:
1. Razor Pages: Best fit for page-centric workflows with code-behind patterns
- Map ASPX pages to Razor Pages
- Replace server controls with Tag Helpers or Razor components
- Move code-behind logic to PageModel handlers
2. Blazor Server: Best fit when preserving stateful component behavior
- Recreate server controls as Blazor components
- Maintain server-side rendering with interactive updates
- Preserve familiar event-driven programming model
3. MVC: Best fit when moving toward clean separation of concerns
- Extract business logic into services
- Map postback handlers to controller actions
- Replace ViewState with explicit state management
MVC 5 to ASP.NET Core MVC
MVC migration is more straightforward but still requires attention:
1. Routing: Replace RouteConfig with endpoint routing
routes.MapRoutebecomesendpoints.MapControllerRoute- Attribute routing works similarly but uses different base classes
2. Filters: Migrate action filters to ASP.NET Core filter contracts
IActionFilterinterface signature changesFilterContextreplacesActionExecutingContextpatterns
3. Dependency Injection: Move from third-party containers to built-in DI
- Register services in
Program.csorStartup.cs - Replace service locator patterns with constructor injection
4. Configuration: Replace Web.config with appsettings.json
- Move connection strings to configuration providers
- Replace ConfigurationManager calls with IConfiguration injection
5. Authentication: Replace Forms Authentication or ASP.NET Identity
- Cookie authentication middleware replaces FormsAuthentication
- ASP.NET Core Identity replaces legacy ASP.NET Identity
Session State
1. In-Process Session: Direct migration to ASP.NET Core session middleware 2. SQL Server Session: Use distributed session with SQL Server provider 3. Redis Session: Use distributed session with Redis provider 4. Custom Providers: Implement IDistributedCache or ITicketStore
Caching
1. System.Web.Caching: Replace with IMemoryCache or IDistributedCache 2. Output Caching: Use Response Caching middleware 3. HttpRuntime.Cache: Inject IMemoryCache instead
HTTP Modules and Handlers
1. HTTP Modules: Convert to middleware
BeginRequestmaps to middleware pipeline positionAuthenticateRequestmaps to authentication middlewareAuthorizeRequestmaps to authorization middleware
2. HTTP Handlers: Convert to middleware or endpoint handlers
- ASHX handlers become middleware or minimal API endpoints
- Map specific routes to handler logic
Configuration Migration
Web.config to appsettings.json
| Web.config | ASP.NET Core |
|---|---|
<connectionStrings> | ConnectionStrings section in appsettings.json |
<appSettings> | Configuration sections or direct values |
<system.web> | Middleware and service configuration |
<httpRuntime> | Kestrel limits and middleware options |
<customErrors> | Exception handling middleware |
<authorization> | Authorization middleware and policies |
Machine.config Dependencies
Identify machine-level configuration dependencies:
- Connection strings in machine.config
- Custom configuration sections
- GAC assemblies
Migrate these to:
- Environment variables or secure configuration providers
- NuGet package references
- Application-level configuration
Testing Migration
1. Unit Tests: Often portable with namespace changes
- Update test framework packages to .NET-compatible versions
- Replace HttpContext mocks with ASP.NET Core test infrastructure
2. Integration Tests: Require significant rework
- Use WebApplicationFactory for in-memory testing
- Replace SystemWeb test utilities with Microsoft.AspNetCore.Mvc.Testing
3. End-to-End Tests: Often require minimal changes
- Update URLs if routing changes
- Adjust authentication flows if mechanisms change
Risk Mitigation
1. Feature Parity Checklist: Document all legacy features before migration 2. Parallel Running: Run both stacks in production during migration 3. Rollback Plan: Maintain ability to route back to legacy 4. Monitoring: Implement equivalent logging and metrics in both stacks 5. Performance Baseline: Capture legacy performance metrics for comparison
Common Pitfalls
1. Assuming API compatibility: ASP.NET Core is not a drop-in replacement 2. Ignoring IIS dependencies: Classic pipeline behaviors do not exist in Core 3. Session affinity assumptions: Default session is not sticky in Core 4. ViewState replacement: There is no automatic state preservation 5. Global.asax lifecycle: Application events work differently 6. HttpContext.Current: Static context access does not exist in Core
Maintenance Patterns for Legacy ASP.NET Code
This document provides patterns for maintaining and stabilizing classic ASP.NET applications on .NET Framework.
Stabilization Patterns
Isolate Business Logic
Extract business logic from code-behind and controllers into standalone classes:
// Before: Logic embedded in code-behind
public partial class OrderPage : Page
{
protected void SubmitOrder_Click(object sender, EventArgs e)
{
var order = new Order();
order.CustomerId = int.Parse(CustomerIdField.Text);
order.Total = CalculateTotal();
// validation, persistence, notification all inline
}
}
// After: Logic in a service class
public class OrderService
{
public OrderResult SubmitOrder(OrderRequest request)
{
// validation, persistence, notification encapsulated
}
}Benefits:
- Testable without Web Forms infrastructure
- Portable to other hosting models
- Clear boundaries for future migration
Introduce Dependency Injection Gradually
Add a DI container without requiring a full rewrite:
1. Install a container compatible with System.Web (Autofac, Unity, Simple Injector) 2. Configure the container in Global.asax Application_Start 3. Use property injection for Web Forms pages 4. Use constructor injection for new service classes
// Global.asax.cs
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.RegisterType<OrderService>().As<IOrderService>();
builder.RegisterType<OrderRepository>().As<IOrderRepository>();
var container = builder.Build();
// Web Forms property injection
var propertyInjection = new AutofacWebFormsPropertyInjection(container);
propertyInjection.InjectDependenciesIntoPage(this);
}Wrap Static Dependencies
Isolate HttpContext.Current and other static dependencies:
// Wrapper interface
public interface IHttpContextAccessor
{
HttpContextBase Current { get; }
}
// Production implementation
public class WebHttpContextAccessor : IHttpContextAccessor
{
public HttpContextBase Current => new HttpContextWrapper(HttpContext.Current);
}
// Test implementation
public class FakeHttpContextAccessor : IHttpContextAccessor
{
public HttpContextBase Current { get; set; }
}Configuration Abstraction
Wrap ConfigurationManager to enable testing and future migration:
public interface IAppConfiguration
{
string GetConnectionString(string name);
string GetSetting(string key);
T GetSection<T>(string sectionName) where T : class;
}
public class WebConfigConfiguration : IAppConfiguration
{
public string GetConnectionString(string name) =>
ConfigurationManager.ConnectionStrings[name]?.ConnectionString;
public string GetSetting(string key) =>
ConfigurationManager.AppSettings[key];
public T GetSection<T>(string sectionName) where T : class =>
ConfigurationManager.GetSection(sectionName) as T;
}Web Forms Patterns
Reduce ViewState Dependency
Minimize ViewState usage to improve performance and simplify migration:
1. Disable ViewState at page level when not needed: EnableViewState="false" 2. Use explicit hidden fields for required state 3. Store complex state in session or database 4. Prefer server-side data retrieval over ViewState round-trips
Master Page Consolidation
Reduce layout duplication before migration:
1. Consolidate to a single master page hierarchy 2. Extract common scripts and styles to a shared location 3. Use ContentPlaceHolders consistently 4. Document the master page contract for Razor layout conversion
User Control Inventory
Catalog user controls for component migration planning:
| Control | Dependencies | State | Migration Target |
|---|---|---|---|
| HeaderControl.ascx | Session, Auth | Minimal | Razor partial |
| OrderGrid.ascx | ViewState, DataSource | Heavy | Blazor component |
| SearchBox.ascx | None | None | Tag Helper |
MVC Patterns
Area Organization
Organize large MVC applications into areas for incremental migration:
/Areas
/Orders
/Controllers
/Views
/Models
/Customers
/Controllers
/Views
/Models
/Legacy
/Controllers (unmigrated controllers)
/ViewsEach area can be migrated independently.
Filter Standardization
Consolidate action filters before migration:
// Standardized exception filter
public class StandardExceptionFilter : IExceptionFilter
{
private readonly ILogger _logger;
public StandardExceptionFilter(ILogger logger)
{
_logger = logger;
}
public void OnException(ExceptionContext filterContext)
{
_logger.Error(filterContext.Exception, "Unhandled exception in {Controller}/{Action}",
filterContext.RouteData.Values["controller"],
filterContext.RouteData.Values["action"]);
filterContext.Result = new ViewResult { ViewName = "Error" };
filterContext.ExceptionHandled = true;
}
}Route Consolidation
Document and simplify routing before migration:
// Before: Scattered route definitions
routes.MapRoute("OrderDetails", "orders/{id}", new { controller = "Orders", action = "Details" });
routes.MapRoute("OrderList", "orders", new { controller = "Orders", action = "Index" });
routes.MapRoute("CustomerOrders", "customers/{customerId}/orders", new { controller = "Orders", action = "ByCustomer" });
// After: Consistent attribute routing
[RoutePrefix("orders")]
public class OrdersController : Controller
{
[Route("")]
public ActionResult Index() { }
[Route("{id:int}")]
public ActionResult Details(int id) { }
[Route("~/customers/{customerId:int}/orders")]
public ActionResult ByCustomer(int customerId) { }
}Session and Caching Patterns
Session Abstraction
Wrap session access for testing and migration:
public interface ISessionStore
{
T Get<T>(string key);
void Set<T>(string key, T value);
void Remove(string key);
}
public class AspNetSessionStore : ISessionStore
{
private readonly HttpSessionStateBase _session;
public AspNetSessionStore(HttpSessionStateBase session)
{
_session = session;
}
public T Get<T>(string key) => (T)_session[key];
public void Set<T>(string key, T value) => _session[key] = value;
public void Remove(string key) => _session.Remove(key);
}Cache Abstraction
Wrap System.Web.Caching for migration:
public interface ICacheStore
{
T Get<T>(string key);
void Set<T>(string key, T value, TimeSpan expiration);
void Remove(string key);
}
public class WebCacheStore : ICacheStore
{
public T Get<T>(string key) => (T)HttpRuntime.Cache.Get(key);
public void Set<T>(string key, T value, TimeSpan expiration)
{
HttpRuntime.Cache.Insert(key, value, null,
DateTime.UtcNow.Add(expiration),
Cache.NoSlidingExpiration);
}
public void Remove(string key) => HttpRuntime.Cache.Remove(key);
}Authentication Patterns
Authentication Abstraction
Wrap Forms Authentication for flexibility:
public interface IAuthenticationService
{
void SignIn(string username, bool persistent);
void SignOut();
string GetCurrentUser();
bool IsAuthenticated { get; }
}
public class FormsAuthenticationService : IAuthenticationService
{
public void SignIn(string username, bool persistent)
{
FormsAuthentication.SetAuthCookie(username, persistent);
}
public void SignOut()
{
FormsAuthentication.SignOut();
}
public string GetCurrentUser() => HttpContext.Current.User?.Identity?.Name;
public bool IsAuthenticated => HttpContext.Current.User?.Identity?.IsAuthenticated ?? false;
}Role-Based Authorization Consolidation
Standardize role checks before migration:
public interface IAuthorizationService
{
bool HasPermission(string permission);
bool IsInRole(string role);
IEnumerable<string> GetRoles();
}
public class WebAuthorizationService : IAuthorizationService
{
public bool HasPermission(string permission)
{
// Map permissions to roles or custom logic
return Roles.IsUserInRole(permission);
}
public bool IsInRole(string role) => Roles.IsUserInRole(role);
public IEnumerable<string> GetRoles() => Roles.GetRolesForUser();
}Logging and Monitoring Patterns
Structured Logging Introduction
Add structured logging without changing existing code:
// Logging abstraction
public interface IAppLogger
{
void Info(string message, params object[] args);
void Warn(string message, params object[] args);
void Error(Exception ex, string message, params object[] args);
}
// Implementation with structured logging
public class SerilogAppLogger : IAppLogger
{
private readonly ILogger _logger;
public SerilogAppLogger(ILogger logger)
{
_logger = logger;
}
public void Info(string message, params object[] args) =>
_logger.Information(message, args);
public void Warn(string message, params object[] args) =>
_logger.Warning(message, args);
public void Error(Exception ex, string message, params object[] args) =>
_logger.Error(ex, message, args);
}Health Check Endpoint
Add a health check endpoint for monitoring:
// HealthController.cs
public class HealthController : Controller
{
private readonly IHealthCheckService _healthCheck;
public HealthController(IHealthCheckService healthCheck)
{
_healthCheck = healthCheck;
}
[Route("health")]
public ActionResult Index()
{
var result = _healthCheck.Check();
Response.StatusCode = result.IsHealthy ? 200 : 503;
return Json(result, JsonRequestBehavior.AllowGet);
}
}Testing Patterns
Test Seams for Legacy Code
Introduce test seams without major refactoring:
1. Extract and Override: Make methods virtual and override in tests 2. Subclass and Override: Create testable subclasses 3. Wrap Static Calls: Create instance wrappers around static dependencies
// Extract and Override pattern
public class OrderProcessor
{
public virtual DateTime GetCurrentTime() => DateTime.UtcNow;
public bool IsOrderExpired(Order order)
{
return order.ExpirationDate < GetCurrentTime();
}
}
// Test subclass
public class TestableOrderProcessor : OrderProcessor
{
public DateTime CurrentTime { get; set; }
public override DateTime GetCurrentTime() => CurrentTime;
}Integration Test Infrastructure
Create test infrastructure that mirrors production:
[TestFixture]
public class OrderControllerTests
{
private TestServer _server;
[SetUp]
public void Setup()
{
_server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>());
}
[Test]
public async Task GetOrders_ReturnsOrders()
{
var client = _server.CreateClient();
var response = await client.GetAsync("/api/orders");
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
}Deployment Patterns
Configuration Transform Management
Organize configuration transforms for maintainability:
/Web.config
/Web.Debug.config
/Web.Release.config
/Web.Staging.config
/Web.Production.configUse consistent transform patterns:
<!-- Web.Production.config -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add name="DefaultConnection"
connectionString="#{DatabaseConnectionString}#"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
<appSettings>
<add key="Environment" value="Production"
xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
</appSettings>
</configuration>IIS Configuration as Code
Document IIS configuration for reproducibility:
# Create application pool
New-WebAppPool -Name "LegacyAppPool"
Set-ItemProperty "IIS:\AppPools\LegacyAppPool" -Name "managedRuntimeVersion" -Value "v4.0"
Set-ItemProperty "IIS:\AppPools\LegacyAppPool" -Name "enable32BitAppOnWin64" -Value $false
# Create website
New-Website -Name "LegacyApp" -Port 80 -PhysicalPath "C:\inetpub\wwwroot\LegacyApp" -ApplicationPool "LegacyAppPool"
# Configure authentication
Set-WebConfigurationProperty -Filter "/system.webServer/security/authentication/anonymousAuthentication" -Name "enabled" -Value $true -PSPath "IIS:\Sites\LegacyApp"
Set-WebConfigurationProperty -Filter "/system.webServer/security/authentication/windowsAuthentication" -Name "enabled" -Value $false -PSPath "IIS:\Sites\LegacyApp"