Conditional dependency injection binding only when

2019-01-27 02:37发布

问题:

It is a desktop application which is obliged to impersonate the current user when accessing the underlying data source.

How can I tell Ninject not to bind the dependency until the property of a parent object is not null?

  1. Application forces user authentication upon startup
  2. Once authenticated, a reference to the current user credentials is kept within the IMembershipService
  3. Accessing the underlying datasource obliges to have a user authenticated so that the commection string states the credentials to impersonate

I am actually using NHibernate, and I need to dynamically create the connection string based on the user credentials provided upon authentication.

So I would like to achieve:

public class DataModule : NinjectModule {
    public override void Load() {
        Bind<ISessionFactory>().ToProvider<SessionFactoryProvider>()
            .When( // IMembershipService.CurrentUser != null );
    } 
}

Since the SessionProviderFactory depends on the IMembershipService.CurrentUser.

I am aware this might not be the design of the century, beside I need it to work this way for now so that I can deliver my piece of potentially shippable product. I'll be glad to refactor afterwards.

SessionFactoryProvider

public class SessionFactoryProvider : Provider<ISessionFactory> {
    public class SessionFactoryProvider(IMembershipService service) {
        membershipService = service;
    }

    protected override ISessionFactory CreateInstance(IContext context) {
        Configuration nhconfig = new Configuration().Configure();
        nhconfig.AddAssembly(Assembly.GetExecutingAssembly());
        nhconfig.Properties["connection.connection_string"] = buildConnectiongString();
        return nhconfig.BuildSessionFactory();
    }

    private string buildConnectionString() {
        return string.Format(membershipService.GetDefaultConnectionString()
            , membershipService.CurrentUser.DatabaseInstanceName
            , membershipService.CurrentUser.Login
            , membershipService.CurrentUser.Password);
    }

    private readonly IMembershipService membershipService;
}

SessionProvider

public class SessionProvider : Provider<ISession> {
    public class SessionProvider(ISessionFactory factory) {
        sessionFactory = factory;
    }

    protected override ISession CreateInstance(IContext context) {
        return sessionFactory.OpenSession();
    }

    private readonly ISessionFactory sessionFactory;
}

The fact is that I can't instantiate the IMembershipService.CurrentUser upon application startup. The user has first to authenticate to the system, hence the ActivationException.

Error activating ISession

No matching bindings are available, and the type is not self-bindable.

Activation path:

3) Injection of dependency ISession into parameter session of constructor of type InquiriesRepository

2) Injection of dependency IInquiriesRepository into parameter repository of constructor of type InquiriesManagementPresenter

1) Request for InquiriesManagementPresenter

Suggestions:

1) Ensure that you have defined a binding for ISession.

2) If the binding was defined in a module, ensure that the module has been loaded into the kernel.

3) Ensure you have not accidentally created more than one kernel.

4) If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.

5) If you are using automatic module loading, ensure the search path and filters are correct.

So, how can I make it?

Any design improvement suggestions welcome. But keep in mind that I need it to work badly. I'll refactor for the next delivery.

EDIT

It looks like I perhaps could use contexts/scope dependency injection.

How to use the additional Ninject Scopes of NamedScope

And still, I cannot figure out how to make it work here in my scenario.

UPDATE

Through my readings, I begin to doubt that perhaps contextual binding would be more suitable for my needs than conditional binding.

Any thoughts?

Each article which relates on conditional binding is talking about conditional type binding, not conditional state binding.

In my scenario, it specifically believe it is a conditional state binding, hence the thoughts about context binding.

Then, I would have two bindings.

  1. Before the user is authenticated
  2. After the user is authenticated

So perhaps I either have to use lazy injection, which would resolve my problem, as an instance of the ISessionFactory isn't required on application startup.

I'm a bit loss here...

回答1:

You might be able to create a conditional binding:

IBindingRoot.Bind<IFoo>().To<Foo>()
    .When(request => request.ParentContext.Kernel.Get<IUserService>().AuthenticatedUser != null);

But i don't recommend it ;-)

@Pacane

The When condition will be executed every time one requests an IFoo. It translates to another request and is thus somewhat costly.

Also, this approach only works in case there is a parent request. In case you do a IResolutionRoot.Get<IFoo>() it will throw a NullReferenceException.

You could work around that by accessing the kernel provided by the syntax. But i don't know whether it's going to stay there in future releases of ninject...


Also, having an application bootstrapping mechanism which ensures that you only instanciate certain parts of the object tree after you are "fully initialized" (=> authenticated) is a better design. At least in my opinion.