Skip to content

Commit

Permalink
add keyed services
Browse files Browse the repository at this point in the history
  • Loading branch information
jcreach committed Feb 5, 2025
1 parent 0a067cd commit 01e40e3
Show file tree
Hide file tree
Showing 8 changed files with 401 additions and 48 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
**/.vs
**/bin
**/obj
**/.idea

# Files
**/*.user
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The purpose of this library is to provide tools to facilitate dependency injection when using dotnet.

## How to use
## How to use (classic case)

### First : Decorate needed classes

Expand Down Expand Up @@ -73,6 +73,15 @@ public class MySuperAuthService : IAuthService
}
```

#### With interfaces (Transient)

Same as `Scope` you just need to change the `LifeTime` to `Transient`

#### With interfaces (Singleton)

Same as `Scope` you just need to change the `LifeTime` to `Singleton`.
Note that **LifeTime** is set to **Singleton by default**

### Then : Scan your assemblies

To register your classes in the `ServiceCollection` you must add the `ScanAssemblies` instruction to your `Program.cs`
Expand Down Expand Up @@ -104,3 +113,84 @@ builder.Services.ScanAssemblies("MySuperAssembly", "MyAwesomeAssembly");
// Before this one
var app = builder.Build();
```

## How to use (keyed case)

### First : Decorate needed classes

#### With interfaces (Scoped)

Let's assume that we have an `IAuthService` interface

```CSharp
public interface IAuthService
{
string Register(RegisterDto registerDto);
}
```

And two custom auth services that implement this interface here it's `MySuperAuthService` and `MyFabulousAuthService`. And we needed it with the scoped life time.

```CSharp
using MeleagantDependencyScan.Attributes;

[MeleagantInjectionKeyed(LifeTime = ServiceLifetime.Scoped, VisibleFromInterface = true, VisibleAs = [typeof(IAuthService)], Key = "Super")]
public class MySuperAuthService : IAuthService
{
// Your auth logic
public string Register(RegisterDto registerDto)
{
// Register logic
}
}

[MeleagantInjectionKeyed(LifeTime = ServiceLifetime.Scoped, VisibleFromInterface = true, VisibleAs = [typeof(IAuthService)], Key = "Fabulous")]
public class MyFabulousAuthService : IAuthService
{
// Your auth logic
public string Register(RegisterDto registerDto)
{
// Register logic
}
}
```
#### With interfaces (Transient)

Same as `Scope` you just need to change the `LifeTime` to `Transient`

#### With interfaces (Singleton)

Same as `Scope` you just need to change the `LifeTime` to `Singleton`.
Note that **LifeTime** is set to **Singleton by default**

### Then : Scan your assemblies

To register your classes in the `ServiceCollection` you must add the `ScanAssemblies` instruction to your `Program.cs`

#### Single assembly

```CSharp
using MeleagantDependencyScan.Extensions;

// Some logic ...
// Use this instruction
builder.Services.ScanKeyedAssemblies("MySuperAssembly");

// Before this one
var app = builder.Build();
```

#### Multiple assembly

```CSharp
using MeleagantDependencyScan.Extensions;

// Some logic ...
// Use this instruction
builder.Services.ScanKeyedAssemblies("MySuperAssembly", "MyAwesomeAssembly");

// Before this one
var app = builder.Build();
```
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Data.SqlTypes;
using System.Linq;
using System.Reflection;
using FluentAssertions;
Expand All @@ -9,6 +10,8 @@ namespace MeleagantDependencyScan.UnitTest
{
public class MeleagantServiceCollectionExtensionsTests
{
#region Simple services

[Fact]
public void ServiceCollection_Should_Contain_Transient_Visible_By_ItSelf_HelloWorldService()
{
Expand Down Expand Up @@ -128,5 +131,139 @@ public void ServiceCollection_Should_Contain_Singleton_Visible_By_Interface_Hell

testResult.Should().NotBeNullOrEmpty().And.HaveCount(1);
}

#endregion

#region Keyed services

[Fact]
public void ServiceCollection_Should_Contain_Keyed_Transient_Visible_By_Interface_HelloWorldKeyedService()
{
// Arrange

var sc = new ServiceCollection();
sc.Clear();
sc.ScanKeyedAssemblies(Assembly.GetExecutingAssembly().GetName().Name!);

// Act

var testResult = sc.Where(s => s.ServiceType == typeof(IHelloWorldKeyedServices)
&& s.IsKeyedService
&& s.ServiceKey!.ToString() == "TKeyA"
&& s.KeyedImplementationType == typeof(HelloWorldKeyedTransientServicesWithInterfaceKeyA)
&& s.Lifetime == ServiceLifetime.Transient);

// Assert
testResult.Should().NotBeNullOrEmpty().And.HaveCount(1);
}

[Theory]
[InlineData("TKeyA")]
[InlineData("TKeyB")]
public void ServiceProvider_Should_Provide_TKey_Transient_Visible_By_Interface_HelloWorldKeyedService(string key)
{
// Arrange

var sc = new ServiceCollection();
sc.Clear();
sc.ScanKeyedAssemblies(Assembly.GetExecutingAssembly().GetName().Name!);
var sp = sc.BuildServiceProvider();

// Act

var testResult = sp.GetKeyedService<IHelloWorldKeyedServices>(key)?.SayHiKeyed($"{key} service");

// Assert
testResult.Should().Be($"Hello Keyed World {key} service with interface");
}

[Fact]
public void ServiceCollection_Should_Contain_Keyed_Scoped_Visible_By_Interface_HelloWorldKeyedService()
{
// Arrange

var sc = new ServiceCollection();
sc.Clear();
sc.ScanKeyedAssemblies(Assembly.GetExecutingAssembly().GetName().Name!);

// Act

var testResult = sc.Where(s => s.ServiceType == typeof(IHelloWorldKeyedServices)
&& s.IsKeyedService
&& s.ServiceKey!.ToString() == "SKeyA"
&& s.KeyedImplementationType == typeof(HelloWorldKeyedScopedServicesWithInterfaceKeyA)
&& s.Lifetime == ServiceLifetime.Scoped);

// Assert

testResult.Should().NotBeNullOrEmpty().And.HaveCount(1);
}

[Theory]
[InlineData("SKeyA")]
[InlineData("SKeyB")]
public void ServiceProvider_Should_Provide_SKey_Scoped_Visible_By_Interface_HelloWorldKeyedService(string key)
{
// Arrange

var sc = new ServiceCollection();
sc.Clear();
sc.ScanKeyedAssemblies(Assembly.GetExecutingAssembly().GetName().Name!);
var sp = sc.BuildServiceProvider();

// Act

var testResult = sp.GetKeyedService<IHelloWorldKeyedServices>(key)?.SayHiKeyed($"{key} service");

// Assert

testResult.Should().Be($"Hello Keyed World {key} service with interface");
}

[Fact]
public void ServiceCollection_Should_Contain_Keyed_Singleton_Visible_By_Interface_HelloWorldKeyedService()
{
// Arrange

var sc = new ServiceCollection();
sc.Clear();
sc.ScanKeyedAssemblies(Assembly.GetExecutingAssembly().GetName().Name!);

// Act

var testResult = sc.Where(s => s.ServiceType == typeof(IHelloWorldKeyedServices)
&& s.IsKeyedService
&& s.ServiceKey!.ToString() == "SiKeyA"
&& s.KeyedImplementationType == typeof(HelloWorldKeyedSingletonServicesWithInterfaceKeyA)
&& s.Lifetime == ServiceLifetime.Singleton);

// Assert

testResult.Should().NotBeNullOrEmpty().And.HaveCount(1);
}

[Theory]
[InlineData("SiKeyA")]
[InlineData("SiKeyB")]
public void ServiceProvider_Should_Provide_SiKey_Singleton_Visible_By_Interface_HelloWorldKeyedService(string key)
{
// Arrange

var sc = new ServiceCollection();
sc.Clear();
sc.ScanKeyedAssemblies(Assembly.GetExecutingAssembly().GetName().Name!);
var sp = sc.BuildServiceProvider();

// Act

var testResult = sp.GetKeyedService<IHelloWorldKeyedServices>(key)?.SayHiKeyed($"{key} service");

// Assert

testResult.Should().Be($"Hello Keyed World {key} service with interface");
}

#endregion

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using MeleagantDependencyScan.Attributes;
using Microsoft.Extensions.DependencyInjection;

namespace MeleagantDependencyScan.UnitTest.TestServices;

internal interface IHelloWorldKeyedServices
{
string SayHiKeyed(string fromWho);
}

[MeleagantInjectionKeyed(LifeTime = ServiceLifetime.Transient, VisibleFromInterface = true, VisibleAs = [typeof(IHelloWorldKeyedServices)], Key = "TKeyA")]
public class HelloWorldKeyedTransientServicesWithInterfaceKeyA : IHelloWorldKeyedServices
{
public string SayHiKeyed(string fromWho = "Transient KeyA")
{
return $"Hello Keyed World {fromWho} with interface";
}
}

[MeleagantInjectionKeyed(LifeTime = ServiceLifetime.Transient, VisibleFromInterface = true, VisibleAs = [typeof(IHelloWorldKeyedServices)], Key = "TKeyB")]
public class HelloWorldKeyedTransientServicesWithInterfaceKeyB : IHelloWorldKeyedServices
{
public string SayHiKeyed(string fromWho = "Transient KeyB")
{
return $"Hello Keyed World {fromWho} with interface";
}
}

[MeleagantInjectionKeyed(LifeTime = ServiceLifetime.Scoped, VisibleFromInterface = true, VisibleAs = [typeof(IHelloWorldKeyedServices)], Key = "SKeyA")]
public class HelloWorldKeyedScopedServicesWithInterfaceKeyA : IHelloWorldKeyedServices
{
public string SayHiKeyed(string fromWho = "Scoped KeyA")
{
return $"Hello Keyed World {fromWho} with interface";
}
}

[MeleagantInjectionKeyed(LifeTime = ServiceLifetime.Scoped, VisibleFromInterface = true, VisibleAs = [typeof(IHelloWorldKeyedServices)], Key = "SKeyB")]
public class HelloWorldKeyedScopedServicesWithInterfaceKeyB : IHelloWorldKeyedServices
{
public string SayHiKeyed(string fromWho = "Scoped KeyB")
{
return $"Hello Keyed World {fromWho} with interface";
}
}

[MeleagantInjectionKeyed(VisibleFromInterface = true, VisibleAs = [typeof(IHelloWorldKeyedServices)], Key = "SiKeyA")]
public class HelloWorldKeyedSingletonServicesWithInterfaceKeyA : IHelloWorldKeyedServices
{
public string SayHiKeyed(string fromWho = "Singleton KeyA")
{
return $"Hello Keyed World {fromWho} with interface";
}
}

[MeleagantInjectionKeyed(VisibleFromInterface = true, VisibleAs = [typeof(IHelloWorldKeyedServices)], Key = "SiKeyB")]
public class HelloWorldKeyedSingletonServicesWithInterfaceKeyB : IHelloWorldKeyedServices
{
public string SayHiKeyed(string fromWho = "Singleton KeyB")
{
return $"Hello Keyed World {fromWho} with interface";
}
}
Loading

0 comments on commit 01e40e3

Please sign in to comment.