2

I have a ICommand interface and tasks that are using dependencies injected by constructor. Dependencies are using different constructors so they have to be resolved by the request itself. I want to tell my container how to resolve some dependencies in the specific context it's being resolved.

interface ICommand
{
    string Do();
}

interface IUser
{
    string Name { get; }
}

class Welcome : ICommand
{
    IUser _user;
    public Welcome(IUser user)
    {
        _user = user;
    }

    public string Do()
    {
        return "Hello, "+_user.Name;
    }
}

class OAuthUser : IUser
{
    // use remote service to get data
    public OAuthUser (IOAuthService service, JsonWebToken token)
    {
        // to be implemented
    }
}

class TemporaryTokenUser : IUser
{
    // use sql to check if user has temporary token
    public TemporaryTokenUser (IDbConnection db, string token)
    {
        // to be implemented
    }
}

class UserPasswordUser : IUser
{
    // try authenticating user with credentials
    public UserPasswordUser (IAuthService svc, string user, string password)
    {
        // to be implemented
    }
}

I've registered my interfaces and classes in LightInject:

var container = new LightInject.ServiceContainer();
container.Register<ICommand, Welcome>("welcome");

Now, I want to do something like this in my requests:

using (var scope = container.BeginScope())
{
    // I need to tell my container how to resolve this dependency in case its needed
    // but method below does not exist
    scope.ResolverForScope<IUser>(()=>createIUser(request));
    var command = container.GetInstance<ICommand>(command);
    return command.Do();
}

What would be the correct way to do this in maintainable way with any DI container, considering that dependency chain might get quite long for complex methods?

EDIT I made my use case more clear (changed classes implementing IUser).

Ryszard Fiński
  • 452
  • 3
  • 7
  • I would have in your registration a switch, that says if remote instantiate with this object, if local instantiate with another. Better yet, I would have one method that instantiates all your objects using remote, and another method that instantiates all your objects using local.... – Callum Linington Jun 08 '15 at 15:32
  • But wouldn't a registration switch disable one access method for the whole application instance? – Ryszard Fiński Jun 08 '15 at 15:38
  • it would be per application instance yeah. If I understood it correctly you just want one instance for remote and one for local? or are you in need of them both being available per application instance? – Callum Linington Jun 08 '15 at 15:56
  • I need them both, I've edited the question to be more clear on what I want to do. – Ryszard Fiński Jun 08 '15 at 16:19
  • I personally don't think you have the correct approach to your problem. I would use a strategy pattern and factory pattern to get the correct information and new up an `IUser` implementation. You wouldn't be working out what to do depending on the Implementations... The remote, sql and credentials information must be obtained from something else. – Callum Linington Jun 09 '15 at 07:01
  • I understand the idea behind using a factory - createIUser could be indeed replaced with a factory call. but I don't understand how to I pass multiple different arguments to my container before it even knows if it's going to need those arguments. I've updated question with arguments. Could you post an example? – Ryszard Fiński Jun 09 '15 at 12:00
  • I've just had a thought on what you said! you would have your factory create you those users, and have the DI container (resolver) return them depending on the input. Are you using WebApi or WPF or... – Callum Linington Jun 09 '15 at 13:15
  • SOAP wcf service, rest with owin (and multiple scenarios of authentication depending on input) and tests mocking incoming requests. I've thought about using some temporary struct with all fields usable by the factory (and factory would decide, based on fields provided), but combining that much fields sounds like a terrible mess (unless I used proxy containing Dictionary or ExpandoObject, but that sounds painful to use because of no protection against typos). – Ryszard Fiński Jun 09 '15 at 13:40
  • Yeah I think I may have the answer for you. Will post in a bit – Callum Linington Jun 09 '15 at 13:59
  • @CallumLinington I ended up going for ISecurityScope with scope lifetime that throws UnauthorizedAccessException in case it had no Authenticate method used - the approach is similar to ScopedContainer, but is more clear and verbose about the error. I think that storing some object with credentials per scope is crucial here and couldn't figure out any better approach. – Ryszard Fiński Jun 12 '15 at 15:21

2 Answers2

2
static class ScopedContainerExtensions
{
    class ScopedContainer
    {
        Dictionary<Type, object> factories = new Dictionary<Type,object>();
        public void Register<T>(Func<T> factory)
            where T: class
        {
            factories.Add(typeof(T), new Lazy<T>(factory));
        }

        public T Resolve<T>()
        {
            return ((Lazy<T>)factories[typeof(T)]).Value;
        }
    }

    public static void UseScopedContainerFor<Service>(this IServiceContainer container)
    {
        if (!container.CanGetInstance(typeof(ScopedContainer), ""))
        {
            container.Register<ScopedContainer>(new PerScopeLifetime());
        }
        container.Register<Service>(sf=>sf.GetInstance<ScopedContainer>().Resolve<Service>());
    }

    public static void ResolverForCurrentScope<T>(this IServiceContainer container, Func<IServiceFactory, T> factory)
        where T : class
    {
        var scope = container.ScopeManagerProvider.GetScopeManager().CurrentScope;
        container.GetInstance<ScopedStorage>().Register<T>(() =>
        {
            var instance = factory(container);
            var disposable = instance as IDisposable;
            if (disposable != null)
                scope.TrackInstance(disposable);
            return instance;
        });
    }

Registration:

container.UseScopedContainerFor<IUser>();

Usage in scope:

container.ResolverForCurrentScope<IUser>(fac => fac.GetInstance<OAuthUserFactory>().Create(fac.GetInstance<IOAuthService>(), Request));
Ryszard Fiński
  • 452
  • 3
  • 7
1

It might be developed via using the Factory pattern.

With this approach, you might be able to get an instance of the specific user with a Factory to provide instances for each concrete class.

Using explicit service registration:

var container = new LightInject.ServiceContainer();
//register your command passing a user instance
container.Register<ICommand>(factory => new Welcome(factory.GetUser<IUser>(request)));

using (var scope = container.BeginScope())
{        
    var command = (ICommand)container.GetInstance<ICommand>();
    return command.Do();
}

I just referred to LightInject web page. There is a chapter called "Dependency Constructors" for further information. http://www.lightinject.net/#toc16

Hope it helps

cloud120
  • 3,176
  • 2
  • 13
  • 6
  • That's actually correct approach, but I'm afraid that for long dependency chains it sounds too verbose. Imagine command like `SendInvitationWithReferenceLinkFromUser(IReferalService ref, IUser user, IEmailService)` with `IReferalService` and `IEmailService` requiring a lot of dependencies (including `IUser` that's needed for `IReferalService`). I also don't want to register a factory per request, because I think registering all things in a container can take a bit longer than just using it. – Ryszard Fiński Jun 08 '15 at 17:28