Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Keyed Services #21

Merged
merged 4 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 / 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.

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.
{
ServiceType = null;

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.

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.
}

// 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 / create_nuget

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 / run_test

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
Loading