Skip to content

Commit

Permalink
Merge pull request #21 from Tim-Maes/feature/keyed-services
Browse files Browse the repository at this point in the history
Support for Keyed Services
  • Loading branch information
Tim-Maes authored Jan 2, 2024
2 parents b0640fb + 2c25cf9 commit 6c3996b
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 30 deletions.
51 changes: 42 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
### Supported types
<center>

| Type | Supported|
|----------------|----------|
|AddTransient |✔️ |
|TryAddTransient |✔️ |
|AddScoped |✔️ |
|TryAddScoped |✔️ |
|AddSingleton |✔️ |
|TryAddSingleton |✔️ |
|TryAddEnumerable| |
| **Type** | **Available** | **Keyed (.NET 8)** |
|--------------------|----------|------------------------------|
|AddTransient |✔️ |✔️ |
|TryAddTransient |✔️ | |
|AddScoped |✔️ |✔️ |
|TryAddScoped |✔️ | |
|AddSingleton |✔️ |✔️ |
|TryAddSingleton |✔️ | |
|TryAddEnumerable || |
</center>

## Installation 📦
Expand Down Expand Up @@ -137,6 +137,39 @@ public interface IMyTaskRunner
}
```

**When using keyed services:**

Decorate your class with the attribute and provide the key

```csharp
[AddScoped("myKey")]
public class KeyedService
{
public void Run()
{
// ...
}
}

[AddScoped("key", typeof(IKeyedService))]
public class KeyedService : IKeyedService
{
public void Run()
{
// ...
}
}

[AddScoped("anotherKey")]
public class AnotherKeyedService : IKeyedService
{
public void Run()
{
// ...
}
}
```

### Options Registration

Decorate your class containing the options with `[RegisterOptions]` and specify the corresponding section in `appsettings.json`.
Expand Down
13 changes: 8 additions & 5 deletions src/Bindicate.Tests/Bindicate.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -8,10 +8,13 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit" Version="2.5.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
51 changes: 49 additions & 2 deletions src/Bindicate.Tests/Scoped/AddScopedAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Bindicate.Attributes;
using Bindicate.Attributes.Scoped;
using Microsoft.Extensions.DependencyInjection;

namespace Bindicate.Tests.ScopedTests;
Expand All @@ -7,12 +8,28 @@ public class AddScopedAttributeTests
{
private readonly Assembly _testAssembly = typeof(AddScopedAttributeTests).Assembly;

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

// Act
using var scope = serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetService<NonRegisteredClass>();

// Assert
service.Should().BeNull();
}

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

//Act
Expand All @@ -30,7 +47,7 @@ public void AddScoped_RegistersCorrectly()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
Expand All @@ -42,12 +59,42 @@ public void AddScoped_RegistersCorrectly()
service.Should().NotBeNull().And.BeOfType<SimpleScopedClass>();
serviceDescriptor.Lifetime.Should().Be(ServiceLifetime.Scoped);
}


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

// Act and Assert for the first key
using var scope1 = serviceProvider.CreateScope();
var service1 = scope1.ServiceProvider.GetKeyedService<IKeyedService>("myKey");
service1.Should().NotBeNull().And.BeOfType<KeyedService>();

// Act and Assert for the second key
using var scope2 = serviceProvider.CreateScope();
var service2 = scope2.ServiceProvider.GetKeyedService<IKeyedService>("mySecondKey");
service2.Should().NotBeNull().And.BeOfType<SecondKeyedService>();
}
}

public class NonRegisteredClass { }

[AddScoped]
public class SimpleScopedClass { }

public interface IScopedInterface { }

[AddScoped(typeof(IScopedInterface))]
public class ScopedWithInterface : IScopedInterface { }

[AddKeyedScoped("myKey", typeof(IKeyedService))]
public class KeyedService : IKeyedService { }

[AddKeyedScoped("mySecondKey", typeof(IKeyedService))]
public class SecondKeyedService : IKeyedService { }

public interface IKeyedService { }
9 changes: 4 additions & 5 deletions src/Bindicate.Tests/Singleton/AddSingletonAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
using Bindicate.Attributes;
using Bindicate.Tests.ScopedTests;
using Microsoft.Extensions.DependencyInjection;

namespace Bindicate.Tests.Singleton;

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

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

var instance1 = serviceProvider.GetService<ISingletonInterface>();
Expand All @@ -26,7 +25,7 @@ public void AddSingleton_WithInterface_RegistersCorrectly()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
Expand All @@ -44,7 +43,7 @@ public void AddSingleton_RegistersCorrectly()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
Expand Down
9 changes: 4 additions & 5 deletions src/Bindicate.Tests/Transient/AddTransientAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
using Bindicate.Attributes;
using Bindicate.Tests.ScopedTests;
using Microsoft.Extensions.DependencyInjection;

namespace Bindicate.Tests.Transient;

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

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

var instance1 = serviceProvider.GetService<ITransientInterface>();
Expand All @@ -26,7 +25,7 @@ public void AddTransient_WithInterface_RegistersCorrectly()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
Expand All @@ -44,7 +43,7 @@ public void AddSingleton_RegistersCorrectly()
{
// Arrange
var services = new ServiceCollection();
services.AddAutowiringForAssembly(_testAssembly);
services.AddAutowiringForAssembly(_testAssembly).Register();
var serviceProvider = services.BuildServiceProvider();

// Act
Expand Down
23 changes: 23 additions & 0 deletions src/Bindicate/Attributes/BaseKeyedServiceAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Bindicate.Attributes;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
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; }

// 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 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 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 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 declaring the property as nullable.
{
ServiceType = null;

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

View workflow job for this annotation

GitHub Actions / run_test

Cannot convert null literal to non-nullable reference type.

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

View workflow job for this annotation

GitHub Actions / create_nuget

Cannot convert null literal to non-nullable reference type.
}

// Constructor for explicit interface registration with a key
protected BaseKeyedServiceAttribute(object key, Type serviceType = null)

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

View workflow job for this annotation

GitHub Actions / run_test

Cannot convert null literal to non-nullable reference type.

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

View workflow job for this annotation

GitHub Actions / create_nuget

Cannot convert null literal to non-nullable reference type.
{
Key = key;
ServiceType = serviceType;
}
}
15 changes: 15 additions & 0 deletions src/Bindicate/Attributes/Scoped/AddKeyedScopeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Bindicate.Attributes.Scoped;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container with a specified key.
/// </summary>
public class AddKeyedScopedAttribute : BaseKeyedServiceAttribute
{
public AddKeyedScopedAttribute(object key, Type serviceType)
: base(key, serviceType)
{
}

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

}
14 changes: 14 additions & 0 deletions src/Bindicate/Attributes/Singleton/AddKeyedSingletonAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Bindicate.Attributes.Singleton;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container with a specified key.
/// </summary>
public class AddKeyedSingletonAttribute : BaseKeyedServiceAttribute
{
public AddKeyedSingletonAttribute(object key, Type serviceType)
: base(key, serviceType)
{
}

public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Singleton;
}
14 changes: 14 additions & 0 deletions src/Bindicate/Attributes/Transient/AddKeyedTransientAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Bindicate.Attributes.Transient;

/// <summary>
/// Specifies that a service should be registered with the dependency injection container with a specified key.
/// </summary>
public class AddKeyedTransientAttribute : BaseKeyedServiceAttribute
{
public AddKeyedTransientAttribute(object key, Type serviceType)
: base(key, serviceType)
{
}

public override Lifetime.Lifetime Lifetime => Bindicate.Lifetime.Lifetime.Transient;
}
8 changes: 4 additions & 4 deletions 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 IOptions</PackageReleaseNotes>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<Version>1.3</Version>
<Version>1.5</Version>
</PropertyGroup>

<ItemGroup>
Expand All @@ -28,9 +28,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>

</Project>
Loading

0 comments on commit 6c3996b

Please sign in to comment.