Logo of Our Company, link to homepage
Schedule a free consultation
Software Development

From ASP.NET Boilerplate to ABP Framework – A Complete Enterprise App Migration Guide

  • Written on30 Apr 2026
  • Overview
      • C#

        # Install ABP CLI
        dotnet tool install -g Volo.Abp.Studio.Cli
         
        # OR use the older ABP CLI (open source)
        dotnet tool install -g Volo.Abp.Cli
         
        # Create a Layered Monolith solution
        abp new MyApp --template app --ui mvc --database-provider ef
         
        # For microservice solution:
        abp new MyApp --template microservice 

      • C#

        public class Product : Entity  // int Id assumed
        {
            public string Name { get; set; }
            public decimal Price { get; set; } 
        }

      • C#

        public class Product : AggregateRoot<Guid>  // Guid Id, explicit
        {
            public string Name { get; private set; }
            public decimal Price { get; private set; }
         
            // ABP DDD: protect state via constructor + methods
            public Product(Guid id, string name, decimal price)
                : base(id)
            {
                Name = Check.NotNullOrEmpty(name, nameof(name));
                Price = price;
            }
         
            public void UpdatePrice(decimal newPrice)
            {
                Price = newPrice;
            }
        }

      • C#

        // Before
        public class Invoice : Entity<int>, IMustHaveTenant
        {
            public int TenantId { get; set; }  // int, required
        }
         
        // After
        public class Invoice : AggregateRoot<Guid>, IMultiTenant
        {
            public Guid? TenantId { get; set; }  // Guid, nullable
        }

      • C#

        // Before
        public class Order : FullAuditedEntity<Guid>
        {  }
         
        // After
        public class Order : FullAuditedAggregateRoot<Guid>
        {  }

      • C#

        // BEFORE — ASP.NET Boilerplate
        public class ProductAppService : ApplicationService
        {
            private readonly IRepository<Product, int> _repo;
         
            public async Task<List<ProductDto>> GetExpensiveProductsAsync()
            {
                var products = _repo.GetAll()  // sync IQueryable
                    .Where(p => p.Price > 1000)
                    .ToList();  // sync execution
                return ObjectMapper.Map<List<ProductDto>>(products);
            }
        }
         
        // AFTER — ABP Framework
        public class ProductAppService : ApplicationService
        {
            private readonly IRepository<Product, Guid> _repo;  // Guid not int
         
            public async Task<List<ProductDto>> GetExpensiveProductsAsync()
            {
                var queryable = await _repo.GetQueryableAsync();
                var products = await queryable
                    .Where(p => p.Price > 1000)
                    .ToListAsync();  // async execution
                return ObjectMapper.Map<List<ProductDto>>(products);
            }
        }

      • C#

        // BEFORE
        using (_unitOfWorkManager.Current.SetTenantId(tenantId))
        {
            var orders = await _orderRepo.GetAllListAsync();
        }
         
        // AFTER
        using (_currentTenant.Change(tenantId))   // ICurrentTenant injected
        {
            var orders = await _orderRepo.GetListAsync();
        }
         
        // Switch to host side:
        using (_currentTenant.Change(null))
        {
            // host-side logic here
        }

      • C#

        // BEFORE
        public class ProductManager : DomainService
        {
            public Product CreateProduct(string name)
            {
                if (_repo.GetAll().Any(p => p.Name == name))
                    throw new UserFriendlyException("Duplicate name");
                return new Product { Name = name };
            }
        }
         
        // AFTER
        public class ProductManager : DomainService
        {
            private readonly IRepository<Product, Guid> _repo;
         
            public ProductManager(IRepository<Product, Guid> repo)
            {
                _repo = repo;
            }
         
            public async Task<Product> CreateProductAsync(string name)
            {
                var exists = await _repo.AnyAsync(p => p.Name == name);
                if (exists) throw new BusinessException("MyApp:ProductNameExists")
                    .WithData("Name", name);
         
                return new Product(GuidGenerator.Create(), name);
            }
        }

      • C#

        // BEFORE — AsyncCrudAppService
        public class ProductAppService
            : AsyncCrudAppService<Product, ProductDto, int, PagedResultRequestDto,
                                  CreateProductDto, UpdateProductDto>
        {
            public ProductAppService(IRepository<Product, int> repo)
                : base(repo) { }
         
            // Old update: Id was inside UpdateProductDto
            public override Task<ProductDto> UpdateAsync(UpdateProductDto input)
            {
                // input.Id existed in old pattern
            }
        }
         
        // AFTER — CrudAppService (async-only)
        public class ProductAppService
            : CrudAppService<Product, ProductDto, Guid, PagedAndSortedResultRequestDto,
                             CreateProductDto, UpdateProductDto>
        {
            public ProductAppService(IRepository<Product, Guid> repo)
                : base(repo) { }
         
            // New update: Id is separate parameter
            public override async Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
            {
                var product = await Repository.GetAsync(id);
                product.SetName(input.Name);
                return await base.UpdateAsync(id, input);
            }
        }

      • C#

        // BEFORE
        [AbpAuthorize(ProductPermissions.Products.Delete)]
        public async Task DeleteAsync(int id) { ... }
         
        // AFTER
        [Authorize(ProductPermissions.Products.Delete)]
        public async Task DeleteAsync(Guid id) { ... }
         
        // Defining permissions — BEFORE (AuthorizationProvider)
        public class ProductAuthProvider : AuthorizationProvider
        {
            public override void SetPermissions(IPermissionDefinitionContext ctx)
            {
                ctx.CreatePermission("Products", L("Products"));
            }
        }
         
        // Defining permissions — AFTER (PermissionDefinitionProvider)
        public class ProductPermissionDefinitionProvider : PermissionDefinitionProvider
        {
            public override void Define(IPermissionDefinitionContext context)
            {
                var productsGroup = context.AddGroup(
                    ProductPermissions.GroupName, L("Permission:ProductManagement"));
         
                var products = productsGroup.AddPermission(
                    ProductPermissions.Products.Default, L("Permission:Products"));
         
                products.AddChild(
                    ProductPermissions.Products.Create, L("Permission:Products.Create"));
                products.AddChild(
                    ProductPermissions.Products.Delete, L("Permission:Products.Delete"));
            }
         
            private static LocalizableString L(string name) =>
                LocalizableString.Create<ProductResource>(name);
        }

      • C#

        // BEFORE
        var dto = ObjectMapper.Map<ProductDto>(product);    // source type inferred
        ObjectMapper.Map(input, existingProduct);           // source inferred
         
        // AFTER
        var dto = ObjectMapper.Map<Product, ProductDto>(product);
        ObjectMapper.Map<UpdateProductDto, Product>(input, existingProduct);
         
        // BEFORE — AutoMapTo attribute
        [AutoMapTo(typeof(Product))]
        public class CreateProductDto { ... }
         
        // AFTER — AutoMapper Profile
        public class ProductApplicationAutoMapperProfile : Profile
        {
            public ProductApplicationAutoMapperProfile()
            {
                CreateMap<CreateProductDto, Product>(
                    MemberList.Source);
                CreateMap<Product, ProductDto>();
                CreateMap<UpdateProductDto, Product>(
                    MemberList.Source)
                    .ForMember(x => x.Id, opt => opt.Ignore()); // Id set separately
            }
        }

      • C#

        // BEFORE
        public class CreateProductDto : ICustomValidate
        {
            public string Name { get; set; }
            public decimal Price { get; set; }
         
            public void AddValidationErrors(CustomValidationContext ctx)
            {
                if (Price < 0)
                    ctx.Results.Add(new ValidationResult("Price cannot be negative"));
            }
        }
         
        // AFTER
        public class CreateProductDto : IValidatableObject
        {
            [Required, MaxLength(128)]
            public string Name { get; set; }
         
            public decimal Price { get; set; }
         
            public IEnumerable<ValidationResult> Validate(ValidationContext ctx)
            {
                if (Price < 0)
                    yield return new ValidationResult(
                        "Price cannot be negative.", new[] { nameof(Price) });
            }
        }

      • C#

        // BEFORE — ASP.NET Boilerplate module
        [DependsOn(typeof(AbpEntityFrameworkCoreModule))]
        public class MyAppModule : AbpModule
        {
            public override void PreInitialize()
            {
                Configuration.Auditing.IsEnabled = true;
                IocManager.Register<IMyService, MyService>(DependencyLifeStyle.Transient);
            }
         
            public override void Initialize()
            {
                IocManager.RegisterAssemblyByConvention(typeof(MyAppModule).Assembly);
            }
        }
         
        // AFTER — ABP Framework module
        [DependsOn(
            typeof(AbpEntityFrameworkCoreModule),
            typeof(AbpAuditingModule)
        )]
        public class MyAppDomainModule : AbpModule
        {
            public override void ConfigureServices(ServiceConfigurationContext context)
            {
                // Configure framework options
                Configure<AbpAuditingOptions>(options =>
                {
                    options.IsEnabled = true;
                    options.IsEnabledForGetRequests = false;
                });
         
                // Register custom services
                context.Services.AddTransient<IMyService, MyService>();
                // Convention-based registration is automatic
            }
         
            public override void OnApplicationInitialization(
                ApplicationInitializationContext context)
            {
                var app = context.GetApplicationBuilder();
                // Configure middleware pipeline here
                app.UseAuditing();
            }
        }

      • C#

        // Program.cs / Startup — wire up Autofac
        var builder = WebApplication.CreateBuilder(args);
        builder.Host.UseAutofac();  // <-- replaces Castle Windsor
        await builder.AddApplicationAsync<MyAppWebModule>();
        var app = builder.Build();
        await app.InitializeApplicationAsync();
        await app.RunAsync();

      • C#

        // BEFORE
        public class MyService : ApplicationService
        {
            public void DoSomething()
            {
                var userId = AbpSession.UserId;   // long?
                var tenantId = AbpSession.TenantId; // int?
            }
        }
         
        // AFTER — CurrentUser/CurrentTenant are base properties on ApplicationService
        public class MyService : ApplicationService
        {
            public void DoSomething()
            {
                var userId = CurrentUser.Id;        // Guid?
                var tenantId = CurrentTenant.Id;    // Guid?
                var username = CurrentUser.UserName; // string
                var email = CurrentUser.Email;       // string
                var roles = CurrentUser.Roles;       // string[]
            }
        }

      • HTML

        <!-- MyApp.xml -->
        <localizationDictionary culture="en">
            <texts>
                <text name="HelloWorld">Hello World!</text>
                <text name="ProductCreated">Product created successfully.</text>
            </texts>
        </localizationDictionary>

      • Json

        // en.json — place in /Localization/MyApp/en.json
        {
          "culture": "en",
          "texts": {
            "HelloWorld": "Hello World!",
            "ProductCreated": "Product created successfully.",
            "Permission:Products": "Products",
            "Permission:Products.Create": "Create Products",
            "MyApp:ProductNameExists": "A product with name '{0}' already exists."
          }
        }

      • C#

        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Add<MyAppResource>("en")  // default culture
                .AddBaseTypes(typeof(AbpValidationResource))
                .AddVirtualJson("/Localization/MyApp");
         
            options.DefaultResourceType = typeof(MyAppResource);
        });

      • C#

        // BEFORE
        public class MyService : ApplicationService
        {
            public string GetHello()
            {
                return L("HelloWorld");  // AbpModule L() method
            }
        }
         
        // AFTER
        public class MyService : ApplicationService
        {
            // IStringLocalizer injected through ApplicationService base
            public string GetHello()
            {
                return L["HelloWorld"];  // index into IStringLocalizer
            }
        }

      • C#

        // BEFORE — SettingProvider
        public class MySettingProvider : SettingProvider
        {
            public override IEnumerable<SettingDefinition> GetSettingDefinitions(
                SettingDefinitionProviderContext ctx)
            {
                yield return new SettingDefinition(
                    MySettings.MaxProductCount,
                    defaultValue: "100",
                    scopes: SettingScopes.Application);
            }
        }
        // Then add to: Configuration.Settings.Providers.Add<MySettingProvider>()
         
        // AFTER — SettingDefinitionProvider (auto-discovered)
        public class MySettingDefinitionProvider : SettingDefinitionProvider
        {
            public override void Define(ISettingDefinitionContext context)
            {
                context.Add(
                    new SettingDefinition(
                        MySettings.MaxProductCount,
                        defaultValue: "100",
                        displayName: L("Setting:MaxProductCount"),
                        isVisibleToClients: true  // expose to browser if needed
                    )
                );
            }
        }
         
        // Reading settings:
        // BEFORE: ISettingManager.GetSettingValue(MySettings.MaxProductCount)
        // AFTER:  ISettingProvider.GetOrNullAsync(MySettings.MaxProductCount)
        var maxCount = await SettingProvider.GetOrNullAsync(MySettings.MaxProductCount);

      • C#

        // BEFORE — publishing
        EventBus.Default.Trigger(new ProductCreatedEventData { ProductId = id });
        // or injected:
        _eventBus.Trigger(new ProductCreatedEventData { ProductId = id });
         
        // AFTER — Local event bus
        await _localEventBus.PublishAsync(new ProductCreatedEto { ProductId = id });
         
        // BEFORE — handler
        public class ProductCreatedHandler : IEventHandler<ProductCreatedEventData>,
                                             ITransientDependency
        {
            public void HandleEvent(ProductCreatedEventData data) { ... }
        }
         
        // AFTER — local handler
        public class ProductCreatedHandler
            : ILocalEventHandler<ProductCreatedEto>, ITransientDependency
        {
            public async Task HandleEventAsync(ProductCreatedEto data)
            {
                // handle the event
            }
        }

      • C#

        // BEFORE
        var cache = _cacheManager.GetCache("Products");
        var product = cache.Get("product_1", () => GetFromDb(1));
         
        // AFTER
        public class ProductAppService : ApplicationService
        {
            private readonly IDistributedCache<ProductCacheItem> _cache;
         
            public async Task<ProductCacheItem> GetProductAsync(Guid id)
            {
                return await _cache.GetOrAddAsync(
                    id.ToString(),
                    async () => {
                        var product = await _repo.GetAsync(id);
                        return ObjectMapper.Map<Product, ProductCacheItem>(product);
                    },
                    () => new DistributedCacheEntryOptions
                    {
                        AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
                    }
                );
            }
        }

      • C#

        // BEFORE
        using Castle.Core.Logging;
         
        public class ProductAppService : ApplicationService
        {
            public ILogger Logger { get; set; } = NullLogger.Instance;
         
            public void Create()
            {
                Logger.Info("Creating product...");
            }
        }
         
        // AFTER
        using Microsoft.Extensions.Logging;
         
        public class ProductAppService : ApplicationService
        {
            // Logger<T> is a base property on ApplicationService in ABP
            // No need to declare it — just use Logger directly
         
            public async Task CreateAsync()
            {
                Logger.LogInformation("Creating product at {Time}", DateTime.UtcNow);
            }
        }

      • C#

        // BEFORE
        public class MyAppDbContext : AbpDbContext
        {
            public DbSet<Product> Products { get; set; }
         
            public MyAppDbContext(DbContextOptions<MyAppDbContext> options)
                : base(options) { }
         
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
                modelBuilder.Entity<Product>(b => {
                    b.Property(x => x.Name).HasMaxLength(128);
                });
            }
        }
         
        // AFTER
        [ConnectionStringName("Default")]
        public class MyAppDbContext
            : AbpDbContext<MyAppDbContext>  // typed generic
        {
            public DbSet<Product> Products { get; set; }
         
            public MyAppDbContext(DbContextOptions<MyAppDbContext> options)
                : base(options) { }
         
            protected override void OnModelCreating(ModelBuilder builder)
            {
                base.OnModelCreating(builder);   // CRITICAL — sets up ABP conventions
         
                builder.ConfigureMyApp();        // extension method pattern
            }
        }
         
        // ModelBuilderExtensions.cs
        public static class MyAppDbContextModelCreatingExtensions
        {
            public static void ConfigureMyApp(this ModelBuilder builder)
            {
                builder.Entity<Product>(b =>
                {
                    b.ToTable(MyAppConsts.DbTablePrefix + "Products",
                              MyAppConsts.DbSchema);
                    b.ConfigureByConvention();   // Applies ABP audit, soft delete, MT
                    b.Property(x => x.Name).HasMaxLength(ProductConsts.MaxNameLength)
                                            .IsRequired();
                });
            }
        }

      • C#

        # Add new migration for your ABP solution
        cd src/MyApp.EntityFrameworkCore
        dotnet ef migrations add Initial_ABP_Migration
         
        # Apply migration
        dotnet ef database update
         
        # Or use the DbMigrator project (recommended for ABP)
        cd src/MyApp.DbMigrator
        dotnet run

      • C#

        // BEFORE — NavigationProvider
        public class AppNavigationProvider : NavigationProvider
        {
            public override void SetNavigation(INavigationProviderContext ctx)
            {
                ctx.Manager.MainMenu
                    .AddItem(new MenuItemDefinition("Products", L("Products"),
                        url: "/Products",
                        requiredPermissionName: "Products"));
            }
        }
         
        // AFTER — IMenuContributor (executed on every render)
        public class MyAppMenuContributor : IMenuContributor
        {
            public async Task ConfigureMenuAsync(MenuConfigurationContext context)
            {
                if (context.Menu.Name != StandardMenus.Main) return;
         
                var l = context.GetLocalizer<MyAppResource>();
         
                if (await context.IsGrantedAsync(ProductPermissions.Products.Default))
                {
                    context.Menu.AddItem(
                        new ApplicationMenuItem(
                            "MyApp.Products",
                            l["Menu:Products"],
                            "/Products",
                            icon: "fas fa-box"
                        )
                    );
                }
            }
        }
         
        // Register in module:
        Configure<AbpNavigationOptions>(options =>
        {
            options.MenuContributors.Add(new MyAppMenuContributor());
        });

      • C#

        // OLD — MVC Controller + View
        public class ProductsController : AbpController
        {
            public ActionResult Index()
            {
                var products = _productService.GetAll();
                return View(products);
            }
        }
         
        // NEW — Razor Page Model (/Pages/Products/Index.cshtml.cs)
        public class IndexModel : MyAppPageModel  // inherits AbpPageModel
        {
            private readonly IProductAppService _productService;
         
            public IndexModel(IProductAppService productService)
            {
                _productService = productService;
            }
         
            public async Task OnGetAsync()
            {
                // Data loaded via AJAX + ABP dynamic API proxies in most cases
            }
        }

      • C#

        // AFTER — Register bundles in module
        Configure<AbpBundlingOptions>(options =>
        {
            options.ScriptBundles
                .Configure(StandardBundles.Scripts.Global, bundle =>
                {
                    bundle.AddFiles("/Pages/Products/index.js");
                });
         
            options.StyleBundles
                .Configure(StandardBundles.Styles.Global, bundle =>
                {
                    bundle.AddFiles("/Pages/Products/index.css");
                });
        });

      • SQL

        -- Example SQL snippet: convert int UserId to Guid
        -- Step 1: Create mapping
        CREATE TABLE UserIdMapping (
            OldIntId INT NOT NULL,
            NewGuid UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID()
        );
        INSERT INTO UserIdMapping (OldIntId)
        SELECT Id FROM [OldDb].[dbo].[AbpUsers];
         
        -- Step 2: Migrate your table using mapping
        INSERT INTO [NewDb].[dbo].[Products]
           (Id, TenantId, CreatorId, Name, Price)
        SELECT
           NEWID(),
           tm.NewGuid,   -- mapped TenantId
           um.NewGuid,   -- mapped CreatorUserId
           p.Name,
           p.Price
        FROM [OldDb].[dbo].[Products] p
        JOIN TenantIdMapping tm ON tm.OldIntId = p.TenantId
        JOIN UserIdMapping   um ON um.OldIntId = p.CreatorUserId;

      • cd MyApp/src/MyApp.DbMigrator
        dotnet run   # Seeds initial data, admin user, roles
         
        cd ../MyApp.Web
        dotnet run   # Start the web app

      Share this article