Hidden Magic: Things That Just Work Without You Knowing
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.
1. Automatic Service Registration Without Any Attributes
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.
2. All Interfaces Are Exposed By Default
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
};
3. Automatic Validation on Every Method
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.
4. Automatic Unit of Work Management
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:
- Begins transaction before method execution
- Commits on success
- Rolls back on exception
5. Auditing Is Enabled By Default
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.
6. Security Logging Is Always On
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.
7. Data Filters Are Enabled By Default
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:
- Deleted entities are automatically filtered out
- Multi-tenant data is automatically isolated
You must explicitly disable these filters when you need to access all data:
using (_dataFilter.Disable<IMultiTenant>())
{
// Query all tenants
}
8. Object Mapping (Mapperly - The New Standard)
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
}
});
9. Automatic Data Seed Contributor Discovery
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
10. Automatic Definition Provider Discovery
The Magic: These are all auto-discovered without any configuration:
ISettingDefinitionProvider- SettingsIPermissionDefinitionProvider- PermissionsIFeatureDefinitionProvider- FeaturesINavigationProvider- Navigation items
11. Automatic Widget Discovery
The 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
12. Remote Services Are Enabled By Default
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!
}
13. Auto API Controllers - Application Services Become REST APIs Automatically
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:
- Converts camelCase method names to kebab-case routes
- Maps HTTP methods automatically (Get→GET, Create→POST, Delete→DELETE)
- Generates proper DTOs from parameters and return types
- Handles serialization/deserialization
14. Dynamic Client Proxies - Client-Side Code Generated Automatically
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:
- JavaScript:
Volo.Abp.AspNetCore.Mvc.UI- Dynamic JavaScript proxies - C#:
Volo.Abp.AspNetCore.Mvc.Client- Dynamic C# HTTP clients
This is why you can inject application service interfaces directly in Blazor and call them like local methods!
15. Permission Checks
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.
16. Background Workers Auto-Registration
The Magic: Background workers are enabled by default, and any class implementing IBackgroundWorker or IQuartzBackgroundWorker is auto-registered.
17. Entity ID Generation
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
18. Anti-Forgery Token Magic
The Magic: ABP automatically handles CSRF protection with these hardcoded values:
// Blazor Client
private const string AntiForgeryCookieName = "XSRF-TOKEN";
private const string AntiForgeryHeaderName = "RequestVerificationToken";
19. Automatic Event Handler Discovery
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!
}
}
20. Unit of Work Events - Automatic Save
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.
21. Distributed Event Bus - Outbox Pattern
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
}
22. Automatic Object Extension Properties
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; }
23. Virtual File System
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.
24. Automatic JSON Serialization Settings
The Magic: ABP pre-configures JSON serialization with:
- Camel case property naming
- Null value handling
- Reference loop handling
- Custom converters for common types
All configured automatically.
25. Token-Based Configuration Magic
The Magic: Many settings support tokens that are resolved at runtime:
// These tokens are automatically replaced
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyApp_{TENANT_ID}"
}
{TENANT_ID}- Replaced with current tenant ID{USER_ID}- Replaced with current user ID{CURRENT_TIME}- Replaced with current datetime
26. Localization Automatic Discovery
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!
27. Feature Checks
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.
28. API Versioning Convention
The Magic: ABP automatically handles API versioning with sensible defaults:
- Default version:
1.0 - Version from URL path:
/api/v1/... - Version from header:
Accept: application/json;v=1.0
All configured automatically unless overridden.
29. Health Check Endpoints
The Magic: Health check endpoints are auto-registered:
/health- Overall health status/health/ready- Readiness check/health/live- Liveness check
Includes automatic checks for:
- Database connectivity
- Cache availability
- External services
30. Swagger/OpenAPI Auto-Configuration
The Magic: If you reference Volo.Abp.AspNetCore.Mvc.UI.Swagger, Swagger UI is automatically generated with:
- All API endpoints documented
- Authorization support
- Versioning support
- XML documentation
No configuration needed beyond the package reference!
31. Tenant Resolution Priority Chain
The Magic: ABP automatically resolves tenants in this order (first match wins):
- Route - From URL path (
/api/v1/[tenant]/...) - Query String - From
__tenantquery parameter - Header - From
__tenantheader - Cookie - From
__tenantcookie - Subdomain - From URL subdomain (
tenant.domain.com)
32. Background Job Queue Magic
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
Summary Table
| # | 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 | Token Replacement | Runtime resolution | {TENANT_ID}, {USER_ID} |
| 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 | Tenant Resolution | Multi-source chain | Route → Query → Header → Cookie |
| 31 | Job Retries | Auto with backoff | 3 retries default |
| 32 | Widgets | Auto-discovery | Implement IWidget |
Conclusion
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:
- Debugging - Knowing why certain behaviors happen
- Optimization - Disabling what you don't need
- Security - Understanding what's logged/audited by default
- Architecture - Following the intended patterns
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!
Comments
No one has commented yet, be the first to comment!