This project is read-only.

Ninject equivalents, constructor arguments.

Sep 27, 2012 at 3:44 AM
Edited Sep 27, 2012 at 3:49 AM

On the Ninject we can do something like follow:

using (var unitOfWork = Factory.CreateUnitOfWork()){
	var users = Factory.Resolve<IUserRepository>( unitOfWork );
}

The Ninject allows pass a parameter for the constructor, in this case my UnitOfWork.

On my Factory class, the Resolve method is like:

public static T Resolve<T>(IUnitOfWork uow) { 
	return Factory.Instance.Kernel.Get<T>(new ConstructorArgument("unitOfWork", uow));
}

There's a way on the SimpleInjector that allows me to do something like:

public static T Resolve<T>(IUnitOfWork uow) { 
	return Factory.Instance.Container.GetInstance<T>( uow );
}

According what I saw on the documentation, the only moment that can I pass a parameter for the constructor is on the container.Register.

Really? There's no way pass the parameter on the moment of class creation?

Thanks.

Coordinator
Sep 27, 2012 at 11:13 AM
Edited Oct 6, 2012 at 3:05 PM

Your observation is correct. Simple Injector does not allow you to pass constructor arguments to the GetInstance() method. And the lack of this feature is deliberate. This forces you to have a clean design and this allows Simple Injector to optimize performance. If you take a step back and analyse your problem, you'll find out that in most cases you won't need this, and other cases, you can solve the problem by implementing a custom factory class.

There are multiple ways to solve this issue. The most obvious is to define the IUnitOfWork with a Per Lifetime Scope or Per Web Request. This allows you to let the same IUnitOfWork to be injected into all repositories within that scope, without the need for passing an instance back to the container:

container.RegisterPerWebRequest<IUnitOfWork>(() => ...);


This allows you to resolve or inject repositories without calling back to the container.

If you're not running in web application, you can use the Per Lifetime Scope lifestyle:

container.RegisterLifetimeScope<IUnitOfWork>(() => ...);

 


In that case you need to explicitly begin and end the scope:

using (container.BeginLifetimeScope())
{
    var rep1 = container.GetInstance<IUserRepository>();
    var rep2 = container.GetInstance<IOrderRepository>();

// Both repositories will get the same unit of work.
Assert.IsTrue(object.ReferenceEquals(rep1.UoW, rep2.UoW)); }

Depending on your requirements and design, you might want to take a different approach, but this is this is the first thing that comes to mind, after reading your question.

Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:40 PM
Sep 27, 2012 at 3:16 PM
Edited Sep 27, 2012 at 3:17 PM

Thank you, your tip elegantly solved my impass.

Jan 10, 2013 at 2:42 PM
Edited Jan 10, 2013 at 4:30 PM

Maybe following solution could be helpful:
1. Let's override Container:

public class ExtendedContainer: Container
   {
      private class OneParamHolder<PFirst>
      {
         static OneParamHolder()
         {
            First = new ThreadLocal<PFirst>();
         }

         public static ThreadLocal<PFirst> First
         {
            get;
            set;
         }
      }

      private class TwoParamHolder<PFirst, PSecond>: OneParamHolder<PFirst>
      {
         static TwoParamHolder()
         {
            Second = new ThreadLocal<PSecond>();
         }

         public static ThreadLocal<PSecond> Second
         {
            get;
            set;
         }
      }

      public void Register<PFirst, TService>(Func<PFirst, TService> instanceCreator) where TService: class
      {
         base.Register<TService>(() =>
         {
            try
            {
               return instanceCreator(OneParamHolder<PFirst>.First.Value);
            }
            finally
            {
               OneParamHolder<PFirst>.First.Value = default(PFirst);
            }
         });
      }

      public void Register<PFirst, PSecond, TService>(Func<PFirst, PSecond, TService> instanceCreator) where TService: class
      {
         Register<PFirst, TService>((p) =>
         {
            try
            {
               return instanceCreator(p, TwoParamHolder<PFirst, PSecond>.Second.Value);
            }
            finally
            {
               TwoParamHolder<PFirst, PSecond>.Second.Value = default(PSecond);
            }
         });
      }

      public TService GetInstance<PFirst, PSecond, TService>(PFirst first, PSecond second) where TService: class
      {
         TwoParamHolder<PFirst, PSecond>.First.Value = first;
         TwoParamHolder<PFirst, PSecond>.Second.Value = second;
         return base.GetInstance<TService>();
      }
   }

2. Then having example class:

public class Injected
   {
      public Injected(int age, string name)
      {
         Age = age;
         Name = name;
      }

      public string Name
      {
         get;
         private set;
      }
      public int Age
      {
         get;
         private set;
      }

      public override string ToString()
      {
         return string.Format("{0} [{1}]", Name, Age);
      }
   }

and factory:

public class Factory
   {
      private readonly ExtendedContainer container;

      public Factory(ExtendedContainer container)
      {
         this.container = container;
         container.Register<int, string, Injected>((i, s) => new Injected(i, s));
      }

      public Injected Create(int age, string name)
      {
         return container.GetInstance<int, string, Injected>(age, name);
      }
   }

3. You could use it this way:

class Program
   {
      static void Main(string[] args)
      {
         var container = new ExtendedContainer();
         var factory = new Factory(container);
         Console.WriteLine(factory.Create(1, "first"));
         Console.WriteLine(factory.Create(2, "second"));
         Console.ReadLine();
      }
   }

I'm new to Simple Injector and I'm not sure about possible side effects of this solution, but it seems to work.

Coordinator
Jan 10, 2013 at 3:31 PM
Edited Jan 10, 2013 at 4:56 PM

As far as I see, this would work correctly (except for the container.Register call inside the factory).

However, it is much easier to register a delegate in the container directly as follows:

container.RegisterSingle<Func<int, string, Injected>(
    (i, s) => new Injected(i, s));

This way you can directly inject the Func<int, string, Injected> delegate into the Factory class and use it as follows:

public class Factory
{
    private readonly Func<int, string, Injected> factory;

    public Factory(Func<int, string, Injected> factory)
    {
        this.factory = factory;
    }

    public Injected Create(int age, string name)
    {
        return this.factory(age, name);
    }
}

This prevents you from having to extend the container, making the factory dependent on the container and introducing possibly any subtle bugs.

Coordinator
Jul 29, 2013 at 12:37 PM
There's an interesting solution in this Stackoverflow thread that makes use of ThreadLocal<T> and the ResolveUnregisteredType event to allow passing runtime values into the constructor.