NamedResolver 2.1.0
See the version list below for details.
dotnet add package NamedResolver --version 2.1.0
NuGet\Install-Package NamedResolver -Version 2.1.0
<PackageReference Include="NamedResolver" Version="2.1.0" />
paket add NamedResolver --version 2.1.0
#r "nuget: NamedResolver, 2.1.0"
// Install NamedResolver as a Cake Addin #addin nuget:?package=NamedResolver&version=2.1.0 // Install NamedResolver as a Cake Tool #tool nuget:?package=NamedResolver&version=2.1.0
NamedResolver
An abstraction that provide ability to use interface with multiple implementations or preconfigured instances in easy way using local ServiceLocator pattern.
All code covered with unit test by 100%.
Look at tests for additional examples.
Installing NamedResolver
You should install NamedResolver with NuGet:
Install-Package NamedResolver
Or via the .NET Core command line interface:
dotnet add package NamedResolver
Use cases
let's assume we have this:
public interface ISomeInterface {}
public class FirstImplementation : ISomeInterface {}
public class SecondImplementation : ISomeInterface {}
And register in dependency injection container like this:
services.AddScoped<ISomeInterface, FirstImplementation>();
services.AddScoped<ISomeInterface, SecondImplementation>();
And sometimes we need to resolve one of implementation of interface directly.
In most cases, for resolve, we inject IEnumerable<TInterface>
in our DependentClass
and then search using typeof(T).
public class DependentClass
{
private readonly ISomeInterface _firstImplementation;
public DependentClass(IEnumerable<ISomeInterface> implementations)
{
// in this case we always resolve from DI all implementations of ISomeInterface.
_firstImplementation =
implementations.SingleOrDefault(i => i.GetType() == typeof(FirstImplementation)) ??
throw new ArgumentNullException($"Cannot resolve instance of type {typeof(FirstImplementation).FullName}");
}
}
Also we can register to IServiceCollection our implementation without interface, and inject implementation into DependenentClass explicitly, but it is not good for unit testing:
services.AddScoped<FirstImplementation>();
services.AddScoped<SecondImplementation>();
public class DependentClass
{
private readonly ISomeInterface _firstImplementation;
public DependentClass(FirstImplementation firstImplementation)
{
_firstImplementation = firstImplementation;
}
}
Same with NamedResolver
services.AddNamed<string, ISomeInterface>(ServiceLifeTime.Scoped)
.Add<FirstImplementation>("First");
.Add<SecondImplementation>("Second");
public class DependentClass
{
private readonly ISomeInterface _firstImplementation;
public DependentClass(INamedResolver<string, ISomeInterface> resolver)
{
_firstImplementation = resolver.Get("First");
}
}
Features:
Default instance:
public class DefaultImplementation : ISomeInterface {}
services.AddNamed<string, ISomeInterface>(ServiceLifeTime.Scoped)
.Add<FirstImplementation>("First");
.Add<SecondImplementation>("Second")
.Add<DefaultImplementation>(); // default - without name parameter
public class DependentClass
{
private readonly ISomeInterface _defaultImplementation;
public DependentClass(ISomeInterface someInterface)
{
// DefaultImplementation would be injected
_defaultImplementation = someInterface;
}
}
Preconfigured instances
services.AddNamed<string, ISomeInterface>(ServiceLifeTime.Scoped)
.Add<FirstImplementation>("FirstUseCase", _ => new FirstImplementation("FirstUseCase"));
.Add<FirstImplementation>("SecondUseCase", _ => new FirstImplementation("SecondUseCase"));
Enum or custom class as discriminator
public enum MyEnum
{
Default = 0,
FirstUseCase = 1,
SecondUseCase = 2
}
services.AddNamed<MyEnum, ISomeInterface>()
.Add<FirstImplementation>(MyEnum.FirstUseCase)
.Add<SecondImplementation>(MyEnum.SecondUseCase)
.Add<DefaultImplementation>(); // or MyEnum.Default
for correct search type by class you also should implement IEquatable<T> or IEqualityComparer<T> and provide it to AddNamed method. By default used EqualityComparer<T>.Default
Resolve all
public class DependentClass
{
private readonly IEnumerable<ISomeInterface> _implementations;
private readonly IEnumerable<ISomeInterface> _fromSampleNamespace;
private readonly IEnumerable<(string name, ISomeInterface instance)> _implementationsWithNames;
public DependentClass(INamedResolver<string, ISomeInterface> resolver)
{
_implementations = resolver.GetAll();
_implementationsWithNames = resolver.GetAllWithNames();
_fromSampleNamespace = resolver.GetAll(t => t.Namespace.StartsWith("Sample"));
}
}
or with IReadOnlyList<T>. With injecting IEnumerable<T> you get only default implementation or InvalidOperationException.
public class DependentClass
{
private readonly IReadOnlyList<ISomeInterface> _implementations;
// same result as INamedResolver<string, ISomeInterface>.GetAll method
public DependentClass(IReadOnlyList<ISomeInterface> implementations)
{
_implementations = implementations;
}
}
Resolve with delegate
public delegate TInterface ResolveNamed<in TDiscriminator, out TInterface>(
TDiscriminator name = default
)
public class SomeClass
{
private readonly ISomeInterface _implementation;
public SomeClass(ResolveNamed<string, ISomeInterface> resolveNamedFunc)
{
_implementation = resolveNamedFunc("Test");
}
}
Safe TryAdd both generic/non-generic method
services.AddNamed<string, ISomeInterface>(ServiceLifeTime.Scoped)
.Add<FirstImplementation>("FirstUseCase", _ => new FirstImplementation("FirstUseCase"));
// instance with name "FirstUseCase" already registered above,
// this TryAdd with same name has no effect.
.TryAdd<FirstImplementation>("FirstUseCase", _ => new FirstImplementation("SecondUseCase"));
TryAdd use case
this method would be usefull for other libraries. Sometimes we want to register some default implementation after user configure callback call, without exceptions or unwanted replaces with unexpected behaviour:
#region library code
public class SomeLibraryOptions
{
public INamedRegistratorBuilder<string, ISomeInterface> SomeInterfaceRegistrator { get; }
public SomeLibraryOptions(INamedRegistratorBuilder<string, ISomeInterface> registratorBuilder)
{
SomeInterfaceRegistrator = registratorBuilder;
}
}
public static class SomeLibraryServiceCollectionExtensions
{
public static IServiceCollection AddSomeLibrary(this IServiceCollection services, Action<SomeLibraryOptions> configure = null)
{
// take a builder reference
var builder = services.AddNamed<string, ISomeInterface>();
// init library options with builder
var options = new SomeLibraryOptions(builder);
// let call configure options callback with user code if it not null
configure?.Invoke(options);
// try to add default implementation, if user not configured it.
builder.TryAdd<DefaultImplementation>();
return services;
}
}
#endregion library code
#region user code
services.AddSomeLibrary((options) =>
{
options.SomeInterfaceRegistrator
.Add<UserCustomImplementation>(); // registered as default.
});
#endregion user code
Contribute
Feel free for creation issues, or PR 😃
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 is compatible. netcoreapp2.2 is compatible. netcoreapp3.0 is compatible. netcoreapp3.1 is compatible. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETCoreApp 2.1
-
.NETCoreApp 2.2
-
.NETCoreApp 3.0
-
.NETCoreApp 3.1
-
.NETStandard 2.0
-
.NETStandard 2.1
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.