Skip to content

Commit

Permalink
Update DI extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
pomianowski committed Sep 19, 2024
1 parent cec5dd3 commit 510fcb3
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<PropertyGroup>
<Version>3.0.2</Version>
<Version>3.1.0</Version>
<AssemblyVersion>3.0.0</AssemblyVersion>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,50 @@
// Copyright (C) Leszek Pomianowski and ReflectionEventing Contributors.
// All Rights Reserved.

using System.Diagnostics.CodeAnalysis;

namespace ReflectionEventing.DependencyInjection;

/// <summary>
/// Represents a builder for configuring the event bus with .NET Core's built-in dependency injection.
/// </summary>
// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
public class DependencyInjectionEventBusBuilder(IServiceCollection services) : EventBusBuilder
{
/// <summary>
/// Adds a consumer to the event bus and <see cref="IServiceCollection"/> with a specified service lifetime.
/// </summary>
/// <param name="consumerType">The type of the consumer to add.</param>
/// <param name="lifetime">The service lifetime of the consumer.</param>
/// <returns>The current instance of <see cref="EventBusBuilder"/>.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if the consumer is already registered with a different lifetime or if the consumer is not registered in the service collection.
/// </exception>
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);
}

/// <inheritdoc />
public override EventBusBuilder AddConsumer(
#if NET5_0_OR_GREATER
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <param name="consumerType">The type of the consumer to add.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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
);
}

/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TConsumer">The type of the consumer to add.</typeparam>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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
);
}

/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <param name="consumerType">The type of the consumer to add.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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);
}

/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TConsumer">The type of the consumer to add.</typeparam>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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
);
}

/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <param name="consumerType">The type of the consumer to add.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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
);
}

/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TConsumer">The type of the consumer to add.</typeparam>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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
);
}

/// <summary>
/// Adds a consumer to the event bus builder and <see cref="IServiceCollection"/>.
/// </summary>
/// <typeparam name="TConsumer">The type of the consumer to add.</typeparam>
/// <param name="builder">The event bus builder to add the consumer to.</param>
/// <param name="lifetime">The service lifetime of the consumer.</param>
/// <returns>The event bus builder with the consumer added.</returns>
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);
}
}
1 change: 1 addition & 0 deletions src/ReflectionEventing.DependencyInjection/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -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<TestConsumer>(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<TestConsumer>();
DependencyInjectionEventBusBuilder eventBusBuilder = new(services);

Action act = () => eventBusBuilder.AddScopedConsumer(typeof(TestConsumer));

_ = act.Should()
.Throw<InvalidOperationException>()
.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<TestConsumer>();

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<TestConsumer>();

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<TestConsumer>();

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<TestEvent>
{
public Task ConsumeAsync(TestEvent payload, CancellationToken cancellationToken) =>
Task.CompletedTask;
}
}

0 comments on commit 510fcb3

Please sign in to comment.