diff --git a/docs/intro.md b/docs/intro.md index 4ab67a48..48b63535 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -566,24 +566,39 @@ services.AddSlimMessageBus(mbb => Consider the following example: ```cs -// Given a consumer that is found: -public class SomeMessageConsumer : IConsumer -{ -} - +// Given a consumer that is found in the executing assembly: +public class SomeMessageConsumer : IConsumer { } + +// When configuring the bus: services.AddSlimMessageBus(mbb => { // When auto-registration is used: mbb.AddConsumersFromAssembly(Assembly.GetExecutingAssembly()); }; +// Then the .AddConsumersFromAssembly() will result in an equivalent services registration (if it were done manually): +// services.TryAddTransient(); +// services.TryAddTransient>(svp => svp.GetRequiredService()); ``` -This will result in an equivalent services registration: +There is also an option to override the default lifetime of the discovered types (since v2.1.5): ```cs -services.TryAddTransient(); -services.TryAddTransient, SomeMessageConsumer>(); +services.AddSlimMessageBus(mbb => +{ + // Register the found types as Scoped lifetime in MSDI + mbb.AddConsumersFromAssembly(Assembly.GetExecutingAssembly(), consumerLifetime: ServiceLifetime.Scoped); +}; +``` + +There is also an option to provide a type filter predicate. This might be helpful to scan only for types in a specified namespace inside of the assembly: + +```cs +services.AddSlimMessageBus(mbb => +{ + // Register the found types that contain DomainEventHandlers in the namespacce + mbb.AddConsumersFromAssembly(Assembly.GetExecutingAssembly(), filter: (type) => type.Namespace.Contains("DomainEventHandlers")); +}; ``` ### ASP.Net Core diff --git a/src/Host.Transport.Properties.xml b/src/Host.Transport.Properties.xml index a2816089..5c642784 100644 --- a/src/Host.Transport.Properties.xml +++ b/src/Host.Transport.Properties.xml @@ -4,7 +4,7 @@ - 2.1.4 + 2.1.5 true diff --git a/src/SlimMessageBus.Host.Configuration/Discovery/ReflectionDiscoveryScanner.cs b/src/SlimMessageBus.Host.Configuration/Discovery/ReflectionDiscoveryScanner.cs index 5c0dc540..767f5c55 100644 --- a/src/SlimMessageBus.Host.Configuration/Discovery/ReflectionDiscoveryScanner.cs +++ b/src/SlimMessageBus.Host.Configuration/Discovery/ReflectionDiscoveryScanner.cs @@ -67,10 +67,11 @@ public IReadOnlyCollection GetConsumerTypes(Func) }; - public IReadOnlyCollection GetInterceptorTypes() + public IReadOnlyCollection GetInterceptorTypes(Func filterPredicate = null) { var foundTypes = ProspectTypes .Where(x => x.InterfaceType.IsGenericType && GenericTypesInterceptors.Contains(x.InterfaceType.GetGenericTypeDefinition())) + .Where(x => filterPredicate == null || filterPredicate(x.Type)) .ToList(); return foundTypes; diff --git a/src/SlimMessageBus.Host.Memory/MemoryMessageBusBuilder.cs b/src/SlimMessageBus.Host.Memory/MemoryMessageBusBuilder.cs index 5952d1c5..4591e792 100644 --- a/src/SlimMessageBus.Host.Memory/MemoryMessageBusBuilder.cs +++ b/src/SlimMessageBus.Host.Memory/MemoryMessageBusBuilder.cs @@ -24,14 +24,17 @@ internal MemoryMessageBusBuilder(MessageBusBuilder other) : base(other) /// For every found type declares the produced and consumer/handler by applying the topic name that corresponds to the mesage name. /// /// - /// Allows to apply a filter for any found consumer/handler. + /// Allows to apply a filter for the found consumer/handler types. /// By default the type name is used for the topic name. This can be used to customize the topic name. For example, if have types that have same names but are in the namespaces, you might want to include the full type in the topic name. /// public MemoryMessageBusBuilder AutoDeclareFrom(IEnumerable assemblies, Func consumerTypeFilter = null, Func messageTypeToTopicConverter = null) { messageTypeToTopicConverter ??= DefaultMessageTypeToTopicConverter; - var prospectTypes = ReflectionDiscoveryScanner.From(assemblies).Scan().GetConsumerTypes(consumerTypeFilter); + var prospectTypes = ReflectionDiscoveryScanner.From(assemblies).Scan() + .GetConsumerTypes(consumerTypeFilter) + // Take only closed generic types + .Where(x => !x.ConsumerType.IsGenericType || x.ConsumerType.IsConstructedGenericType); var foundConsumers = prospectTypes.Where(x => x.InterfaceType.GetGenericTypeDefinition() == typeof(IConsumer<>)).ToList(); var foundHandlers = prospectTypes.Where(x => x.InterfaceType.GetGenericTypeDefinition() == typeof(IRequestHandler<,>) || x.InterfaceType.GetGenericTypeDefinition() == typeof(IRequestHandler<>)).ToList(); diff --git a/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs b/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs index bb5ee630..f443914a 100644 --- a/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs +++ b/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs @@ -101,110 +101,73 @@ public static IServiceCollection AddSlimMessageBus(this IServiceCollection servi } /// - /// Scans the specified assemblies (using reflection) for types that implement any consumer/handler interface, or any interceptor interface (). - /// The found types are registered in the DI as Transient service (both the consumer type and its interface are registered). + /// Scans the specified assemblies (using reflection) for types that implement consumer/handler interface, or interceptor interface). + /// The found types are registered in the MSDI (both the consumer type and its interface are registered). /// /// /// - /// + /// The filter to be applied for the found types - only types that evaluate the given filter predicate will be registered in MSDI. + /// The consumer lifetime under which the found types should be registerd as. + /// The interceptor lifetime under which the found types should be registerd as. /// - public static MessageBusBuilder AddServicesFromAssembly(this MessageBusBuilder mbb, Assembly assembly, Func filterPredicate = null) + public static MessageBusBuilder AddServicesFromAssembly( + this MessageBusBuilder mbb, + Assembly assembly, + Func filter = null, + ServiceLifetime consumerLifetime = ServiceLifetime.Transient, + ServiceLifetime interceptorLifetime = ServiceLifetime.Transient) { var scan = ReflectionDiscoveryScanner.From(assembly); - var foundConsumerTypes = scan.GetConsumerTypes(filterPredicate); + var foundConsumerTypes = scan.GetConsumerTypes(filter); + var foundInterceptorTypes = scan.GetInterceptorTypes(filter); mbb.PostConfigurationActions.Add(services => { foreach (var (foundType, interfaceTypes) in foundConsumerTypes.GroupBy(x => x.ConsumerType, x => x.InterfaceType).ToDictionary(x => x.Key, x => x)) { // Register the consumer/handler type - services.TryAddTransient(foundType); + services.TryAdd(ServiceDescriptor.Describe(foundType, foundType, consumerLifetime)); foreach (var interfaceType in interfaceTypes) { + if (foundType.IsGenericType && !foundType.IsConstructedGenericType) + { + // Skip open generic types + continue; + } + // Register the interface of the consumer / handler - services.TryAddTransient(interfaceType, foundType); + services.TryAdd(ServiceDescriptor.Describe(interfaceType, svp => svp.GetRequiredService(foundType), consumerLifetime)); } } - var foundInterceptorTypes = scan.GetInterceptorTypes(); foreach (var foundType in foundInterceptorTypes) { - services.AddTransient(foundType.InterfaceType, foundType.Type); + if (foundType.Type.IsGenericType && !foundType.Type.IsConstructedGenericType) + { + // Skip open generic types + continue; + } + services.TryAddEnumerable(ServiceDescriptor.Describe(foundType.InterfaceType, foundType.Type, interceptorLifetime)); } }); return mbb; } /// - /// Scans the specified assemblies (using reflection) for types that implement any consumer/handler interface, any interceptor interface or message bus configurator interface (). - /// The found types are registered in the DI as Transient service (both the consumer type and its interface are registered). + /// Scans the specified assemblies (using reflection) for types that implement consumer/handler interface, or interceptor interface). + /// The found types are registered in the MSDI (both the consumer type and its interface are registered). /// /// /// - /// - /// - public static MessageBusBuilder AddServicesFromAssemblyContaining(this MessageBusBuilder mbb, Func filterPredicate = null) => - mbb.AddServicesFromAssembly(typeof(T).Assembly, filterPredicate); - - #region Obsolete - - /// - /// Scans the specified assemblies (using reflection) for types that implement either or or . - /// The found types are registered in the DI as Transient service (both the consumer type and its interface are registered). - /// - /// - /// Filtering predicate that allows to further narrow down the - /// Assemblies to be scanned + /// The filter to be applied for the found types - only types that evaluate the given filter predicate will be registered in MSDI. + /// The consumer lifetime under which the found types should be registerd as. + /// The interceptor lifetime under which the found types should be registerd as. /// - [Obsolete("Use the new mbb.AddServicesFromAssembly() or mbb.AddServicesFromAssemblyContaining()")] - public static IServiceCollection AddMessageBusConsumersFromAssembly(this IServiceCollection services, Func filterPredicate, params Assembly[] assemblies) - { - var foundTypes = ReflectionDiscoveryScanner.From(assemblies).GetConsumerTypes(filterPredicate); - foreach (var (foundType, interfaceTypes) in foundTypes.GroupBy(x => x.ConsumerType, x => x.InterfaceType).ToDictionary(x => x.Key, x => x)) - { - // Register the consumer/handler type - services.TryAddTransient(foundType); - - foreach (var interfaceType in interfaceTypes) - { - // Register the interface of the consumer / handler - services.TryAddTransient(interfaceType, foundType); - } - } - - return services; - } - - /// - /// Scans the specified assemblies (using reflection) for types that implement either or or . - /// The found types are registered in the DI as Transient service. - /// - /// - /// Assemblies to be scanned - /// - [Obsolete("Use the new mbb.AddServicesFromAssembly() or mbb.AddServicesFromAssemblyContaining()")] - public static IServiceCollection AddMessageBusConsumersFromAssembly(this IServiceCollection services, params Assembly[] assemblies) - => services.AddMessageBusConsumersFromAssembly(filterPredicate: null, assemblies); - - /// - /// Scans the specified assemblies (using reflection) for types that implement one of the interceptor interfaces ( or ) and adds them to DI. - /// This types will be use during message bus configuration. - /// - /// - /// Assemblies to be scanned - /// - [Obsolete("Use the new mbb.AddServicesFromAssembly() or mbb.AddServicesFromAssemblyContaining()")] - public static IServiceCollection AddMessageBusInterceptorsFromAssembly(this IServiceCollection services, params Assembly[] assemblies) - { - var foundTypes = ReflectionDiscoveryScanner.From(assemblies).GetInterceptorTypes(); - foreach (var foundType in foundTypes) - { - services.AddTransient(foundType.InterfaceType, foundType.Type); - } - - return services; - } - - #endregion + public static MessageBusBuilder AddServicesFromAssemblyContaining( + this MessageBusBuilder mbb, + Func filter = null, + ServiceLifetime consumerLifetime = ServiceLifetime.Transient, + ServiceLifetime interceptorLifetime = ServiceLifetime.Transient) => + mbb.AddServicesFromAssembly(typeof(T).Assembly, filter, consumerLifetime: consumerLifetime, interceptorLifetime: interceptorLifetime); } diff --git a/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusBuilderTests.cs b/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusBuilderTests.cs index f7c7b2cb..698b1881 100644 --- a/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusBuilderTests.cs +++ b/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusBuilderTests.cs @@ -21,8 +21,9 @@ public void Given_AssemblyWithConsumers_When_AutoDiscoverConsumers_Then_AllOfThe // arrange // act + var whitelistNames = new[] { "Ping", "Echo", "Some", "Generic" }; subject.AutoDeclareFrom(Assembly.GetExecutingAssembly(), - consumerTypeFilter: (consumerType) => consumerType.Name.Contains("Ping") || consumerType.Name.Contains("Echo") || consumerType.Name.Contains("Some")); // include specific consumers only + consumerTypeFilter: (consumerType) => whitelistNames.Any(name => consumerType.Name.Contains(name))); // include specific consumers only // assert diff --git a/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusTests.cs b/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusTests.cs index a27346ce..fd3f19b9 100644 --- a/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusTests.cs +++ b/src/Tests/SlimMessageBus.Host.Memory.Test/MemoryMessageBusTests.cs @@ -434,6 +434,11 @@ public virtual void Dispose() public virtual Task OnHandle(SomeMessageA messageA) => Task.CompletedTask; } +public class GenericConsumer : IConsumer +{ + public Task OnHandle(T message) => Task.CompletedTask; +} + public class SomeMessageAConsumer2 : IConsumer { public virtual Task OnHandle(SomeMessageA messageA) => Task.CompletedTask; diff --git a/src/Tests/SlimMessageBus.Host.Test/DependencyResolver/ServiceCollectionExtensionsTest.cs b/src/Tests/SlimMessageBus.Host.Test/DependencyResolver/ServiceCollectionExtensionsTest.cs index 1e6ee4e3..9935a72c 100644 --- a/src/Tests/SlimMessageBus.Host.Test/DependencyResolver/ServiceCollectionExtensionsTest.cs +++ b/src/Tests/SlimMessageBus.Host.Test/DependencyResolver/ServiceCollectionExtensionsTest.cs @@ -117,7 +117,7 @@ public void When_AddSlimMessageBus_Given_AddServicesFromAssembly_Then_AllOfTheCo { mbb.AddChildBus("bus1", mbb => { - mbb.AddServicesFromAssemblyContaining(x => x.Name == nameof(SomeMessageConsumer)); + mbb.AddServicesFromAssemblyContaining(x => x.Name == nameof(SomeMessageConsumer), consumerLifetime: ServiceLifetime.Scoped); }); }); @@ -125,14 +125,14 @@ public void When_AddSlimMessageBus_Given_AddServicesFromAssembly_Then_AllOfTheCo { mbb.AddChildBus("bus2", mbb => { - mbb.AddServicesFromAssemblyContaining(x => x.Name == nameof(SomeMessageConsumer)); + mbb.AddServicesFromAssemblyContaining(x => x.Name == nameof(SomeMessageConsumer), consumerLifetime: ServiceLifetime.Transient); }); }); // assert _services.Count(x => x.ServiceType == typeof(SomeMessageConsumer)).Should().Be(1); _services.Count(x => x.ServiceType == typeof(IConsumer)).Should().Be(1); - _services.Should().Contain(x => x.ServiceType == typeof(SomeMessageConsumer) && x.ImplementationType == typeof(SomeMessageConsumer) && x.Lifetime == ServiceLifetime.Transient); - _services.Should().Contain(x => x.ServiceType == typeof(IConsumer) && x.ImplementationType == typeof(SomeMessageConsumer) && x.Lifetime == ServiceLifetime.Transient); + _services.Should().Contain(x => x.ServiceType == typeof(SomeMessageConsumer) && x.ImplementationType == typeof(SomeMessageConsumer) && x.Lifetime == ServiceLifetime.Scoped); + _services.Should().Contain(x => x.ServiceType == typeof(IConsumer) && x.ImplementationFactory != null && x.Lifetime == ServiceLifetime.Scoped); } }