The Goal:
I am trying to dynamically register delegates for creating objects in simple injector container. Basically I want to be able not only to get actual instances from DI container but also a methods that could be used to create those instances. This could be useful for lazy object creation. For example if I have a service with a lot of dependencies, I don't want them to be created on service object creation but only when those specific dependencies need to be used.
The Problem:
When I usecontainer.Register<T>
method I can successfully register delegates:
container.Register<Func<IRepository>>(() => container.GetInstance<IRepository>);
The problem is when I want to register those kind of delegates dynamically when the type is known only at runtime:
Here is my code:
private static void RegisterFunctions(Container container)
{
var types = container.GetCurrentRegistrations()
.Where(r => r.ServiceType.IsInterface && r.ServiceType != typeof(Func<>))
.Select(r => r.ServiceType);
var typesList = types as IList<Type> ?? types.ToList();
foreach (var t in typesList)
{
var typeToRegister = typeof(Func<>).MakeGenericType(t);
//This needs to be replaced:
container.Register(typeToRegister, () => container.GetInstance(t));
}
}
The problem is in container.Register(typeToRegister, () => container.GetInstance(t));
I assumed that it's behavior will be the same as container.Register<T>
method but I was wrong.
When I run this code for the following scenario:
public interface IProductService
{
}
public class ProductService : IProductService
{
public ProductService(Func<IProductRepository> getRepositoryFunc)
{
this.ProductRepositoryFunc = getRepositoryFunc;
}
}
I am getting an System.InvalidCastException
:
[InvalidCastException: Unable to cast object of type 'XXX.ProductRepository' to type 'System.Func'1[XXX.IProductRepository]'.]
lambda_method(Closure ) +83
lambda_method(Closure ) +179
SimpleInjector.Scope.CreateAndCacheInstance(ScopedRegistration'2 registration) +74
SimpleInjector.Scope.GetInstance(ScopedRegistration'2 registration) +260 SimpleInjector.Scope.GetInstance(ScopedRegistration'2 registration, Scope scope) +207
SimpleInjector.Advanced.Internal.LazyScopedRegistration'2.GetInstance(Scope scope) +241 lambda_method(Closure ) +310
SimpleInjector.InstanceProducer.GetInstance() +117[ActivationException: The registered delegate for type ProductController threw an exception. Unable to cast object of type 'XXX.ProductRepository' to type 'System.Func'1[XXX.IProductRepository]'.]
SimpleInjector.InstanceProducer.GetInstance() +222
SimpleInjector.Container.GetInstance(Type serviceType) +148
SimpleInjector.Integration.Web.Mvc.SimpleInjectorDependencyResolver.GetService(Type serviceType) +137
System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +87[InvalidOperationException: An error occurred when trying to create a controller of type 'YYY.ProductController'. Make sure that the controller has a parameterless public constructor.] System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +247
System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +438
System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +257
System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +326 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +157
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +88
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +50
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
I understand the exception and the issue that causes it but I am struggling to find a correct way to implement the described functionality.
So if I summarize my question:
Is there a way to perform a registration similar to:
container.Register<Func<IRepository>>(() => container.GetInstance<IRepository>)
for a dynamic type that that resolved in runtime, something like
container.Register<Func<T>>(() => container.GetInstance<T>)
The problem here is that you are using the wrong Register
overload. You do this:
container.Register(typeToRegister, () => container.GetInstance(t));
Because of the way C# overload resolution works, the following overload is selected:
Register(Type serviceType, Func<object> instanceCreator)
This overload however, expects that the returned value from the supplied Func<object>
is of type serviceType
. But this is not the case in your case, because you want to register the delegate itself. There are a few ways to solve this.
For instance, you can supply this overload with a Func<object>
that returns the actual delegate as follows:
Func<object> func = () => container.GetInstance(t);
container.Register(typeToRegister, () => func);
But since the delegate that you register itself is a singleton, it's better to do it as follows:
Func<object> func = () => container.GetInstance(t);
container.RegisterSingleton(typeToRegister, () => func);
This is better, because this prevents the Diagnostic Services from reporting Lifestyle Mismatches on the consumers of your Func<T>
registrations.
But since all you want to do is register a single instance (the delegate), you can also use the RegisterSingleton(Type, object)
overload as follows:
Func<object> func = () => container.GetInstance(t);
container.RegisterSingleton(typeToRegister, (object)func);
The effect is the same, but this is a bit cleaner IMO.
Do note however that none of the given solutions actually work. They don't work because we're constructing a Func<object>
, while trying to register a Func<SomethingElse>
, and a Func<object>
can't be casted and this will cause either the registration to fail, or the application to fail when resolving or injecting such instance.
So the real solution is to construct the exact Func<T>
type and register that. And this is exactly what the example in the example in the register factory delegates section of the documentation does:
container.ResolveUnregisteredType += (s, e) =>
{
var type = e.UnregisteredServiceType;
if (!type.IsGenericType ||
type.GetGenericTypeDefinition() != typeof(Func<>))
return;
Type serviceType = type.GetGenericArguments().First();
InstanceProducer producer = container.GetRegistration(serviceType, true);
Type funcType = typeof(Func<>).MakeGenericType(serviceType);
var factoryDelegate =
Expression.Lambda(funcType, producer.BuildExpression()).Compile();
e.Register(Expression.Constant(factoryDelegate));
};
Here unregistered type resolution is used to build Func<T>
delegates on the fly in the background. The effect is about the same as what you are doing with the manual calls to Register
, but the behaviour is more deterministic, because your RegisterFunctions
must be called last before all other registrations, while the timing for this method does not matter. It also prevents your DI configuration to be 'polluted' with Func<T>
registrations that are never used. This makes it somewhat easier to browse through the registrations and see how your real configuration looks like.
This all said though, the following worries me:
For example if I have a service with a lot of dependencies, I don't want them to be created on service object creation but only when those specific dependencies need to be used.
Even a moderately fast container, will be able to build up object graphs at a speed that the size of the object graph should hardly ever be a problem. Simple Injector on the other hand is extremely fast. Delaying the building of part of your object graph using Func<T>
for performance is useless and only pollutes your code base. Using a Func<T>
as abstraction is in a sense a Leaky Abstraction (a violation of the Dependency Inversion Principle), because the dependency now tells the consumer something about the implementation; that it is heavy to create. But this is an implementation detail and the consumer should not be bottered with that. It makes code harder to read, and the consumer harder to test.
If however you have trouble with the time it takes to construct object graphs, there might be a problem in your application. Injection constructors should be simple and fast. Letting a constructor do more than just storing the incoming dependencies is violation of the SRP.
It's because of this that Simple Injector does not build Func<T>
delegates for you out of the box.