“Why should you migrate your legacy .NET application to the ABP Platform instead of rewriting from scratch?”
Migration is less about “rewriting everything” and more about moving to a modular, layered architecture with consistent cross-cutting infrastructure. ABP sits on top of the .NET ecosystem while standardizing modules, use cases, transactions, and enterprise concerns like authorization, auditing, caching, and background processing. This guide has no specific constraint on .NET version, database, or UI framework; where choices matter, trade-offs are highlighted.
ABP’s value is mostly architectural: a repeatable structure for modular enterprise apps while keeping the .NET platform and libraries you know. Legacy systems suffer from technical debt, cross-cutting concerns, and coupling—ABP can reduce long-term maintenance and improve change speed with a plan that keeps the legacy system safe while you move value incrementally.
Comparing typical legacy attributes against ABP Platform standards.
Not every legacy project needs a rewrite. Use this interactive tool to evaluate if the ROI justifies the effort based on your project’s current state.
Select items to see analysis...
“The chosen methodology dictates risk profile, delivery cadence, and resource allocation.”
Incremental is the umbrella—don’t migrate everything at once.
Move features one slice at a time; legacy remains live. Technologies like YARP route traffic at the edge: new endpoints go to the ABP app, unmigrated requests go to the legacy app. Users are unaware that two systems are serving requests.
Best fit: Large production systems; applications requiring continuous uptime where minor feature sets can be moved easily.
“A technical roadmap from legacy code to a clean ABP solution.”
Click on each phase to explore details.
“Anticipating issues allows architects to design defensive solutions early.”
Apply these principles throughout the migration lifecycle.
The problem
Legacy schemas lack GUID PKs, use composite keys, or don’t match DDD boundaries.
ABP solution
Use Entity<TKey> for custom key types; map via EF Core Fluent API in OnModelCreating. Map to existing schema first, then refactor. Separate DbContext per module.
The problem
“Everything depends on everything”; copying into ABP is risky.
ABP solution
Anti-Corruption Layer (ACL): clean interface in ABP, adapters to legacy. Treat each bounded context as an ABP module behind stable APIs. Use strangler façade to route requests.
The problem
Static classes, HttpContext, direct new MyService(); shared libs hide coupling.
ABP solution
Refactor to injectable interfaces (ITransientDependency); replace HttpContext.Current with ICurrentUser / ICurrentTenant. Enforce dependency direction.
The problem
Auth sync and cookie sharing; performance overhead of new layers.
ABP solution
Share cookies via Data Protection (shared key ring); identical scheme names and cookie paths. Use DTO projections and ABP Redis batch ops; control audit via options.
A legacy 15-year-old ASP.NET MVC 5 monolith managing a complex B2B supply chain was migrated to ABP.
.NET Framework 4.7.2, single solution, controllers with ADO.NET + HTML, custom tbl_Users + MD5, 400 tables, stored procedures, no independent deployments, hardcoded tenant isolation.
.NET 8, ABP layered app. YARP proxy as gateway. Bounded contexts: Identity, Catalog, Fulfillment. Same DB with TablePrefix/schema isolation. ABP Identity + custom LegacyPasswordHasher for MD5 upgrade.
Execution: YARP routed 100% to legacy initially. Catalog module built in ABP; proxy routed /api/products and /catalog to ABP. Shared auth cookies via Data Protection. Over 18 months legacy was starved of traffic and decommissioned—strangulation complete.
Move features one slice at a time; legacy remains live during transition. Technologies like YARP route traffic at the edge: new endpoints go to the ABP app, unmigrated requests go to the legacy app. Excellent end-user experience (users unaware that two systems are serving requests).
Build a new ABP solution and replace in one cut-over ('Big Bang'). Cleanest end-state architecture quickly; avoids maintaining two concurrent systems. High risk; long 'big bang' period; historically highly prone to failure; lack of immediate feedback loops.
Define bounded contexts and migrate each as an ABP module (even inside one deployment). Each module gets its own DbContext, application services, and UI; legacy calls the new module via HTTP REST APIs or message queues. Aligns with ABP modularity and package layering; supports later split to microservices; forces teams to untangle the database.
Pre-migration success depends on rigorous analysis. Then scaffold a fresh ABP solution—do not retrofit ABP into the legacy solution.
System.Web.HttpContext, legacy session APIs).
dotnet tool install -g Volo.Abp.Studio.Cli then e.g. abp new Acme.LegacyMigration --template app --ui-framework mvc (or angular --tiered, blazor-webapp --tiered). Start with a single deployable monolith; move to distributed later if needed.
Map legacy three-tier to ABP’s DDD layers; then migrate entities and database (schema compatibility first, or module DB boundaries if needed).
.EntityFrameworkCore (IRepository, EF Core DbContext). Legacy BLL → .Domain (AggregateRoot, DomainService) + .Application (ApplicationService). Legacy UI → .Web or HttpApi + SPA. External integrations → dedicated integration package.
.Domain with AggregateRoot<Guid>. In .EntityFrameworkCore, override OnModelCreating to map to legacy table/column names (e.g. b.ToTable("tbl_LegacyUsers")). Use ObjectExtensionManager for extra legacy columns on ABP entities. Use DbMigrator for migrations and seed data.
Extract business logic from controllers and static managers into Application Services; use DTOs and map legacy auth to ABP permissions.
.Application.Contracts (e.g. IOrderAppService, OrderDto, CreateOrderDto). Implement in .Application inheriting ApplicationService; use IRepository<Order, Guid> and optional DomainService for complex rules. ABP starts a unit of work per app service method (transaction boundary).
PermissionDefinitionProvider in Application.Contracts; add permissions (e.g. LegacyMigration.Orders.Create). Use [Authorize("PermissionName")] on application services. Gradually replace legacy role checks with permission checks.
HttpContext.Current with injectable interfaces (ITransientDependency) and ABP’s ICurrentUser / ICurrentTenant.
Auth (three options), UI migration paths, background jobs/caching, and testing during migration.
IPasswordHasher to verify legacy MD5/SHA1 and return SuccessRehashNeeded. Share cookies via Data Protection API (shared key store) when running side-by-side. Option C: Tiered with OpenIddict (AuthServer) for token-based and multi-client.
HttpApi.Client for remote module consumption.
Volo.Abp.BackgroundJobs (Hangfire/Quartz). Replace HttpRuntime.Cache with IDistributedCache<T> (e.g. Redis). Use distributed event bus for module communication (in-process by default; plug RabbitMQ/Kafka later).
TestBase, in-memory SQLite/EphemeralMongo, and IDataSeedContributor for predictable test data.