Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decorate class that relies on a runtime value for creation

I'm brand new to using Simple Injector although I have been using Ninject for a long time, so I am comfortable with DI in general. One thing that attracted me to want to use Simple Injector was the ease of use of Decorators.

I have been able to successfully use decorators with Simple Injector in all normal cases where the dependencies are resolved when the service is requested. However, I am having a hard time figuring out if there is a way to get my decorators applied in a case when the service must be constructed using a runtime value.

In Ninject, I could pass a ConstructorArgument to the kernel.Get<IService> request that could be inherited down the chain of N decorators all the way to the "real" implementing class. I cannot figure out a way to replicate that using Simple Injector.

I have put some very basic code below to illustrate. What I would want to do in the real world would be to pass an IMyClassFactory instance into other classes in my application. Those other classes could then use it to create IMyClass instances using the IRuntimeValue they would provide. The IMyClass instance they got from the IMyClassFactory would be decorated automatically by the registered decorators.

I know I could manually apply my decorator(s) in my IMyClassFactory or any Func<IMyClass> I could come up with, but I would like it to "just work".

I keep going around and around trying to abstract out the MyClass construction, but I can't figure out how to get it to resolve with the IRuntimeValue constructor argument and be decorated.

Am I overlooking an obvious solution?

using System;
using SimpleInjector;
using SimpleInjector.Extensions;

public class MyApp
{
    [STAThread]
    public static void Main()
    {
        var container = new Container();
        container.Register<IMyClassFactory, MyClassFactory>();
        container.RegisterDecorator(typeof (IMyClass), typeof (MyClassDecorator));

        container.Register<Func<IRuntimeValue, IMyClass>>(
                 () => r => container.GetInstance<IMyClassFactory>().Create(r));

        container.Register<IMyClass>(() =>  ?????));  // Don't know what to do

        container.GetInstance<IMyClass>(); // Expect to get decorated class
    }
}

public interface IRuntimeValue
{
}

public interface IMyClass
{
    IRuntimeValue RuntimeValue { get; }
}

public interface IMyClassFactory
{
    IMyClass Create(IRuntimeValue runtimeValue);
}

public class MyClassFactory : IMyClassFactory
{
    public IMyClass Create(IRuntimeValue runtimeValue)
    {
        return new MyClass(runtimeValue);
    }
}

public class MyClass : IMyClass
{
    private readonly IRuntimeValue _runtimeValue;

    public MyClass(IRuntimeValue runtimeValue)
    {
        _runtimeValue = runtimeValue;
    }

    public IRuntimeValue RuntimeValue
    {
        get
        {
            return _runtimeValue;
        }
    }
}

public class MyClassDecorator : IMyClass
{
    private readonly IMyClass _inner;

    public MyClassDecorator(IMyClass inner)
    {
        _inner = inner;
    }

    public IRuntimeValue RuntimeValue
    {
        get
        {
            return _inner.RuntimeValue;
        }
    }
}

Edit 1:

Ok, thanks to Steven for the great answer. It has given me a couple of ideas.

Maybe to make it a little more concrete though (although not my situation, more "classic"). Say I have an ICustomer that I create at runtime by reading a DB or deserializing from disk or something. So I guess that would be considered a "newable" to quote one of the articles Steven linked. I would like to create an instance of ICustomerViewModel so I can display and manipulate my ICustomer. My concrete CustomerViewModel class takes in an ICustomer in its constructor along with another dependency that can be resolved by the container.

So I have an ICustomerViewModelFactory that has a .Create(ICustomer customer) method defined which returns ICustomerViewModel. I could always get this working before I asked this question because in my implementation of ICustomerViewModelFactory I could do this (factory implemented in composition root):

return new CustomerViewModel(customer, container.GetInstance<IDependency>());

My issue was that I wanted my ICustomerViewModel to be decorated by the container and newing it up bypassed that. Now I know how to get around this limitation.

So I guess my follow-up question is: Is my design wrong in the first place? I really feel like the ICustomer should be passed into the constructor of CustomerViewModel because that demonstrates intent that it is required, gets validated, etc. I don't want to add it after the fact.

like image 631
Jon Comtois Avatar asked Mar 12 '14 23:03

Jon Comtois


1 Answers

Simple Injector explicitly lacks support for passing on runtime values through the GetInstance method. Reason for this is that runtime values should not be used when the object graph is constructed. In other words, the constructors of your injectables should not depend on runtime values. There are several problems with doing that. First of all, your injectables might need to live much longer than those runtime values do. But perhaps more importantly, you want to be able to verify and diagnose your container's configuration and that becomes much more troublesome when you start using runtime values in the object graphs.

So in general there are two solutions for this. Either you pass on the runtime value through the method call graph or you create a 'contextual' service that can supply this runtime value when requested.

Passing on the runtime value through the call graph is especially a valid solution when you practice architectures like this and this where you pass on messages through your system or when the runtime value can be an obvious part of the service's contract. In that case it is easy to pass on the runtime value with the message or the method and this runtime value will also pass through any decorator on the way through.

In your case this would mean that the factory creates the IMyService without passing in the IRuntimeValue and your code passes this value on to the IMyService using the method(s) it specifies:

var service = _myServiceFactory.Create();
service.DoYourThing(runtimeValue);

Passing through the runtime value through the call graph however is not always a good solution. Especially when this runtime value should not be part of the contract of the message that is sent. This especially holds for contextual information use as information about the current logged in user, the current system time, etc. You don't want to pass this information through; you just want it to be available. We don't want this, because this would give an extra burden to the consumers of passing the right value every time, while they probably shouldn't even be able to change this information (take the user in who's context the request is executed for instance).

In that case you should define service that can be injected and allows retrieving this context. For instance:

public interface IUserContext {
    User CurrentUser { get; }
}

public interface ITimeProvider {
    DateTime Now { get; }
}

In these cases the current user and the current time aren't injected directly into a constructor, but instead these services are. The component that needs to access the current user can simply call _userContext.CurrentUser and this will be done after the object is constructed (read: not inside the constructor). Thus: in a lazy fashion.

This does mean however that the IRuntimeValue must be set somewhere before MyClass gets invoked. This probably means you need to set it inside the factory. Here's an example:

var container = new Container();
var context = new RuntimeValueContext();
container.RegisterSingle<RuntimeValueContext>(context);
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof(IMyClass), typeof(MyClassDecorator));
container.Register<IMyClass, MyClass>();

public class RuntimeValueContext {
    private ThreadLocal<IRuntimeValue> _runtime;
    public IRuntimeValue RuntimeValue {
        get { return _runtime.Value; }
        set { _runtime.Value = value; }
    }
}

public class MyClassFactory : IMyClassFactory {
    private readonly Container _container;
    private readonly RuntimeValueContext context;
    public MyClassFactory(Container container, RuntimeValueContext context) {
        _container = container;
        _context = context;
    }

    public IMyClass Create(IRuntimeValue runtimeValue) {
        var instance = _container.GetInstance<IMyClass>();
        _context.RuntimeValue = runtimeValue;
        return instance;
    }
}

public class MyClass : IMyClass {
    private readonly RuntimeValueContext _context;
    public MyClass(RuntimeValueContext context) {
        _context = context;
    }
    public IRuntimeValue RuntimeValue { get { return _context.Value; } }
}

You can also let the MyClass accept the IRuntimeValue and make the following registration:

container.Register<IRuntimeValue>(() => context.Value);

But the disallows verifying the object graph, since Simple Injector will ensure that registrations never return null, but context.Value will be null by default. So another option is to do the following:

container.Register<IMyClass>(() => new MyClass(context.Value));

This allows the IMyClass registration to be verified, but will during verification still create a new MyClass instance that is injected with a null value. If you have a guard clause in the MyClass constructor, this will fail. This registration however disallows MyClass to be auto-wired by the container. Auto-wiring that class can come in handy when you've got more dependencies to inject into MyClass for instance.

like image 170
Steven Avatar answered Oct 23 '22 09:10

Steven