Using asp.net 5
I'd like my controller to be injected with a Func<T>
instead of T
For example:
public HomeController(Func<Interfaces.IUnitOfWork> uow)
Instead of
public HomeController(Interfaces.IUnitOfWork uow)
Is it possible with the built-in DI or am I forced to move to an external DI?
Dependency Injection is done by supplying the DEPENDENCY through the class's constructor when creating the instance of that class. The injected component can be used anywhere within the class. Recommended to use when the injected dependency, you are using across the class methods.
To answer your question, there is no way. by definition, private constructors are inaccessible by other classes.
A collection of services for the application to compose. This is useful for adding user provided or framework provided services. C# Copy. public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; }
Func<T>
does not get registered or resolved by default but there is nothing stopping you from registering it yourself.
e.g.
services.AddSingleton(provider =>
new Func<IUnitOfWork>(() => provider.GetService<IUnitOfWork>()));
Note that you will also need to register IUnitOfWork itself in the usual way.
You can register a Func<T>
or a delegate with a ServiceCollection
. I recommend a delegate because it allows you to distinguish between different methods with identical signatures.
Here's an example.
public interface IThingINeed {}
public class ThingINeed : IThingINeed { }
public delegate IThingINeed ThingINeedFactory();
public class DelegateRegistrationTests
{
[Test]
public void RegisterDelegateFromDependency()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IThingINeed, ThingINeed>();
serviceCollection.AddTransient<ThingINeedFactory>(
provider => provider.GetService<IThingINeed>);
var serviceProvider = serviceCollection.BuildServiceProvider();
var factoryMethod = serviceProvider.GetService<ThingINeedFactory>();
var output = factoryMethod();
Assert.IsInstanceOf<ThingINeed>(output);
}
}
This almost looks like a service locator because the function we're resolving is actually IServiceCollection.GetService<ThingINeedFactory>()
. But that's hidden in the composition root. A class that injects this delegate depends on the delegate, not on the implementation.
You can use the same approach if the method you want to return belongs to a class that the container must resolve.
public interface IThingINeed
{
string SayHello();
}
public class ThingINeed : IThingINeed
{
private readonly string _greeting;
public ThingINeed(string greeting)
{
_greeting = greeting;
}
public string SayHello() => _greeting;
}
public class ThingINeedFactory
{
public IThingINeed Create(string input) => new ThingINeed(input);
}
public delegate IThingINeed ThingINeedFactoryMethod(string input);
public class DelegateRegistrationTests
{
[Test]
public void RegisterDelegateFromDependency()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IThingINeed, ThingINeed>();
serviceCollection.AddSingleton<ThingINeedFactory>();
serviceCollection.AddSingleton<ThingINeedFactoryMethod>(provider =>
provider.GetService<ThingINeedFactory>().Create);
var serviceProvider = serviceCollection.BuildServiceProvider();
var factoryMethod = serviceProvider.GetService<ThingINeedFactoryMethod>();
var created = factoryMethod("abc");
var greeting = created.SayHello();
Assert.AreEqual("abc", greeting);
}
}
Here's an extension method to (maybe) make it a little bit easier:
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterDelegate<TSource, TDelegate>(
this IServiceCollection serviceCollection,
Func<TSource, TDelegate> getDelegateFromSource)
where TDelegate : class
{
return serviceCollection.AddSingleton(provider =>
getDelegateFromSource(provider.GetService<TSource>()));
}
}
serviceCollection
.RegisterDelegate<ThingINeedFactory, ThingINeedFactoryMethod>(
factory => factory.Create);
While there is no built in Func building support in the default dependency injection for .net core we can build an extension method to add in all the missing funcs. We just need to make sure we call it at the end of registration.
public static class ServiceCollectionExtensions
{
private static MethodInfo GetServiceMethod;
static ServiceCollectionExtensions()
{
Func<IServiceProvider, object> getServiceMethod = ServiceProviderServiceExtensions.GetService<object>;
GetServiceMethod = getServiceMethod.Method.GetGenericMethodDefinition();
}
/// <summary>
/// Registers all Funcs in constructors to the ServiceCollection - important to call after all registrations
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
public static IServiceCollection AddFactories(this IServiceCollection collection)
{
// Get a list of all Funcs used in constructors of regigstered types
var funcTypes = new HashSet<Type>(collection.Where(x => x.ImplementationType != null)
.Select(x => x.ImplementationType)
.SelectMany(x => x.GetConstructors(BindingFlags.Public | BindingFlags.Instance))
.SelectMany(x => x.GetParameters())
.Select(x => x.ParameterType)
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(Func<>)));
// Get a list of already registered Func<> and remove them from the hashset
var registeredFuncs = collection.Select(x => x.ServiceType)
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(Func<>));
funcTypes.ExceptWith(registeredFuncs);
// Each func build the factory for it
foreach (var funcType in funcTypes)
{
var type = funcType.GetGenericArguments().First();
collection.AddTransient(funcType, FuncBuilder(type));
}
return collection;
}
/// <summary>
/// This build expression tree for a func that is equivalent to
/// Func<IServiceProvider, Func<TType>> factory = serviceProvider => new Func<TType>(serviceProvider.GetService<TType>);
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static Func<IServiceProvider, object> FuncBuilder(Type type)
{
var serviceProvider = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
var method = GetServiceMethod.MakeGenericMethod(type);
var call = Expression.Call(method, serviceProvider);
var returnType = typeof(Func<>).MakeGenericType(type);
var returnFunc = Expression.Lambda(returnType, call);
var func = Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(IServiceProvider), returnType), returnFunc, serviceProvider);
var factory = func.Compile() as Func<IServiceProvider, object>;
return factory;
}
}
In AddFactories we get a list of all the concreate types that are registered then check their constructors for any Func<>. From that list remove any Func that has been registered before. Using some expressiontrees we build the needed Funcs.
The code is also over in codereview, minus the check for already registered funcs.
I wrote a little extension method that registres the service and the factory (Func<T>
):
public static class IServiceCollectionExtension
{
public static IServiceCollection AddFactory<TService, TServiceImplementation>(this IServiceCollection serviceCollection)
where TService : class
where TServiceImplementation : class, TService
{
return serviceCollection
.AddTransient<TService, TServiceImplementation>();
.AddSingleton<Func<TService>>(sp => sp.GetRequiredService<TService>);
}
}
Usage:
serviceCollection
.AddFactory<IMyInterface, MyImplementation>()
As far as I'm aware deferring dependencies like this isn't possible using the current default IoC container within ASP.NET Core. I've not been able to get it working anyway!
To defer the initialisation of dependencies like this you'll need to implement an existing, more feature rich IoC container.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With