ASP.NET Core’s Dependency Injection system is a solid implementation and can provide solutions for most DI needs. But, like most DI systems, it is an opinionated API (and rightly so). I moved to the ASP.NET Core implementation from Ninject and, in doing so, there were a couple of Ninject methods that I really missed. Especially the .Rebind functions.
These are the functions that will take a interface-to-implementation binding in the system, and remove the old binding and set up a new one; with new lifetime scoping and new configuration/implementation details. With ASP.NET Core’s system, they really want the developer of the application to setup exactly the bindings that they desire at the very beginning of the program. And, the first binding that’s put in place should be the last binding made for that interface-to-implementation approach.
Their approach is well reasoned and it has it’s merits. It should lower overall confusion and needed knowledge when trying to figure out what bindings are being used. If you start reading Startup.cs’s ConfigureServices and you find a binding declaration, that should be the correct binding which will be resolved at runtime.
However, because of Ninject’s .Rebind functions, I am stuck in the mind set that bindings should be flexible as new subsystems are added. If you make a library, MyLib, that has a default caching implementation that uses InMemory caching, then your library will most likely setup a binding of IMyLibCache to MyLibInMemoryCache. If I then create an add-on library that implements caching using redis, MyLib.Redis, then I want to be able to swap out the binding of IMyLibCache with a new binding to MyLibRedisCache.
With the prescribed API of ASP.NET Core’s DI system, the way you would do this in code would look something like this:
services.AddMyLibRedis(); // binds IMyLibCache to MyLibRedisCache | |
services.AddMyLib(); // would internally use services.TryAddTransient<IMyLibCache, MyLibInMemoryCache>() which would not succeed |
But, that just feels backwards. When you were writing your original code, you would have to know upfront that someone in the future would have a need to use a different caching system. So, you would have to have the forethought to create the binding using .TryAddTransient() instead of .AddTransient().
It would feel much more natural if it was written like this:
services.AddMyLib(); // would do a default binding of services.AddTransient<IMyLibCache, MyLibInMemoryCache>() | |
services.AddMyLibRedis(); // should do a rebind with services.RebindTransient<IMyLibCache, MyLibRedisCache>() |
So, that’s the Ninject thinking that is stuck in my head. And, because of it, here are a few convenience overloads which can make working with IServiceCollection a little bit easier:
using System; | |
using System.Linq; | |
namespace Microsoft.Extensions.DependencyInjection | |
{ | |
/// <summary> | |
/// Extensions methods that for the IServiceCollection object that can be used across many different projects types (Web, Exe, Batch, etc). | |
/// </summary> | |
public static class ConvenienceIServiceCollectionExtensions | |
{ | |
/// <summary> | |
/// Tests if the service collection contains a "binding" for the given type. | |
/// </summary> | |
/// <param name="services">The collection to read through.</param> | |
/// <param name="serviceType">The type to find.</param> | |
/// <returns>True if the type is found. False otherwise.</returns> | |
public static bool Contains(this IServiceCollection services, Type serviceType) | |
{ | |
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == serviceType); | |
return serviceDescriptor != null; | |
} | |
/// <summary> | |
/// Tests if the service collection contains a "binding" for the given type. | |
/// </summary> | |
/// <param name="services">The collection to read through.</param> | |
/// <param name="serviceType">The type to find.</param> | |
/// <returns>True if the type is found. False otherwise.</returns> | |
public static bool Contains<T>(this IServiceCollection services) | |
{ | |
return Contains(services, typeof(T)); | |
} | |
/// <summary> | |
/// Removes a "binding" from the service collection. This functionality is also available from the standard | |
/// IServiceCollection (<see cref="IServiceCollection.Remove" />), but you can to find the underlying | |
/// <see cref="ServiceDescriptor" /> object which contains the binding. The is a convenience overload | |
/// that does that search and removal for you. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The type binding to remove.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection Remove(this IServiceCollection services, Type serviceType) | |
{ | |
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == serviceType); | |
if (serviceDescriptor != null) services.Remove(serviceDescriptor); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" from the service collection. This functionality is also available from the standard | |
/// IServiceCollection (<see cref="IServiceCollection.Remove" />), but you can to find the underlying | |
/// <see cref="ServiceDescriptor" /> object which contains the binding. The is a convenience overload | |
/// that does that search and removal for you. | |
/// </summary> | |
/// <typeparam name="T">The type binding to remove.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection Remove<T>(this IServiceCollection services) | |
where T : class | |
{ | |
return services.Remove(typeof(T)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it in the new scope. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindTransient( | |
this IServiceCollection services, | |
Type serviceType | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddTransient(serviceType); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it to type <typeparamref name="TImplementation" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationType">The type to bind to.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindTransient( | |
this IServiceCollection services, | |
Type serviceType, | |
Type implementationType | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddTransient(serviceType, implementationType); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it in the new scope. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <typeparam name="TService">The service/interface type to bind for lookup.</typeparam> | |
/// <typeparam name="TImplementation">The concrete/implementation to bind to.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindTransient<TService>(this IServiceCollection services) | |
where TService : class | |
{ | |
return services.RebindTransient(typeof(TService)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it to type <typeparamref name="TImplementation" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <typeparam name="TService">The service/interface type to bind for lookup.</typeparam> | |
/// <typeparam name="TImplementation">The concrete/implementation to bind to.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindTransient<TService, TImplementation>(this IServiceCollection services) | |
where TService : class | |
where TImplementation : class, TService | |
{ | |
return services.RebindTransient(typeof(TService), typeof(TImplementation)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationFactory">A factory to generate the requested service/interface type.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindTransient( | |
this IServiceCollection services, | |
Type serviceType, | |
Func<IServiceProvider, object> implementationFactory | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddTransient(serviceType, implementationFactory); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="implementationFactory">A factory to generate the requested service/interface type.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindTransient<T>( | |
this IServiceCollection services, | |
Func<IServiceProvider, object> implementationFactory | |
) | |
{ | |
return RebindTransient(services, typeof(T), implementationFactory); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it in the new scope. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddSingleton{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationType">The type to bind to.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton( | |
this IServiceCollection services, | |
Type serviceType | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddSingleton(serviceType); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to type <paramref name="implementationType" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddSingleton{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationType">The type to bind to.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton( | |
this IServiceCollection services, | |
Type serviceType, | |
Type implementationType | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddSingleton(serviceType, implementationType); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it in the new scope. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddSingleton{TService, TImplementation}" />. | |
/// </summary> | |
/// <typeparam name="TService">The service/interface type to bind for lookup.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton<TService>(this IServiceCollection services) | |
where TService : class | |
{ | |
return services.RebindSingleton(typeof(TService)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it to type <typeparamref name="TImplementation" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddSingleton{TService, TImplementation}" />. | |
/// </summary> | |
/// <typeparam name="TService">The service/interface type to bind for lookup.</typeparam> | |
/// <typeparam name="TImplementation">The concrete/implementation to bind to.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton<TService, TImplementation>(this IServiceCollection services) | |
where TService : class | |
where TImplementation : class, TService | |
{ | |
return services.RebindSingleton(typeof(TService), typeof(TImplementation)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddSingleton{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationFactory">A factory to generate the requested service/interface type.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton( | |
this IServiceCollection services, | |
Type serviceType, | |
Func<IServiceProvider, object> implementationFactory | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddSingleton(serviceType, implementationFactory); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="implementationFactory">A factory to generate the requested service/interface type.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton<T>( | |
this IServiceCollection services, | |
Func<IServiceProvider, object> implementationFactory | |
) | |
{ | |
return RebindSingleton(services, typeof(T), implementationFactory); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="implementationInstance">A instance of type T.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindSingleton<T>( | |
this IServiceCollection services, | |
T implementationInstance | |
) where T : class | |
{ | |
services.Remove(typeof(T)); | |
services.AddSingleton<T>(implementationInstance); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it in the new scope. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddScoped{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindScoped( | |
this IServiceCollection services, | |
Type serviceType | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddScoped(serviceType); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to type <paramref name="implementationType" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddScoped{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationType">The type to bind to.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindScoped( | |
this IServiceCollection services, | |
Type serviceType, | |
Type implementationType | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddScoped(serviceType, implementationType); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it in the new scope. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddScoped{TService, TImplementation}" />. | |
/// </summary> | |
/// <typeparam name="TService">The service/interface type to bind for lookup.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindScoped<TService>(this IServiceCollection services) | |
where TService : class | |
{ | |
return services.RebindScoped(typeof(TService)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <typeparamref name="TService" /> and rebinds it to type <typeparamref name="TImplementation" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddScoped{TService, TImplementation}" />. | |
/// </summary> | |
/// <typeparam name="TService">The service/interface type to bind for lookup.</typeparam> | |
/// <typeparam name="TImplementation">The concrete/implementation to bind to.</typeparam> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindScoped<TService, TImplementation>(this IServiceCollection services) | |
where TService : class | |
where TImplementation : class, TService | |
{ | |
return services.RebindScoped(typeof(TService), typeof(TImplementation)); | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddScoped{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="serviceType">The service/interface type to bind for lookup.</param> | |
/// <param name="implementationFactory">A factory to generate the requested service/interface type.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindScoped( | |
this IServiceCollection services, | |
Type serviceType, | |
Func<IServiceProvider, object> implementationFactory | |
) | |
{ | |
services.Remove(serviceType); | |
services.AddScoped(serviceType, implementationFactory); | |
return services; | |
} | |
/// <summary> | |
/// Removes a "binding" for the type <paramref name="serviceType" /> and rebinds it to the type | |
/// returned from <paramref name="implementationFactory" />. | |
/// This is a convenience overload which calls <see cref="Remove{T}" /> and then | |
/// <see cref="ServiceCollectionServiceExtensions.AddTransient{TService, TImplementation}" />. | |
/// </summary> | |
/// <param name="services">The <see cref="IServiceCollection" /> object to update.</param> | |
/// <param name="implementationFactory">A factory to generate the requested service/interface type.</param> | |
/// <returns>The <see cref="IServiceCollection" /> object which was referenced/passed in.</returns> | |
public static IServiceCollection RebindScoped<T>( | |
this IServiceCollection services, | |
Func<IServiceProvider, object> implementationFactory | |
) | |
{ | |
return RebindScoped(services, typeof(T), implementationFactory); | |
} | |
} | |
} |