diff --git a/Directory.Build.props b/Directory.Build.props
index 930ae77..a3b2cd8 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,7 +6,7 @@
- 3.0.2
+ 3.1.0
3.0.0
diff --git a/src/ReflectionEventing.DependencyInjection/DependencyInjectionEventBusBuilder.cs b/src/ReflectionEventing.DependencyInjection/DependencyInjectionEventBusBuilder.cs
index 84fd2a1..a1bfc63 100644
--- a/src/ReflectionEventing.DependencyInjection/DependencyInjectionEventBusBuilder.cs
+++ b/src/ReflectionEventing.DependencyInjection/DependencyInjectionEventBusBuilder.cs
@@ -3,15 +3,50 @@
// Copyright (C) Leszek Pomianowski and ReflectionEventing Contributors.
// All Rights Reserved.
-using System.Diagnostics.CodeAnalysis;
-
namespace ReflectionEventing.DependencyInjection;
///
/// Represents a builder for configuring the event bus with .NET Core's built-in dependency injection.
///
+// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
public class DependencyInjectionEventBusBuilder(IServiceCollection services) : EventBusBuilder
{
+ ///
+ /// Adds a consumer to the event bus and with a specified service lifetime.
+ ///
+ /// The type of the consumer to add.
+ /// The service lifetime of the consumer.
+ /// The current instance of .
+ ///
+ /// Thrown if the consumer is already registered with a different lifetime or if the consumer is not registered in the service collection.
+ ///
+ public virtual EventBusBuilder AddConsumer(
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ Type consumerType,
+ ServiceLifetime lifetime
+ )
+ {
+ ServiceDescriptor? descriptor = services.FirstOrDefault(d => d.ServiceType == consumerType);
+
+ if (descriptor is not null)
+ {
+ if (descriptor.Lifetime != lifetime)
+ {
+ throw new InvalidOperationException(
+ "Event consumer must be registered with the same lifetime as the one provided."
+ );
+ }
+
+ return base.AddConsumer(consumerType);
+ }
+
+ services.Add(new ServiceDescriptor(consumerType, consumerType, lifetime));
+
+ return base.AddConsumer(consumerType);
+ }
+
///
public override EventBusBuilder AddConsumer(
#if NET5_0_OR_GREATER
diff --git a/src/ReflectionEventing.DependencyInjection/EventBusBuilderExtensions.cs b/src/ReflectionEventing.DependencyInjection/EventBusBuilderExtensions.cs
new file mode 100644
index 0000000..b229253
--- /dev/null
+++ b/src/ReflectionEventing.DependencyInjection/EventBusBuilderExtensions.cs
@@ -0,0 +1,189 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and ReflectionEventing Contributors.
+// All Rights Reserved.
+
+namespace ReflectionEventing.DependencyInjection;
+
+public static class EventBusBuilderExtensions
+{
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The event bus builder to add the consumer to.
+ /// The type of the consumer to add.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddTransientConsumer(
+ this EventBusBuilder builder,
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ Type consumerType
+ )
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a transient consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(
+ consumerType,
+ ServiceLifetime.Transient
+ );
+ }
+
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The type of the consumer to add.
+ /// The event bus builder to add the consumer to.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddTransientConsumer<
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ TConsumer
+ >(this EventBusBuilder builder)
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a transient consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(
+ typeof(TConsumer),
+ ServiceLifetime.Transient
+ );
+ }
+
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The event bus builder to add the consumer to.
+ /// The type of the consumer to add.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddScopedConsumer(
+ this EventBusBuilder builder,
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ Type consumerType
+ )
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a scoped consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(consumerType, ServiceLifetime.Scoped);
+ }
+
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The type of the consumer to add.
+ /// The event bus builder to add the consumer to.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddScopedConsumer<
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ TConsumer
+ >(this EventBusBuilder builder)
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a scoped consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(
+ typeof(TConsumer),
+ ServiceLifetime.Scoped
+ );
+ }
+
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The event bus builder to add the consumer to.
+ /// The type of the consumer to add.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddSingletonConsumer(
+ this EventBusBuilder builder,
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ Type consumerType
+ )
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a singleton consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(
+ consumerType,
+ ServiceLifetime.Singleton
+ );
+ }
+
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The type of the consumer to add.
+ /// The event bus builder to add the consumer to.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddSingletonConsumer<
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ TConsumer
+ >(this EventBusBuilder builder)
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a singleton consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(
+ typeof(TConsumer),
+ ServiceLifetime.Singleton
+ );
+ }
+
+ ///
+ /// Adds a consumer to the event bus builder and .
+ ///
+ /// The type of the consumer to add.
+ /// The event bus builder to add the consumer to.
+ /// The service lifetime of the consumer.
+ /// The event bus builder with the consumer added.
+ public static EventBusBuilder AddConsumer<
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
+#endif
+ TConsumer
+ >(this EventBusBuilder builder, ServiceLifetime lifetime)
+ {
+ if (builder is not DependencyInjectionEventBusBuilder dependencyInjectionEventBusBuilder)
+ {
+ throw new InvalidOperationException(
+ "The event bus builder must be of type DependencyInjectionEventBusBuilder to add a transient consumer."
+ );
+ }
+
+ return dependencyInjectionEventBusBuilder.AddConsumer(typeof(TConsumer), lifetime);
+ }
+}
diff --git a/src/ReflectionEventing.DependencyInjection/GlobalUsings.cs b/src/ReflectionEventing.DependencyInjection/GlobalUsings.cs
index b52d80b..eb86b05 100644
--- a/src/ReflectionEventing.DependencyInjection/GlobalUsings.cs
+++ b/src/ReflectionEventing.DependencyInjection/GlobalUsings.cs
@@ -5,5 +5,6 @@
global using System;
global using System.Collections.Generic;
+global using System.Diagnostics.CodeAnalysis;
global using System.Linq;
global using Microsoft.Extensions.DependencyInjection;
diff --git a/tests/ReflectionEventing.DependencyInjection.UnitTests/EventBusBuilderExtensionsTests.cs b/tests/ReflectionEventing.DependencyInjection.UnitTests/EventBusBuilderExtensionsTests.cs
new file mode 100644
index 0000000..e917750
--- /dev/null
+++ b/tests/ReflectionEventing.DependencyInjection.UnitTests/EventBusBuilderExtensionsTests.cs
@@ -0,0 +1,117 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and ReflectionEventing Contributors.
+// All Rights Reserved.
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace ReflectionEventing.DependencyInjection.UnitTests;
+
+public sealed class EventBusBuilderExtensionsTests
+{
+ [Fact]
+ public void AddConsumer_ShouldRegisterConsumerWhenLifetimeProvided()
+ {
+ IServiceCollection services = new ServiceCollection();
+ DependencyInjectionEventBusBuilder eventBusBuilder = new(services);
+
+ eventBusBuilder.AddConsumer(ServiceLifetime.Transient);
+
+ IConsumerTypesProvider consumerTypesProvider = eventBusBuilder.BuildTypesProvider();
+ _ = consumerTypesProvider
+ .GetConsumerTypes(typeof(TestEvent))
+ .Should()
+ .Contain(typeof(TestConsumer));
+
+ services
+ .First(d => d.ServiceType == typeof(TestConsumer))
+ .Lifetime.Should()
+ .Be(ServiceLifetime.Transient);
+ }
+
+ [Fact]
+ public void AddScopedConsumer_ShouldThrowExceptionWhenConsumerIsRegisteredWithDifferentScope()
+ {
+ IServiceCollection services = new ServiceCollection();
+ _ = services.AddSingleton();
+ DependencyInjectionEventBusBuilder eventBusBuilder = new(services);
+
+ Action act = () => eventBusBuilder.AddScopedConsumer(typeof(TestConsumer));
+
+ _ = act.Should()
+ .Throw()
+ .WithMessage(
+ "Event consumer must be registered with the same lifetime as the one provided."
+ );
+ }
+
+ [Fact]
+ public void AddScopedConsumer_ShouldRegisterTransientConsumer()
+ {
+ IServiceCollection services = new ServiceCollection();
+ DependencyInjectionEventBusBuilder eventBusBuilder = new(services);
+
+ eventBusBuilder.AddTransientConsumer();
+
+ IConsumerTypesProvider consumerTypesProvider = eventBusBuilder.BuildTypesProvider();
+ _ = consumerTypesProvider
+ .GetConsumerTypes(typeof(TestEvent))
+ .Should()
+ .Contain(typeof(TestConsumer));
+
+ services
+ .First(d => d.ServiceType == typeof(TestConsumer))
+ .Lifetime.Should()
+ .Be(ServiceLifetime.Transient);
+ }
+
+ [Fact]
+ public void AddScopedConsumer_ShouldRegisterScopedConsumer()
+ {
+ IServiceCollection services = new ServiceCollection();
+ DependencyInjectionEventBusBuilder eventBusBuilder = new(services);
+
+ eventBusBuilder.AddScopedConsumer();
+
+ IConsumerTypesProvider consumerTypesProvider = eventBusBuilder.BuildTypesProvider();
+ _ = consumerTypesProvider
+ .GetConsumerTypes(typeof(TestEvent))
+ .Should()
+ .Contain(typeof(TestConsumer));
+
+ services
+ .First(d => d.ServiceType == typeof(TestConsumer))
+ .Lifetime.Should()
+ .Be(ServiceLifetime.Scoped);
+ }
+
+ [Fact]
+ public void AddScopedConsumer_ShouldRegisterSingletonConsumer()
+ {
+ IServiceCollection services = new ServiceCollection();
+ DependencyInjectionEventBusBuilder eventBusBuilder = new(services);
+
+ eventBusBuilder.AddSingletonConsumer();
+
+ IConsumerTypesProvider consumerTypesProvider = eventBusBuilder.BuildTypesProvider();
+ _ = consumerTypesProvider
+ .GetConsumerTypes(typeof(TestEvent))
+ .Should()
+ .Contain(typeof(TestConsumer));
+
+ services
+ .First(d => d.ServiceType == typeof(TestConsumer))
+ .Lifetime.Should()
+ .Be(ServiceLifetime.Singleton);
+ }
+
+ public interface ITestEvent;
+
+ public record TestEvent : ITestEvent;
+
+ public class TestConsumer : IConsumer
+ {
+ public Task ConsumeAsync(TestEvent payload, CancellationToken cancellationToken) =>
+ Task.CompletedTask;
+ }
+}