diff --git a/docs/en/Dependency-Injection.md b/docs/en/Dependency-Injection.md index d8afe5c21c..143ec8cc19 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -480,6 +480,24 @@ This example simply checks if the service class has `MyLogAttribute` attribute a > Notice that `OnRegistered` callback might be called multiple times for the same service class if it exposes more than one service/interface. So, it's safe to use `Interceptors.TryAdd` method instead of `Interceptors.Add` method. See [the documentation](Dynamic-Proxying-Interceptors.md) of dynamic proxying / interceptors. +### IServiceCollection.OnActivated Event + +The `OnActivated` event is raised once a service is fully constructed. Here you can perform application-level tasks that depend on the service being fully constructed - these should be rare. + +````csharp +var serviceDescriptor = ServiceDescriptor.Transient(); +services.Add(serviceDescriptor); +if (setIsReadOnly) +{ + services.OnActivated(serviceDescriptor, x => + { + x.Instance.As().IsReadOnly = true; + }); +} +```` + +> Notice that `OnActivated` event can be registered multiple times for the same `ServiceDescriptor`. + ## 3rd-Party Providers While ABP has no core dependency to any 3rd-party DI provider, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. diff --git a/docs/zh-Hans/Dependency-Injection.md b/docs/zh-Hans/Dependency-Injection.md index 2c22c123ab..4cc6616f52 100644 --- a/docs/zh-Hans/Dependency-Injection.md +++ b/docs/zh-Hans/Dependency-Injection.md @@ -310,6 +310,24 @@ public class AppModule : AbpModule > 注意, 如果服务类公开了多于一个服务或接口, `OnRegistered` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md). +### IServiceCollection.OnActivated 事件 + +一旦服务完全构建完成`OnActivated`事件就会触发. 你可以执行依赖于服务已完全构建的的一些任务, 虽然这种情况可能很少见. + +````csharp +var serviceDescriptor = ServiceDescriptor.Transient(); +services.Add(serviceDescriptor); +if (setIsReadOnly) +{ + services.OnActivated(serviceDescriptor, x => + { + x.Instance.As().IsReadOnly = true; + }); +} +```` + +> 注意,`OnActivated`事件可以为一个`ServiceDescriptor`注册多次. + ## 第三方提供程序 虽然ABP框架没有对任何第三方DI提供程序的核心依赖, 但它必须使用一个提供程序来支持动态代理(dynamic proxying)和一些高级特性以便ABP特性能正常工作. diff --git a/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs b/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs index 4ee9ba5916..043f824b1a 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core; using Autofac.Extras.DynamicProxy; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Autofac; using Volo.Abp.Castle.DynamicProxy; using Volo.Abp.DependencyInjection; @@ -14,10 +15,14 @@ public static class AbpRegistrationBuilderExtensions { public static IRegistrationBuilder ConfigureAbpConventions( this IRegistrationBuilder registrationBuilder, + ServiceDescriptor serviceDescriptor, IModuleContainer moduleContainer, - ServiceRegistrationActionList registrationActionList) + ServiceRegistrationActionList registrationActionList, + ServiceActivatedActionList activatedActionList) where TActivatorData : ReflectionActivatorData { + registrationBuilder = registrationBuilder.InvokeActivatedActions(activatedActionList, serviceDescriptor); + var serviceType = registrationBuilder.RegistrationData.Services.OfType().FirstOrDefault()?.ServiceType; if (serviceType == null) { @@ -36,6 +41,24 @@ public static class AbpRegistrationBuilderExtensions return registrationBuilder; } + private static IRegistrationBuilder InvokeActivatedActions( + this IRegistrationBuilder registrationBuilder, + ServiceActivatedActionList activatedActionList, + ServiceDescriptor serviceDescriptor) + where TActivatorData : ReflectionActivatorData + { + registrationBuilder.OnActivated(context => + { + var serviceActivatedContext = new OnServiceActivatedContext(context.Instance!); + foreach (var action in activatedActionList.GetActions(serviceDescriptor)) + { + action.Invoke(serviceActivatedContext); + } + }); + + return registrationBuilder; + } + private static IRegistrationBuilder InvokeRegistrationActions( this IRegistrationBuilder registrationBuilder, ServiceRegistrationActionList registrationActionList, diff --git a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs index 56a5d7480c..9e86a0e3ce 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs @@ -183,6 +183,7 @@ public static class AutofacRegistration { var moduleContainer = services.GetSingletonInstance(); var registrationActionList = services.GetRegistrationActionList(); + var activatedActionList = services.GetServiceActivatedActionList(); foreach (var descriptor in services) { @@ -196,7 +197,7 @@ public static class AutofacRegistration .RegisterGeneric(descriptor.ImplementationType) .As(descriptor.ServiceType) .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) - .ConfigureAbpConventions(moduleContainer, registrationActionList); + .ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList); } else { @@ -204,7 +205,7 @@ public static class AutofacRegistration .RegisterType(descriptor.ImplementationType) .As(descriptor.ServiceType) .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons) - .ConfigureAbpConventions(moduleContainer, registrationActionList); + .ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList); } } else if (descriptor.ImplementationFactory != null) diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLifetimeEventExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLifetimeEventExtensions.cs new file mode 100644 index 0000000000..14e2d28d51 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionLifetimeEventExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.DependencyInjection; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionLifetimeEventExtensions +{ + // OnActivated + public static void OnActivated(this IServiceCollection services, ServiceDescriptor descriptor, Action onActivatedAction) + { + GetOrCreateOnActivatedActionList(services).Add(new KeyValuePair>(descriptor, onActivatedAction)); + } + + public static ServiceActivatedActionList GetServiceActivatedActionList(this IServiceCollection services) + { + return GetOrCreateOnActivatedActionList(services); + } + + private static ServiceActivatedActionList GetOrCreateOnActivatedActionList(IServiceCollection services) + { + var actionList = services.GetSingletonInstanceOrNull>()?.Value; + if (actionList == null) + { + actionList = new ServiceActivatedActionList(); + services.AddObjectAccessor(actionList); + } + + return actionList; + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceActivatedContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceActivatedContext.cs new file mode 100644 index 0000000000..b2e1d10a93 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IOnServiceActivatedContext.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.DependencyInjection; + +public interface IOnServiceActivatedContext +{ + public object Instance { get; } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceActivatedContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceActivatedContext.cs new file mode 100644 index 0000000000..91af1e130b --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/OnServiceActivatedContext.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.DependencyInjection; + +public class OnServiceActivatedContext : IOnServiceActivatedContext +{ + public object Instance { get; set; } + + public OnServiceActivatedContext(object instance) + { + Instance = instance; + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceActivatedActionList.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceActivatedActionList.cs new file mode 100644 index 0000000000..3e3c4afd9e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ServiceActivatedActionList.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.DependencyInjection; + +public class ServiceActivatedActionList : List>> +{ + public List> GetActions(ServiceDescriptor descriptor) + { + return this.Where(x => x.Key == descriptor).Select(x => x.Value).ToList(); + } +} diff --git a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs index a877c673f1..6e98d442c5 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Microsoft/Extensions/DependencyInjection/ServiceCollectionRepositoryExtensions.cs @@ -84,22 +84,16 @@ public static class ServiceCollectionRepositoryExtensions bool replaceExisting, bool isReadOnlyRepository = false) { - ServiceDescriptor descriptor; + var descriptor = ServiceDescriptor.Transient(serviceType, implementationType); if (isReadOnlyRepository) { - services.TryAddTransient(implementationType); - descriptor = ServiceDescriptor.Transient(serviceType, provider => + services.OnActivated(descriptor, context => { - var repository = provider.GetRequiredService(implementationType); + var repository = context.Instance.As(); ObjectHelper.TrySetProperty(repository.As(), x => x.IsChangeTrackingEnabled, _ => false); - return repository; }); } - else - { - descriptor = ServiceDescriptor.Transient(serviceType, implementationType); - } if (replaceExisting) { diff --git a/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs new file mode 100644 index 0000000000..b2af6d85da --- /dev/null +++ b/framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/AutoFac_OnActivated_Tests.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Autofac.Interception; +using Xunit; + +namespace Volo.Abp.Autofac; + +public class AutoFac_OnActivated_Tests : Autofac_Interception_Test +{ + protected override Task AfterAddApplicationAsync(IServiceCollection services) + { + var serviceDescriptor = ServiceDescriptor.Transient(); + services.Add(serviceDescriptor); + services.OnActivated(serviceDescriptor, x => + { + x.Instance.As().Name += "1"; + }); + services.OnActivated(serviceDescriptor, x => + { + x.Instance.As().Name += "2"; + }); + + return base.AfterAddApplicationAsync(services); + } + + [Fact] + public void Should_Call_OnActivated() + { + var server = ServiceProvider.GetRequiredService(); + server.Name.ShouldBe("MyServer12"); + } +} + +class MyServer +{ + public string Name { get; set; } = "MyServer"; +} diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs index ca98017e0a..4a7a9fb28f 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs @@ -31,15 +31,15 @@ public class RepositoryRegistration_Tests //Assert //MyTestAggregateRootWithoutPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); //MyTestAggregateRootWithGuidPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); @@ -69,24 +69,24 @@ public class RepositoryRegistration_Tests //Assert //MyTestAggregateRootWithoutPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); //MyTestAggregateRootWithGuidPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); //MyTestEntityWithInt32Pk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyBasicRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); + services.ShouldContainTransient(typeof(IReadOnlyBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); } @@ -114,20 +114,20 @@ public class RepositoryRegistration_Tests services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); //MyTestAggregateRootWithGuidPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyBasicRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); + services.ShouldContainTransient(typeof(IReadOnlyBasicRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); //MyTestEntityWithInt32Pk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyBasicRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); + services.ShouldContainTransient(typeof(IReadOnlyBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); } @@ -209,10 +209,10 @@ public class RepositoryRegistration_Tests services.ShouldNotContainService(typeof(IRepository)); //MyTestAggregateRootWithGuidPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestDefaultRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestDefaultRepository)); } @@ -234,11 +234,11 @@ public class RepositoryRegistration_Tests new MyTestRepositoryRegistrar(options).AddRepositories(); //MyTestAggregateRootWithGuidPk - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyRepository)); - services.ShouldContainTransientImplementationFactory(typeof(IReadOnlyBasicRepository)); + services.ShouldContainTransient(typeof(IReadOnlyRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); + services.ShouldContainTransient(typeof(IReadOnlyBasicRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IBasicRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); services.ShouldContainTransient(typeof(IRepository), typeof(MyTestAggregateRootWithDefaultPkCustomRepository)); }