| name | migrate-aspnetboilerplate-to-abp |
| description | Migrate existing ASP.NET Boilerplate projects to ABP.IO framework with step-by-step guidance, code transformation, and best practices. |
Migrate ASP.NET Boilerplate to ABP.IO
Migrate existing ASP.NET Boilerplate projects to the modern ABP.IO framework with comprehensive guidance and automated transformation steps.
Primary Directive
Your goal is to guide the migration of an existing ASP.NET Boilerplate project to ABP.IO framework, ensuring minimal disruption and maximum compatibility.
Migration Assessment
Pre-Migration Checklist
Before starting migration, verify:
Migration Feasibility
Good Candidates for Migration:
- ASP.NET Boilerplate 4.x+ projects
- .NET Core compatible codebase
- Standard ABP features usage
- Moderate customization level
- Active development project
Challenging Migrations:
- Heavy custom UI modifications
- Legacy .NET Framework code
- Extensive third-party integrations
- Highly customized authentication
- Complex multi-tenant implementations
Migration Strategy
Phase 1: Preparation
1.1 Environment Setup
dotnet tool install -g Volo.Abp.Cli
abp --version
1.2 Backup Current Project
cp -r MyProject MyProject_backup_$(date +%Y%m%d)
git tag aspnetboilerplate_final
git push origin aspnetboilerplate_final
1.3 Dependency Analysis
Create dependency inventory:
dotnet list package
Phase 2: New ABP Project Creation
2.1 Template Selection
Based on project analysis, select appropriate ABP template:
abp new MyCompany.MyProject -t app -u mvc -d ef
abp new MyCompany.MyProject -t app -u angular -d ef --tiered
abp new MyCompany.MyProject -t app-nolayers -u mvc -d ef
2.2 Initial Setup
cd MyCompany.MyProject
dotnet restore
dotnet ef database update
dotnet run --project src/MyCompany.MyProject.Web
Phase 3: Code Migration
3.1 Domain Layer Migration
Entity Migration Pattern:
public class Product : FullAuditedEntity
{
public string Name { get; set; }
public decimal Price { get; set; }
public ProductCategory Category { get; set; }
}
public class Product : FullAuditedAggregateRoot<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
public ProductCategory Category { get; set; }
protected Product() { }
public Product(
Guid id,
string name,
decimal price,
ProductCategory category) : base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
Price = price;
Category = category;
}
}
Value Object Migration:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class Address : ValueObject
{
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Street;
yield return City;
yield return Country;
}
}
Repository Migration:
public interface IProductRepository : IRepository<Product>
{
List<Product> GetProductsByCategory(int categoryId);
}
public interface IProductRepository : IRepository<Product, Guid>
{
Task<List<Product>> GetProductsByCategoryAsync(Guid categoryId);
}
3.2 Application Layer Migration
Application Service Migration:
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product> _productRepository;
public ProductAppService(IRepository<Product> productRepository)
{
_productRepository = productRepository;
}
public ListResultDto<ProductDto> GetAll(GetAllProductsInput input)
{
var products = _productRepository.GetAllList();
return new ListResultDto<ProductDto>(
ObjectMapper.Map<List<Product>, List<ProductDto>>(products)
);
}
}
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListAsync(GetProductsInput input)
{
var queryable = await _productRepository.GetQueryableAsync();
var products = await AsyncExecuter.ToListAsync(
queryable
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
p => p.Name.Contains(input.Filter))
.OrderBy(p => p.Name)
.PageBy(input.SkipCount, input.MaxResultCount)
);
var totalCount = await AsyncExecuter.CountAsync(
queryable.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
p => p.Name.Contains(input.Filter))
);
return new PagedResultDto<ProductDto>(
totalCount,
ObjectMapper.Map<List<Product>, List<ProductDto>>(products)
);
}
}
DTO Migration:
public class ProductDto : EntityDto
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductDto : EntityDto<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateUpdateProductDto
{
[Required]
[StringLength(ProductConsts.MaxNameLength)]
public string Name { get; set; }
[Range(0.01, 10000.00)]
public decimal Price { get; set; }
}
3.3 Infrastructure Layer Migration
Entity Framework Configuration:
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable("Products");
builder.Property(p => p.Name)
.IsRequired()
.HasMaxLength(128);
builder.Property(p => p.Price)
.HasColumnType("decimal(18,2)");
}
}
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable(MyProjectConsts.DbTablePrefix + "Products", MyProjectConsts.DbSchema);
builder.Property(x => x.Name)
.IsRequired()
.HasMaxLength(ProductConsts.MaxNameLength);
builder.Property(x => x.Price)
.HasColumnType("decimal(18,2)");
}
}
3.4 Web Layer Migration
Controller Migration:
public class ProductsController : AbpController
{
private readonly IProductAppService _productAppService;
public ProductsController(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public ActionResult Index()
{
var products = _productAppService.GetAll(new GetAllProductsInput());
return View(products);
}
}
public class ProductsController : AbpControllerBase
{
private readonly IProductAppService _productAppService;
public ProductsController(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task<IActionResult> Index()
{
var products = await _productAppService.GetListAsync(new GetProductsInput());
return View(products);
}
}
Phase 4: Namespace and Package Updates
4.1 Namespace Changes
using Abp.Application.Services;
using Abp.Domain.Repositories;
using Abp.Authorization;
using Abp.UI;
using Abp.Domain.Entities;
using Abp.EntityFrameworkCore;
using Abp.AspNetCore.Mvc.Controllers;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Authorization;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.AspNetCore.Mvc;
4.2 Package Reference Updates
<PackageReference Include="Abp.AspNetCore" Version="6.4.0" />
<PackageReference Include="Abp.EntityFrameworkCore" Version="6.4.0" />
<PackageReference Include="Abp.AspNetCore.Mvc" Version="6.4.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="8.0.0" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore" Version="8.0.0" />
Phase 5: Configuration Migration
5.1 Module Configuration
[DependsOn(typeof(AbpAspNetCoreModule))]
public class MyProjectWebModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(MyProjectWebModule));
}
}
[DependsOn(
typeof(MyProjectApplicationModule),
typeof(MyProjectEntityFrameworkCoreModule),
typeof(AbpAspNetCoreMvcModule)
)]
public class MyProjectWebModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(MyProjectApplicationModule).Assembly);
});
}
}
5.2 Configuration Updates
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyProjectDb;Trusted_Connection=true"
}
}
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyProjectDb;Trusted_Connection=true"
},
"App": {
"SelfUrl": "https://localhost:44300",
"CorsOrigins": "https://localhost:44300"
},
"AuthServer": {
"Authority": "https://localhost:44300",
"RequireHttpsMetadata": "false"
}
}
Phase 6: Testing and Validation
6.1 Unit Test Migration
public class ProductAppService_Tests : AppTestBase
{
private readonly IProductAppService _productAppService;
public ProductAppService_Tests()
{
_productAppService = Resolve<IProductAppService>();
}
[Fact]
public void Should_Get_All_Products()
{
var products = _productAppService.GetAll(new GetAllProductsInput());
products.Items.Count.ShouldBeGreaterThan(0);
}
}
public class ProductAppService_Tests : MyProjectApplicationTestBase
{
private readonly IProductAppService _productAppService;
public ProductAppService_Tests()
{
_productAppService = GetRequiredService<IProductAppService>();
}
[Fact]
public async Task Should_Get_All_Products()
{
var products = await _productAppService.GetListAsync(new GetProductsInput());
products.TotalCount.ShouldBeGreaterThan(0);
}
}
6.2 Integration Testing
dotnet test
dotnet ef migrations list
dotnet run --project src/MyCompany.MyProject.Web
Migration Automation Scripts
Batch Migration Script
#!/bin/bash
PROJECT_NAME=$1
TEMPLATE_TYPE=${2:-"app"}
UI_FRAMEWORK=${3:-"mvc"}
DB_PROVIDER=${4:-"ef"}
echo "Migrating $PROJECT_NAME to ABP.IO..."
abp new $PROJECT_NAME -t $TEMPLATE_TYPE -u $UI_FRAMEWORK -d $DB_PROVIDER
echo "Copying domain entities..."
cp -r ../$PROJECT_NAME_backup/src/$PROJECT_NAME.Domain/Entities src/$PROJECT_NAME.Domain/
echo "Preparing application services..."
mkdir -p src/$PROJECT_NAME.Application/Services
echo "Updating package references..."
find . -name "*.csproj" -exec sed -i 's/Abp\./Volo.Abp./g' {} \;
echo "Running initial setup..."
cd $PROJECT_NAME
dotnet restore
dotnet ef database update
echo "Migration setup complete! Manual conversion required for:"
echo "1. Application services"
echo "2. Controllers"
echo "3. Views/UI components"
echo "4. Configuration files"
echo "5. Custom business logic"
Common Migration Issues
Issue Resolution Guide
1. Namespace Conflicts
Problem: Ambiguous references between old and new ABP namespaces
Solution: Use fully qualified names or alias directives
using OldAbp = Abp;
using NewAbp = Volo.Abp;
2. Entity Key Type Changes
Problem: ABP uses Guid keys by default
Solution: Update entities to use Guid keys or configure custom keys
public class Product : FullAuditedAggregateRoot<int>
{
}
3. Async/Await Pattern Requirements
Problem: ABP requires async patterns for repository operations
Solution: Update all repository calls to async
var products = _repository.GetAllList();
var products = await _repository.GetListAsync();
4. Authorization Attribute Changes
Problem: Authorization attributes have different names
Solution: Update attribute references
[AbpAuthorize("Products.Create")]
[Authorize("Products.Create")]
Post-Migration Tasks
1. Performance Optimization
- Review and optimize database queries
- Implement proper caching strategies
- Update to modern async patterns
- Optimize entity configurations
2. Security Hardening
- Update authentication configurations
- Review permission definitions
- Implement proper CORS policies
- Update security headers
3. UI Modernization
- Migrate to LeptonX theme
- Update JavaScript libraries
- Implement responsive design
- Optimize for mobile devices
4. Testing Enhancement
- Update test projects
- Implement integration tests
- Add performance tests
- Set up CI/CD pipeline
Validation Checklist
Pre-Deployment Validation
Production Readiness
Timeline Estimation
Small Projects (< 100 entities)
- Assessment: 1-2 days
- Migration: 3-5 days
- Testing: 2-3 days
- Total: 1-2 weeks
Medium Projects (100-500 entities)
- Assessment: 3-5 days
- Migration: 2-3 weeks
- Testing: 1-2 weeks
- Total: 4-6 weeks
Large Projects (> 500 entities)
- Assessment: 1-2 weeks
- Migration: 2-3 months
- Testing: 1-2 months
- Total: 4-7 months
This migration guide provides comprehensive steps to successfully transition from ASP.NET Boilerplate to ABP.IO while maintaining functionality and improving code quality.