Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use Ninject's Named bindings in Simple Injector? #389

Closed
walter-psjr opened this issue Mar 10, 2017 · 1 comment
Closed

How to use Ninject's Named bindings in Simple Injector? #389

walter-psjr opened this issue Mar 10, 2017 · 1 comment
Labels

Comments

@walter-psjr
Copy link

walter-psjr commented Mar 10, 2017

Hello,

I'm migrating an application with NInject to Simple Injector. I use bind with names for some cases as more than one implementation of the same interface.

For example, this application has two databases and uses NHibernate. For each database I have a factory that builds the instance of every NHibernate session factory. For this I use the Named method of NInject:

Bind<ISessionFactory>()
    .ToProvider<MvSessionFactoryProvider>()
    .InSingletonScope()
    .Named("MvSessionFactory");

Bind<ISessionFactory>()
    .ToProvider<ObaSessionFactoryProvider>()
    .InSingletonScope()
    .Named("ObaSessionFactory");

Then I can perform the injection of each bind in the constructor, for example in a Dao:

public TestDao([Named("MvSessionFactory")] ISessionFactory sessionFactory) ...

I would like to know if there is something in the Simple Injector that allows me to work this way or if this approach is not ideal and there would be an ideal way to work with this type of situation.

Thanks

@dotnetjunkie
Copy link
Collaborator

Let me start by pointing out the problems with your design and how to fix those. After that I'll show how to make the configuration with your current design.

You seem to be using one interface for two incompatible implementations. This is a Liskov Substitution Principle (LSP) violation. The LSP states that:

if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program

Or to translate to your case:

if MvSessionFactoryProvider is a subtype of ISessionFactory, then objects of type ISessionFactory in a program may be replaced from MvSessionFactoryProvider with objects of type ObaSessionFactoryProvider without altering any of the desirable properties of that program

If both database have their own independent schema, the above statement will not hold and this implies you are violating LSP. It will fail, because your TestDao would most likely expect a certain schema and it should expect the interface to work. But when it query the Oba database while expecting to query Mv, the TestDao would break and we altered "any of the desirable properties of that program".

Violating LSP leads to applications that are more fragile and leads to DI configurations that are more difficult. In your case you even had to put library-specific attributes (i.e. Ninject's NamedAttribute) on your implementations, while you want to prevent your DI container from leaking into your application.

To solve this problem, and to adhere to the LSP, your interface should be split. For instance:

public interface IMvSessionFactoryProvider { }
public interface IObaSessionFactoryProvider { }

Each implementation can implement its own interface. This means you can simplify TestDao to the following:

public TestDao(IMvSessionFactoryProvider  sessionFactory) ...

And registration can be done as follows:

container.Register<IMvSessionFactoryProvider, MvSessionFactoryProvider>(Lifestyle.Singleton);
container.Register<IObaSessionFactoryProvider, ObaSessionFactoryProvider>(Lifestyle.Singleton);

As you will see shortly, the above registrations are much simpler than any given alternative. This not only holds for Simple Injector, but as well for Ninject and any other DI Container.

If however you are unable to improve the design of your application, you can still wire this up in Simple Injector, but I would strongly advise to take the time to improve the application's design, since that will save you in the long run.

With the common warnings and preaches out of the way, let's take a look how to do this with Simple Injector:

container.RegisterConditional<ISessionFactory, MvSessionFactoryProvider>(
    Lifestyle.Singleton,
    c => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "MvSessionFactory");

container.RegisterConditional<ISessionFactory, ObaSessionFactoryProvider>(
    Lifestyle.Singleton,
    c => c.Consumer.Target.GetCustomAttribute<NamedAttribute>()?.Name == "ObaSessionFactory");

The previous registrations are condition, where the supplied predicate specifies the condition on which they apply. Here you see the registrations still depend on Ninject's NamedAttribute, but you can obviously use your own attribute as well. c.Consumer is the component in which the ISessionFactory is injected into and c.Consumer.Target is in this case the constructor argument the ISessionFactory is applied to.

These conditional registrations can be used in any way you want, so you can also remove the need to have attributes. This means however that there should be a different way to identify which consumer needs which dependency. Perhaps consumers live in a certain namespace:

container.RegisterConditional<ISessionFactory, MvSessionFactoryProvider>(
    Lifestyle.Singleton,
    c => c.Consumer.ImplementationType.Namespace.EndsWith(".DataAccess.Mv"));

container.RegisterConditional<ISessionFactory, ObaSessionFactoryProvider>(
    Lifestyle.Singleton,
    c => c.Consumer.ImplementationType.Namespace.EndsWith(".DataAccess.Oba"));

I hope this helps.

@dotnetjunkie dotnetjunkie changed the title Migrating from NInject - Named bind How to use Ninject's Named bindings in Simple Injector? Mar 11, 2017
Repository owner deleted a comment from hellboy81 Mar 27, 2018
Repository owner locked and limited conversation to collaborators Mar 27, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants