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

الانتقال إلى التثبيت

معلومات المصدر

المستودع
abpframework/abp-skills
آخر نشاط في المصدر
٢٠ يوليو ٢٠٢٦ في ٠٣:٢٤
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٨
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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.
عرض على GitHub