Skip to main content

register-and-replace-services

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).

Informações da origem

Repositório
abpframework/abp-skills
Última atividade na origem
20 de julho de 2026 às 03:24
Idioma detectado do SKILL.md
inglês
Estrelas
20
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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. ```csharp 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]` ```csharp [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. ```csharp [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: ```csharp 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. ```csharp 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 => { // Inspect ImplementationType and ServiceKey here. // Add IAbpInterceptor types only when method interception is intended. }); } ``` - `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`: ```csharp 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. ```csharp 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>` ```csharp 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: ```csharp [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: ```csharp 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.
Ver no GitHub