docs/en/Community-Articles/2026-02-19-ABP-Framework-Hidden-Magic/post.md
The ABP Framework is famous for its Convention-over-Configuration approach, which means a lot of things work automatically without explicit configuration. In this article, I'll uncover these "hidden magics" that make ABP so powerful but often go unnoticed by developers.
The Magic: Any class implementing ITransientDependency, ISingletonDependency, or IScopedDependency is automatically registered with the corresponding lifetime.
// This is automatically registered as Transient - no configuration needed!
public class MyService : IMyService, ITransientDependency
{
public void DoSomething() { }
}
Where it happens: Volo.Abp.Core/Volo/Abp/DependencyInjection/ConventionalRegistrarBase.cs
The framework scans all assemblies and automatically determines service lifetime from class hierarchy. This is why you rarely need to manually register services in ABP.
The Magic: When you register a service, it's automatically registered as both itself AND all its implemented interfaces.
public class UserService : IUserService, IValidationInterceptor
{
// Registered as both IUserService AND IValidationInterceptor
// No ExposeServices attribute needed!
}
Where it happens: Volo.Abp.Core/DependencyInjection/ExposedServiceExplorer.cs:9-14
private static readonly ExposeServicesAttribute DefaultExposeServicesAttribute =
new ExposeServicesAttribute
{
IncludeDefaults = true,
IncludeSelf = true
};
The Magic: Every application service method parameters are automatically validated - you don't need to add [Validate] attributes.
Where it happens: Volo.Abp.Validation/ValidationInterceptorRegistrar.cs
The ValidationInterceptor is automatically added to the interceptor pipeline for all services. Every method call triggers automatic validation of input parameters.
The Magic: Every database operation is automatically wrapped in a transaction. You don't need to explicitly configure unit of work for most scenarios.
Where it happens: The UnitOfWorkInterceptor is auto-registered and automatically:
The Magic: Auditing is ON by default, even for anonymous users!
public class AbpAuditingOptions
{
public AbpAuditingOptions()
{
IsEnabled = true; // Enabled by default!
IsEnabledForAnonymousUsers = true; // Anonymous users are audited!
HideErrors = true; // Errors are silently hidden
AlwaysLogOnException = true; // Exceptions always logged
}
}
Where it happens: Volo.Abp.Auditing/AbpAuditingOptions.cs:73-91
This means every entity change and service call is logged automatically unless explicitly disabled.
The Magic: Security logging is enabled by default in ABP!
public AbpSecurityLogOptions()
{
IsEnabled = true; // Hidden: ON by default!
}
Every authentication attempt, authorization failure, and security-relevant action is logged automatically.
The Magic: ISoftDelete and IMultiTenant filters are enabled by default.
// In DataFilter.cs - Line 103
_filter.Value = _options.DefaultStates.GetOrDefault(typeof(TFilter))?.Clone()
?? new DataFilterState(true); // true = enabled!
This means:
You must explicitly disable these filters when you need to access all data:
using (_dataFilter.Disable<IMultiTenant>())
{
// Query all tenants
}
The Magic: Starting with ABP v9.0, new project templates use Mapperly instead of AutoMapper. Any class using Mapperly attributes is automatically configured.
// Starting with ABP v10.0, new projects use Mapperly instead of AutoMapper
// Inherit from MapperBase - automatically registered with IObjectMapper
public partial class UserMapper : MapperBase<User, UserDto>
{
public override partial UserDto Map(User source);
}
// For two-way mapping
public partial class UserTwoWayMapper : TwoWayMapperBase<User, UserDto>
{
public override partial UserDto Map(User source);
public override partial User ReverseMap(UserDto source);
}
The mapping is done at compile-time (no reflection overhead), and it's automatically registered with ABP's IObjectMapper.
Where it happens: Volo.Abp.Mapperly/AbpMapperlyConventionalRegistrar.cs
// Automatically discovers and configures all Mapperly mappers
context.Services.OnRegistered(context =>
{
if (typeof(MapperBase).IsAssignableFrom(context.ImplementationType))
{
// Register the mapper
}
});
The Magic: Any class implementing IDataSeedContributor is automatically discovered and executed on application startup.
// Automatically discovered and run on startup!
public class MyDataSeeder : IDataSeedContributor
{
public Task SeedAsync(DataSeedContext context)
{
// Seed data here
}
}
Where it happens: Volo.Abp.Data/AbpDataModule.cs:40-56
The Magic: These are all auto-discovered without any configuration:
ISettingDefinitionProvider - SettingsIPermissionDefinitionProvider - PermissionsIFeatureDefinitionProvider - FeaturesINavigationProvider - Navigation itemsThe Magic: Any class implementing IWidget is automatically registered and can be rendered in pages.
Where it happens: Volo.Abp.AspNetCore.Mvc.UI.Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs
The Magic: All API controllers have remote service functionality enabled by default:
public class RemoteServiceAttribute : Attribute
{
public bool IsEnabled { get; set; } = true; // Enabled by default!
}
The Magic: When you create an application service (class implementing an interface or inheriting from ApplicationService), ABP automatically creates REST API endpoints for it - no manual controller needed!
// This interface is automatically exposed as /api/app/product
public interface IProductAppService
{
Task<List<ProductDto>> GetListAsync();
Task<ProductDto> CreateAsync(CreateProductDto input);
Task DeleteAsync(Guid id);
}
// The implementation automatically becomes an API Controller
public class ProductAppService : ApplicationService, IProductAppService
{
public Task<List<ProductDto>> GetListAsync() { ... }
public Task<ProductDto> CreateAsync(CreateProductDto input) { ... }
public Task DeleteAsync(Guid id) { ... }
}
// Available endpoints (auto-generated):
// GET /api/app/product
// POST /api/app/product
// DELETE /api/app/product/{id}
Where it happens: Volo.Abp.AspNetCore.Mvc/AbpServiceConvention.cs
The framework:
The Magic: On the client side, you don't need to write HTTP client code. ABP automatically generates Dynamic JavaScript Proxies and Dynamic C# Proxies that let you call your APIs as if they were local method calls!
JavaScript (MVC/Razor Pages):
// Just call it like a local function!
var products = await productAppService.getList();
await productAppService.create({ name: "New Product" });
await productAppService.delete(id);
C# (Blazor/Console Apps):
// Inject and use like local method calls!
public class ProductListModel : PageModel
{
private readonly IProductAppService _productAppService;
public async Task OnGetAsync()
{
// Actually makes HTTP call to the server!
var products = await _productAppService.GetListAsync();
}
}
Where it happens:
Volo.Abp.AspNetCore.Mvc.UI - Dynamic JavaScript proxiesVolo.Abp.AspNetCore.Mvc.Client - Dynamic C# HTTP clientsThis is why you can inject application service interfaces directly in Blazor and call them like local methods!
By default, all application service methods and controllers are public and accessible. Add [Authorize] or [AbpAuthorize] to restrict access:
[Authorize]
public async Task CreateAsync(CreateDto input) { }
[AbpAuthorize("MyApp.Permissions.CanCreate")]
public async Task CreateAsync(CreateDto input) { }
The AuthorizationInterceptor is added only when [Authorize] attribute is present on the class or method.
The Magic: Background workers are enabled by default, and any class implementing IBackgroundWorker or IQuartzBackgroundWorker is auto-registered.
The Magic: ABP automatically detects the best ID generation strategy based on the entity type:
Guid → Auto-generates GUIDint/long → Database identitystring → No auto-generation (must provide)Where it happens: Volo.Abp.Ddd.Domain/Entities/EntityHelper.cs
The Magic: ABP automatically handles CSRF protection with these hardcoded values:
// Blazor Client
private const string AntiForgeryCookieName = "XSRF-TOKEN";
private const string AntiForgeryHeaderName = "RequestVerificationToken";
The Magic: Any class implementing IEventHandler<TEvent> is automatically subscribed to handle events - no manual registration needed!
// This handler is automatically registered when the assembly loads!
public class OrderCreatedHandler : IEventHandler<OrderCreatedEvent>
{
public Task HandleEventAsync(OrderCreatedEvent eventData)
{
// Handle the event - automatically subscribed!
}
}
The Magic: Events are not fired immediately - they're collected during the unit of work and fired at the end when everything succeeds!
// In UnitOfWorkEventPublisher.cs
// Events are queued and published only when UOW successfully completes
await _localEventBus.PublishAsync(
entityChangeEvent,
onUnitOfWorkComplete: true // Wait for UOW to complete!
);
This ensures transactional consistency - if your UOW fails, no events are fired.
The Magic: ABP implements the Outbox Pattern automatically for distributed events, ensuring no events are lost!
// In DistributedEventBusBase.cs
// Events are stored in outbox table and processed reliably
foreach (var outboxConfig in AbpDistributedEventBusOptions.Outboxes.Values.OrderBy(x => x.Selector is null))
{
// Outbox processing happens automatically
}
The Magic: Any property decorated with [DisableAuditing] is automatically excluded from audit logs without any configuration!
// This property is automatically excluded from auditing
[DisableAuditing]
public string SecretData { get; set; }
The Magic: ABP provides a virtual file system that merges embedded resources from all modules into a single virtual path!
// Any file embedded as "EmbeddedResource" is accessible virtually
// No configuration needed for module authors!
Where it happens: Volo.Abp.VirtualFileSystem/AbpVirtualFileSystemModule.cs
This is how ABP modules include static files (CSS, JS, images) that work without copying to wwwroot.
The Magic: ABP pre-configures JSON serialization with:
All configured automatically.
The Magic: All .json localization files in the application are automatically discovered and loaded:
/Localization/MyApp/
en.json
tr.json
de.json
No explicit registration needed - just add files and they're available!
Add [RequiresFeature] to restrict access based on feature flags:
[RequiresFeature("MyApp.Features.SomeFeature")]
public async Task DoSomethingAsync()
{
}
The FeatureInterceptor is added only when [RequiresFeature] attribute is present on the class or method.
The Magic: ABP automatically handles API versioning with sensible defaults:
1.0/api/v1/...Accept: application/json;v=1.0All configured automatically unless overridden.
The Magic: Health check endpoints are auto-registered:
/health - Overall health status/health/ready - Readiness check/health/live - Liveness checkIncludes automatic checks for:
The Magic: If you reference Volo.Abp.AspNetCore.Mvc.UI.Swagger, Swagger UI is automatically generated with:
No configuration needed beyond the package reference!
The Magic: Background jobs are automatically retried with exponential backoff:
// Jobs are automatically:
// - Queued when published
// - Retried on failure (3 times default)
// - Delayed with exponential backoff
Where it happens: Volo.Abp.BackgroundJobs/AbpBackgroundJobOptions.cs
| # | Feature | Default Behavior | You Need to Know |
|---|---|---|---|
| 1 | Service Registration | Auto by interface | Implement ITransientDependency |
| 2 | Service Exposure | Self + all interfaces | Default is generous |
| 3 | Validation | All methods validated | Happens automatically |
| 4 | Unit of Work | Transactional by default | Auto-commits/rollbacks |
| 5 | Auditing | Enabled + anonymous users | Can disable per entity/method |
| 6 | Security Log | Always on | Can configure what to log |
| 7 | Soft Delete Filter | Enabled by default | Must disable to query deleted |
| 8 | Multi-Tenancy Filter | Enabled by default | Must disable for host data |
| 9 | Object Mapping | Mapperly (compile-time) | Inherit from MapperBase |
| 10 | Data Seeds | Auto-discovery | Implement IDataSeedContributor |
| 11 | Remote Services | Enabled by default | Can disable per service/method |
| 12 | Auto API Controllers | App services → REST APIs | No manual controller needed |
| 13 | Dynamic Client Proxies | Auto-generated | Call APIs like local methods |
| 14 | Permissions | NOT automatic | Must add [Authorize] |
| 15 | Settings | Auto-discovery | Define via ISettingDefinitionProvider |
| 16 | Features | NOT automatic | Must add [RequiresFeature] |
| 17 | Background Workers | Auto-registration | Implement IBackgroundWorker |
| 18 | Entity ID Generation | Auto by type | Guid, int, string strategies |
| 19 | Anti-Forgery | Auto-enabled | Token cookie/header handling |
| 20 | Event Handlers | Auto-discovery | Implement IEventHandler<TEvent> |
| 21 | UOW Events | Deferred execution | Transactional consistency |
| 22 | Distributed Events | Outbox pattern | Reliable messaging |
| 23 | Virtual Files | Module merging | Embedded resources as virtual |
| 24 | JSON Settings | Pre-configured | CamelCase, null handling |
| 25 | Tenant Resolution | Multi-source chain | Route → Query → Header → Cookie → Subdomain |
| 26 | Localization | Auto-discovery | JSON files in /Localization |
| 27 | API Versioning | Default v1.0 | URL, header, query support |
| 28 | Health Checks | Auto-registered | /health, /health/ready, /health/live |
| 29 | Swagger | Auto-generated | With authorization support |
| 30 | Background Job Queue | Auto with backoff | 3 retries default |
| 31 | Widgets | Auto-discovery | Implement IWidget |
ABP Framework's hidden magic is what makes it so productive to use. These conventions allow developers to focus on business logic rather than boilerplate configuration. However, understanding these defaults is crucial for:
The next time something "just works" in ABP, there's likely a hidden convention behind it!
What hidden ABP magic have you discovered? Share your findings in the comments!