Search code examples
c#dependency-injectionlight-inject

Resolve object using DI container with object instance


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).


Solution

  • 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));