| name | register-and-replace-services |
| description | Apply ABP's advanced dependency-injection conventions and controlled service customization. USE FOR: precise conventional exposure and lifetime rules; ExposeServices and Dependency attributes; replacing registrations; OnExposing, OnRegistered, and OnActivated callbacks; LazyServiceProvider/IAbpLazyServiceProvider; IObjectAccessor; keyed or string-named services; preserving lifetimes while decorating a service. DO NOT USE FOR: defining an AbpModule, its dependencies, and basic lifecycle or lifetime-marker registration (use define-application-modules); adding cross-cutting method interception (use use-interceptors-and-dynamic-proxy); replacing services specifically inside an installed pre-built module together with its entity/UI extensions (use customize-application-modules).
|
| license | MIT |
Registering and Replacing Services
ABP builds on IServiceCollection: conventional registrars choose a lifetime and exposed service types, while attributes and registration callbacks let a module change those choices. Use constructor injection for normal dependencies; use the advanced mechanisms here only when exposure, replacement, late resolution, keys, activation hooks, or decoration requires them.
When to Use
- Restrict or expand which service types a conventionally registered class exposes.
- Replace an existing interface/base-class registration without editing the original module.
- Observe or modify service exposure and registration during assembly scanning.
- Resolve optional services lazily or pass an object into startup/runtime code through
IObjectAccessor<T>.
- Register several implementations under keys, including string keys used as names.
- Wrap an existing implementation while preserving its registered lifetime.
When Not to Use
- Create a module or learn its lifecycle/basic DI markers — use define-application-modules.
- Attach method interceptors — use use-interceptors-and-dynamic-proxy.
- Customize a packaged module across entity, DTO, service, and UI extension points — use customize-application-modules.
How it works
Conventional registration and default exposure
ITransientDependency, IScopedDependency, and ISingletonDependency select the lifetime. Without an explicit exposure provider, ABP exposes the implementation type itself plus matching default interfaces. A default interface is one whose name matches the implementation suffix, ignoring an initial I and generic arity.
public interface IInvoiceCalculator
{
decimal Calculate(decimal netAmount);
}
public class InvoiceCalculator : IInvoiceCalculator, ITransientDependency
{
public decimal Calculate(decimal netAmount) => netAmount;
}
This exposes InvoiceCalculator and IInvoiceCalculator. An unrelated implemented interface is not a default service unless explicitly exposed.
Control exposure with [ExposeServices]
[ExposeServices(typeof(IInvoiceCalculator))]
public class InvoiceCalculator : IInvoiceCalculator, ITransientDependency
{
public decimal Calculate(decimal netAmount) => netAmount;
}
An explicit [ExposeServices] replaces default exposure unless IncludeDefaults = true; set IncludeSelf = true when the concrete class must also resolve. Multiple attributes are allowed.
Control lifetime, try-add, and replacement
[Dependency] has Lifetime, TryRegister, and ReplaceServices. The conventional registrar applies them in this order: replace, try-add, otherwise add.
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IInvoiceCalculator))]
public class DiscountInvoiceCalculator : IInvoiceCalculator, ITransientDependency
{
public decimal Calculate(decimal netAmount) => netAmount * 0.9m;
}
The imperative equivalent is useful when the replacement is selected by configuration:
context.Services.Replace(
ServiceDescriptor.Transient<IInvoiceCalculator, DiscountInvoiceCalculator>());
Match the old lifetime unless there is a reviewed reason to change it. Replace replaces the first descriptor with the same service type; inspect the collection when multiple registrations are intentional.
Registration callbacks
Install callbacks in PreConfigureServices so they observe conventionally scanned services.
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.OnExposing(exposingContext =>
{
if (typeof(IInternalService).IsAssignableFrom(exposingContext.ImplementationType))
{
exposingContext.ExposedTypes.RemoveAll(
service => service.ServiceType == exposingContext.ImplementationType);
}
});
context.Services.OnRegistered(registrationContext =>
{
});
}
OnExposing runs while conventional exposure is being built; its mutable ExposedTypes contains ServiceIdentifier entries.
OnRegistered receives ImplementationType, ServiceKey, and the interceptor type list. Use use-interceptors-and-dynamic-proxy before adding interceptors.
OnActivated(descriptor, callback) is descriptor-specific. With the Autofac integration, its callback receives the created Instance; it observes activation but does not replace that instance.
Lazy resolution
ABP has no ILazyServiceProvider type. ABP base classes expose a property named LazyServiceProvider whose type is IAbpLazyServiceProvider:
var clock = LazyServiceProvider.LazyGetRequiredService<IClock>();
IAbpLazyServiceProvider remains for compatibility; its own source recommends ITransientCachedServiceProvider for new code. Both are cached service providers: the first resolution is stored, including transient services, for that provider instance.
public class ReportBuilder : ITransientDependency
{
private readonly ITransientCachedServiceProvider _services;
public ReportBuilder(ITransientCachedServiceProvider services)
{
_services = services;
}
public IOptionalFormatter? GetFormatter()
{
return _services.GetService<IOptionalFormatter>();
}
}
Prefer constructor injection when the dependency is required. Lazy lookup hides dependencies and can defer lifetime/cycle failures until runtime.
Share an object with IObjectAccessor<T>
var accessor = context.Services.AddObjectAccessor(new StartupState());
accessor.Value.IsConfigured = true;
AddObjectAccessor inserts the same ObjectAccessor<T> instance as singleton registrations for both ObjectAccessor<T> and IObjectAccessor<T>. It throws if one was already registered; TryAddObjectAccessor<T>() returns the existing accessor instead. During configuration, GetObject<T>() / GetObjectOrNull<T>() retrieves a previously registered accessor value from the service collection.
Keyed and named services
ABP supports .NET keyed services in conventional registration:
[ExposeKeyedService<IInvoiceCalculator>("standard")]
public class StandardInvoiceCalculator : IInvoiceCalculator, ITransientDependency
{
public decimal Calculate(decimal netAmount) => netAmount;
}
public class CheckoutService
{
private readonly IInvoiceCalculator _calculator;
public CheckoutService(
[FromKeyedServices("standard")] IInvoiceCalculator calculator)
{
_calculator = calculator;
}
}
Resolve imperatively with GetRequiredKeyedService<T>(key). A string key is the named-service pattern in this version; there is no separate ABP INamedServiceProvider abstraction in the verified dependency-injection sources. If a class has only [ExposeKeyedService] and no [ExposeServices], ABP does not add its default unkeyed services.
Decorating a service (know the limits)
ABP core provides no general Decorate<T> extension. The descriptor-wrapping pattern below preserves only the original lifetime value — it does not preserve the original ABP registration. The inner service is rebuilt with ActivatorUtilities.CreateInstance, which bypasses the Autofac/ABP registration pipeline, so that inner instance loses ABP's dynamic-proxy interceptors (validation, unit of work, authorization, auditing, feature checks), convention-based property injection, and OnActivated callbacks. It also breaks the shared-instance identity when the type is exposed as both its concrete type and its interface (ConventionalRegistrarBase normally redirects those to one instance).
So use this pattern only for a simple implementation-type service that does not rely on any of those ABP behaviors. When you need to decorate a service that keeps ABP interception/property injection, use container-specific decoration (Autofac's RegisterDecorator) instead:
var descriptor = context.Services.LastOrDefault(
service => service.ServiceType == typeof(IInvoiceCalculator));
if (descriptor?.ImplementationType == null)
{
throw new InvalidOperationException(
"IInvoiceCalculator must use an implementation-type registration.");
}
context.Services.Replace(ServiceDescriptor.Describe(
typeof(IInvoiceCalculator),
serviceProvider => new LoggingInvoiceCalculator(
(IInvoiceCalculator)ActivatorUtilities.CreateInstance(
serviceProvider,
descriptor.ImplementationType)),
descriptor.Lifetime));
Do not apply this exact pattern to factory, instance, or keyed descriptors; their implementation data lives in different ServiceDescriptor properties.
Validation
- Resolve every intended service type (interface, concrete type, and key) and assert its runtime implementation.
- Inspect
IServiceCollection after configuration to confirm replacement count and lifetime.
- For singleton/scoped services exposed through multiple types, verify all paths resolve the same instance within the expected scope.
- Verify lazy/cached resolution twice, especially if the underlying service is transient.
- For decoration, test behavior, exception propagation, and preservation of the original lifetime.
Common Pitfalls
- Assuming every implemented interface is exposed — default exposure is name-based. Use
[ExposeServices] for unrelated interfaces.
- Forgetting
IncludeSelf — explicit exposure can make the concrete class unresolvable.
- Replacing under the wrong service type — replace every injection path that consumers actually use.
- Treating
IAbpLazyServiceProvider as a fresh resolve each time — it caches resolved services, including transients.
- Using service location for required dependencies — constructor injection makes requirements and cycles visible at startup.
- Expecting a keyed-only class to resolve unkeyed — add
[ExposeServices] explicitly if both forms are required.
- Decorating a factory/instance descriptor as if it had
ImplementationType — branch by descriptor shape or use container-specific decoration.