.agents/skills/abp-core/SKILL.md
Documentation: https://abp.io/docs/latest API Reference: https://abp.io/docs/api/
IClock / Clock.Now instead of DateTime.Now / DateTime.UtcNowITransientDependency / ISingletonDependency instead of AddScoped/AddTransient/AddSingletonIRepository<T> instead of injecting DbContext directlyClock, CurrentUser, GuidGenerator, L) before injecting servicesBusinessException with namespaced error codes for domain rule violationsEvery ABP application/module has a module class that configures services:
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpEntityFrameworkCoreModule)
)]
public class MyAppModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Service registration and configuration
}
}
Note: Middleware configuration (
OnApplicationInitialization) should only be done in the final host application, not in reusable modules.
ABP automatically registers services implementing marker interfaces:
ITransientDependency → Transient lifetimeISingletonDependency → Singleton lifetimeIScopedDependency → Scoped lifetimeClasses inheriting from ApplicationService, DomainService, AbpController are also auto-registered.
You can use the generic IRepository<TEntity, TKey> for simple CRUD operations. Define custom repository interfaces only when you need custom query methods:
// Simple CRUD - Generic repository is fine
public class BookAppService : ApplicationService
{
private readonly IRepository<Book, Guid> _bookRepository; // ✅ OK for simple operations
}
// Custom queries needed - Define custom interface
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name); // Custom query
}
public class BookAppService : ApplicationService
{
private readonly IBookRepository _bookRepository; // ✅ Use custom when needed
}
[ExposeServices(typeof(IMyService))]
public class MyService : IMyService, ITransientDependency { }
| Base Class | Purpose |
|---|---|
Entity<TKey> | Basic entity with ID |
AggregateRoot<TKey> | DDD aggregate root |
DomainService | Domain business logic |
ApplicationService | Use case orchestration |
AbpController | REST API controller |
ABP base classes already inject commonly used services as properties. Before injecting a service, check if it's already available:
| Property | Available In | Description |
|---|---|---|
GuidGenerator | All base classes | Generate GUIDs |
Clock | All base classes | Current time (use instead of DateTime) |
CurrentUser | All base classes | Authenticated user info |
CurrentTenant | All base classes | Multi-tenancy context |
L (StringLocalizer) | ApplicationService, AbpController | Localization |
AuthorizationService | ApplicationService, AbpController | Permission checks |
FeatureChecker | ApplicationService, AbpController | Feature availability |
DataFilter | All base classes | Data filtering (soft-delete, tenant) |
UnitOfWorkManager | ApplicationService, DomainService | Unit of work management |
LoggerFactory | All base classes | Create loggers |
Logger | All base classes | Logging (auto-created) |
LazyServiceProvider | All base classes | Lazy service resolution |
Useful methods from base classes:
CheckPolicyAsync() - Check permission and throw if not grantedIsGrantedAsync() - Check permission without throwing.Result or .Wait()Async suffixCancellationToken in most cases (e.g., from HttpContext.RequestAborted)CancellationToken explicitly when implementing custom cancellation logicNever use DateTime.Now or DateTime.UtcNow directly. Use ABP's IClock service:
// In classes inheriting from base classes (ApplicationService, DomainService, etc.)
public class BookAppService : ApplicationService
{
public void DoSomething()
{
var now = Clock.Now; // ✅ Already available as property
}
}
// In other services - inject IClock
public class MyService : ITransientDependency
{
private readonly IClock _clock;
public MyService(IClock clock) => _clock = clock;
public void DoSomething()
{
var now = _clock.Now; // ✅ Correct
// var now = DateTime.Now; // ❌ Wrong - not testable, ignores timezone settings
}
}
Tip: Before injecting a service, check if it's already available as a property in your base classes.
Use BusinessException for domain rule violations with namespaced error codes:
throw new BusinessException("MyModule:BookNameAlreadyExists")
.WithData("Name", bookName);
Configure localization mapping:
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("MyModule", typeof(MyModuleResource));
});
ApplicationService, AbpController, etc.): Use L["Key"] - this is the IStringLocalizer propertyIStringLocalizer<TResource>Localization file location: *.Domain.Shared/Localization/{ResourceName}/{lang}.json
// Example: MyProject.Domain.Shared/Localization/MyProject/en.json
{
"culture": "en",
"texts": {
"Menu:Home": "Home",
"Welcome": "Welcome",
"BookName": "Book Name"
}
}
| Don't Use | Use Instead |
|---|---|
| Minimal APIs | ABP Controllers or Auto API Controllers |
| MediatR | Application Services |
DbContext directly in App Services | IRepository<T> |
AddScoped/AddTransient/AddSingleton | ITransientDependency, ISingletonDependency |
DateTime.Now | IClock / Clock.Now |
| Custom UnitOfWork | ABP's IUnitOfWorkManager |
| Manual HTTP calls from UI | ABP client proxies (generate-proxy) |
| Hardcoded role checks | Permission-based authorization |
| Business logic in Controllers | Application Services |