Skip to content

Commit

Permalink
Add TryAddEnumerable (#28)
Browse files Browse the repository at this point in the history
* Add TryAddEnumerable

* Bump version
  • Loading branch information
Tim-Maes authored Oct 15, 2024
1 parent 46b1de3 commit caef71a
Show file tree
Hide file tree
Showing 20 changed files with 224 additions and 73 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

## Features 🌟

- Automatic registration of services using custom attributes.
- Automatic registration of (keyed) services using custom attributes.
- Automatic registration and configuration of options via `IOptions<T>`.
- Provides clear visibility and reduces boilerplate code.
- Simple integration with the built-in .NET IoC container.
- Supports Keyed Services
- Supports Decorators

### Supported types
<center>
Expand All @@ -24,7 +24,7 @@
|TryAddScoped |✔️ |||||
|AddSingleton |✔️ |✔️ | ✔️|||
|TryAddSingleton |✔️ |||||
|TryAddEnumerable | |||||
|TryAddEnumerable |✔️ |||||
</center>

## Installation 📦
Expand Down
92 changes: 92 additions & 0 deletions src/Bindicate.Tests/Enumerable/TryAddEnumerableTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Bindicate.Attributes.Enumerable;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Bindicate.Tests.Enumerable;

public class TryAddEnumberableTests
{
private readonly Assembly _testAssembly = typeof(TryAddEnumberableTests).Assembly;

[Fact]
public void TryAddEnumerable_MultipleImplementations_RegistersAll()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
var myServices = serviceProvider.GetServices<IMyService>().ToList();

// Assert
myServices.Should().NotBeNull();
myServices.Count.Should().Be(2);
myServices.Should().ContainSingle(s => s.GetType() == typeof(MyServiceA));
myServices.Should().ContainSingle(s => s.GetType() == typeof(MyServiceB));
}

[Fact]
public void TryAddEnumerable_DuplicateImplementation_NotRegisteredTwice()
{
// Arrange
var services = new ServiceCollection();

// Manually add MyServiceA to simulate duplicate registration
services.TryAddEnumerable(ServiceDescriptor.Transient<IMyService, MyServiceA>());
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
var myServices = serviceProvider.GetServices<IMyService>().ToList();

// Assert
myServices.Should().NotBeNull();
myServices.Count().Should().Be(2);
myServices.Should().ContainSingle(s => s.GetType() == typeof(MyServiceA));
myServices.Should().ContainSingle(s => s.GetType() == typeof(MyServiceB));
}

[Fact]
public void TryAddEnumerable_Lifetime_IsRespected()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
var scope1Services = serviceProvider.GetServices<IMyService>().ToList();
var scope2Services = serviceProvider.GetServices<IMyService>().ToList();

// Assert
// Since the services are registered as Transient, each resolve should return new instances
scope1Services.Should().NotBeSameAs(scope2Services);

var serviceA1 = scope1Services.First(s => s.GetType() == typeof(MyServiceA));
var serviceA2 = scope2Services.First(s => s.GetType() == typeof(MyServiceA));

serviceA1.Should().NotBeSameAs(serviceA2);
}
}

public interface IMyService
{
void Execute();
}

[TryAddEnumerable(Lifetime.TryAddEnumerableTransient, typeof(IMyService))]
public class MyServiceA : IMyService
{
public void Execute()
{
}
}

[TryAddEnumerable(Lifetime.TryAddEnumerableTransient, typeof(IMyService))]
public class MyServiceB : IMyService
{
public void Execute()
{
}
}
4 changes: 2 additions & 2 deletions src/Bindicate.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
global using System.Linq;
global using FluentAssertions;
global using System.Linq;
global using System.Reflection;
global using Xunit;
global using FluentAssertions;
2 changes: 1 addition & 1 deletion src/Bindicate/Attributes/BaseKeyedServiceAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public abstract class BaseKeyedServiceAttribute : Attribute
public Type ServiceType { get; protected set; }
public object Key { get; protected set; } // Added key property

public abstract Lifetime.Lifetime Lifetime { get; }
public abstract Lifetime Lifetime { get; }

// Constructor for class-only registration without a key
protected BaseKeyedServiceAttribute()

Check warning on line 12 in src/Bindicate/Attributes/BaseKeyedServiceAttribute.cs

View workflow job for this annotation

GitHub Actions / run_test

Non-nullable property 'ServiceType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 12 in src/Bindicate/Attributes/BaseKeyedServiceAttribute.cs

View workflow job for this annotation

GitHub Actions / run_test

Non-nullable property 'Key' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 12 in src/Bindicate/Attributes/BaseKeyedServiceAttribute.cs

View workflow job for this annotation

GitHub Actions / create_nuget

Non-nullable property 'ServiceType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 12 in src/Bindicate/Attributes/BaseKeyedServiceAttribute.cs

View workflow job for this annotation

GitHub Actions / create_nuget

Non-nullable property 'Key' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
Expand Down
6 changes: 2 additions & 4 deletions src/Bindicate/Attributes/BaseServiceAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public abstract class BaseServiceAttribute : Attribute
{
public Type ServiceType { get; protected set; }

public abstract Lifetime.Lifetime Lifetime { get; }
public abstract Lifetime Lifetime { get; }

// Constructor for class-only registration
protected BaseServiceAttribute()

Check warning on line 11 in src/Bindicate/Attributes/BaseServiceAttribute.cs

View workflow job for this annotation

GitHub Actions / run_test

Non-nullable property 'ServiceType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 11 in src/Bindicate/Attributes/BaseServiceAttribute.cs

View workflow job for this annotation

GitHub Actions / create_nuget

Non-nullable property 'ServiceType' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
Expand Down
21 changes: 21 additions & 0 deletions src/Bindicate/Attributes/Enumerable/TryAddEnumerableAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Bindicate.Attributes.Enumerable;

/// <summary>
/// Specifies that a service should be registered using TryAddEnumerable with the dependency injection container.
/// This allows multiple implementations of the same service type to be registered.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class TryAddEnumerableAttribute : BaseServiceAttribute
{
public override Lifetime Lifetime { get; }

public TryAddEnumerableAttribute(Lifetime lifetime)
{
Lifetime = lifetime;
}

public TryAddEnumerableAttribute(Lifetime lifetime, Type serviceType) : base(serviceType)
{
Lifetime = lifetime;
}
}
2 changes: 1 addition & 1 deletion src/Bindicate/Attributes/Scoped/AddKeyedScopeAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public AddKeyedScopedAttribute(object key, Type serviceType)
{
}

public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Scoped;
public override Lifetime Lifetime => Lifetime.Scoped;

}
6 changes: 2 additions & 4 deletions src/Bindicate/Attributes/Scoped/AddScopedAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container with a scoped lifetime.
/// This means a new instance is created once per scope.
/// </summary>
public class AddScopedAttribute : BaseServiceAttribute
{
public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Scoped;
public override Lifetime Lifetime => Lifetime.Scoped;

public AddScopedAttribute() : base() { }

Expand Down
6 changes: 2 additions & 4 deletions src/Bindicate/Attributes/Scoped/TryAddScopedAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container for the lifetime of a scope.
Expand All @@ -9,7 +7,7 @@ namespace Bindicate.Attributes;
/// </summary>
public class TryAddScopedAttribute : BaseServiceAttribute
{
public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.TryAddScoped;
public override Lifetime Lifetime => Lifetime.TryAddScoped;

public TryAddScopedAttribute() : base() { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ public AddKeyedSingletonAttribute(object key, Type serviceType)
{
}

public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Singleton;
public override Lifetime Lifetime => Bindicate.Lifetime.Singleton;
}
6 changes: 2 additions & 4 deletions src/Bindicate/Attributes/Singleton/AddSingletonAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container with a singleton lifetime.
/// This means a single instance is created and it acts as a singleton.
/// </summary>
public class AddSingletonAttribute : BaseServiceAttribute
{
public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Singleton;
public override Lifetime Lifetime => Lifetime.Singleton;

public AddSingletonAttribute() : base() { }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

/// <summary>
/// Attempts to register a service with the dependency injection container with a singleton lifetime.
/// If a service with the same type is already registered, the existing registration is preserved.
/// </summary>
public class TryAddSingletonAttribute : BaseServiceAttribute
{
public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.TryAddSingleton;
public override Lifetime Lifetime => Bindicate.Lifetime.TryAddSingleton;

public TryAddSingletonAttribute() : base() { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ public AddKeyedTransientAttribute(object key, Type serviceType)
{
}

public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Transient;
public override Lifetime Lifetime => Lifetime.Transient;
}
6 changes: 2 additions & 4 deletions src/Bindicate/Attributes/Transient/AddTransientAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container with a transient lifetime.
/// This means a new instance is created every time it is requested.
/// </summary>
public class AddTransientAttribute : BaseServiceAttribute
{
public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Transient;
public override Lifetime Lifetime => Bindicate.Lifetime.Transient;

public AddTransientAttribute() : base() { }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using Bindicate.Lifetime;

namespace Bindicate.Attributes;
namespace Bindicate.Attributes;

/// <summary>
/// Attempts to register a service with the dependency injection container with a transient lifetime.
/// If a service with the same type is already registered, the existing registration is preserved.
/// </summary>
public class TryAddTransientAttribute : BaseServiceAttribute
{
public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.TryAddTransient;
public override Lifetime Lifetime => Lifetime.TryAddTransient;

public TryAddTransientAttribute() : base() { }

Expand Down
2 changes: 1 addition & 1 deletion src/Bindicate/Bindicate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageTags>di, ioc, service, collection, extensions, attribute</PackageTags>
<PackageReleaseNotes>Add support for Decorators</PackageReleaseNotes>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<Version>1.6.1</Version>
<Version>1.7.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit caef71a

Please sign in to comment.