- 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
