ABP development workflow - step-by-step guide for adding new entities, migrations, application services, localization, permissions, and tests. Use when adding new features or entities to an ABP project.
Tutorials: https://abp.io/docs/latest/tutorials
Create entity (location varies by template: *.Domain/Entities/ for layered, Entities/ for single-layer/microservice):
public class Book : AggregateRoot<Guid>
{
public string Name { get; private set; }
public decimal Price { get; private set; }
public Guid AuthorId { get; private set; }
protected Book() { }
public Book(Guid id, string name, decimal price, Guid authorId) : base(id)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
SetPrice(price);
AuthorId = authorId;
}
public void SetPrice(decimal price)
{
Price = Check.Range(price, nameof(price), 0, 9999);
}
}
Add constants and enums in *.Domain.Shared/:
public static class BookConsts
{
public const int MaxNameLength = 128;
}
public enum BookType
{
Novel,
Science,
Biography
}
Define custom repository in *.Domain/ only if you need custom query methods. For simple CRUD, use generic IRepository<Book, Guid> directly:
// Only if custom queries are needed
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name);
}
In *.EntityFrameworkCore/:
DbContext:
public DbSet<Book> Books { get; set; }
OnModelCreating:
builder.Entity<Book>(b =>
{
b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(BookConsts.MaxNameLength);
b.HasIndex(x => x.Name);
});
Repository Implementation (only if custom interface defined):
public class BookRepository : EfCoreRepository<MyDbContext, Book, Guid>, IBookRepository
{
public BookRepository(IDbContextProvider<MyDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<Book> FindByNameAsync(string name)
{
return await (await GetDbSetAsync())
.FirstOrDefaultAsync(b => b.Name == name);
}
}
See abp-ef-core skill for migration commands. Recommended: use DbMigrator project to apply migrations and seed data.
Create DTOs and service interface:
// DTOs
public class BookDto : EntityDto<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
public Guid AuthorId { get; set; }
}
public class CreateBookDto
{
[Required]
[StringLength(BookConsts.MaxNameLength)]
public string Name { get; set; }
[Range(0, 9999)]
public decimal Price { get; set; }
[Required]
public Guid AuthorId { get; set; }
}
// Service Interface
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync(Guid id);
Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input);
Task<BookDto> CreateAsync(CreateBookDto input);
}
ABP supports both Mapperly and AutoMapper. Prefer the provider already used in the solution.
If the solution uses Mapperly, create a mapper in the Application project:
[Mapper]
public partial class BookMapper
{
public partial BookDto MapToDto(Book book);
public partial List<BookDto> MapToDtoList(List<Book> books);
}
Register in module:
context.Services.AddSingleton<BookMapper>();
Implement service (using generic repository - use IBookRepository if you defined custom interface in step 3):
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _bookRepository; // Or IBookRepository
private readonly BookMapper _bookMapper;
public BookAppService(
IRepository<Book, Guid> bookRepository,
BookMapper bookMapper)
{
_bookRepository = bookRepository;
_bookMapper = bookMapper;
}
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return _bookMapper.MapToDto(book);
}
[Authorize(MyProjectPermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = new Book(
GuidGenerator.Create(),
input.Name,
input.Price,
input.AuthorId
);
await _bookRepository.InsertAsync(book);
return _bookMapper.MapToDto(book);
}
}
In *.Domain.Shared/Localization/*/en.json:
{
"Book": "Book",
"Books": "Books",
"BookName": "Name",
"BookPrice": "Price"
}
public static class MyProjectPermissions
{
public static class Books
{
public const string Default = "MyProject.Books";
public const string Create = Default + ".Create";
}
}
public class BookAppService_Tests : MyProjectApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[Fact]
public async Task Should_Create_Book()
{
var result = await _bookAppService.CreateAsync(new CreateBookDto
{
Name = "Test Book",
Price = 19.99m
});
result.Id.ShouldNotBe(Guid.Empty);
result.Name.ShouldBe("Test Book");
}
}