Can't get value of Ninject ConstructorArgument (passed in as parameter to kernel.Get)
A

2

0

Having trouble getting the value of a ConstructorArgument parameter passed to kernel.Get(). I want to use the parameter's value to determine which of two string values will be passed into the constructor. The parameter is in fact there when expected, I just can't get at its value. I end up with a null ref exception after calling the parameter's GetValue method:

namespace MyNS.DBProviders {
    public abstract class DBProviderBase {
        private ObjectContext _db;

        public DBProviderBase(ObjectContext db) {
            _db = db;
        }

        ...
        //abstract methods here
        ...
    }

    public class DBProviderB : DBProviderBase {
        public DBProviderB(ObjectContext db) : base(db) {}
        //implementation details... applies to two DBs w/ same schema
    }

    public class DBProviderModule : NinjectModule {
        public override void Load() {
            //Bind DB providers
            Bind<DBProviderBase>().To<DBProviderB>();

            //Bind LINQ contexts to DB providers
            Bind<ObjectContext>().To<EF_DB_b>()
                .When(UseEF_DB_b_conn1).WithConstructorArgument(
                    "connectionString"
                    , ConfigurationManager.ConnectionStrings["EF_DB_b_conn1"]
                        .ToString()
                )
            ;
            Bind<ObjectContext>().To<EF_DB_b>()
                .When(UseEF_DB_b_conn2).WithConstructorArgument(
                    "connectionString"
                    , ConfigurationManager.ConnectionStrings["EF_DB_b_conn2"]
                        .ToString()
                )
            ;
        }

        public static bool UseEF_DB_b_conn1(IRequest req) {
            string idNum = (string)req.Parameters.First(
                p => p.Name == "idNum"
            ).GetValue(null, null);
            //NULL REF EXCEPTION

            return x != null ?  idNum.Substring(0, 1) == "2" : false;
        }

        public static bool UseEF_DB_b_conn2(IRequest req) {
            return !UseEF_DB_b_conn1(req);
        }
    }
}

namespace MyNS {
    public static class DBProviderFactory {

        public static DBProviderBase GetDBProvider(string idNum) {
            var kernel = new Bootstrapper().Kernel;

            return kernel.Get<DBProviderBase>(
                new ConstructorArgument("idNum", idNum)
            );
        }
    }
}

...//idNum received from elsewhere
var db = DBProviderFactory.GetDBProvider(idNum);
var something = db.GetSomethingFromOneOfThreeDBs();
Adonis answered 31/3, 2014 at 15:50 Comment(2)
After your edit, I think there is a logic error in your code. With your current implementation, return UseEF_DB_b_conn1() || UseEF_DB_b_conn2(); always returns true. So I'm not sure when you should use the binding for DBProviderB.Nesselrode
Made another edit that will hopefully clarify some more. ProviderB is the only one there now. My situation is that we have two (or more) DBs w/ the same schema (or close enough to have a common interface). In ProviderBs case, it's just one set of Entity Framework classes, and the only thing that will change is the connectionString. I just want to be able to check a run-time value (idNum) to determine which DB will be used.Adonis
N
1

Edit: After your comments and edits, I've reformulated the answer. The suggestion below is just one way to go about this. I'm implementing a Provider<ObjectContextFactory> that will inspect the parameter sent to the request when using kernel.Get<ObjectContextFactory>(). This parameter has nothing to do with constructor, it is only used as a "context variable" so you can decide what to do with it.

Here is the code:

public class ObjectContextFactory
{
    private readonly string _idNum;
    private readonly IResolutionRoot _container;

    public ObjectContextFactory(IResolutionRoot container, string idNum)
    {
        _container = container;
        _idNum = idNum;
    }

    public ObjectContext CreateObjectContext()
    {
        //Use _idNum here as you wish, here is a sample implementation
        ConstructorArgument constructorArgument;
        if (_idNum.Substring(0,1) == "2")
        {
            constructorArgument = new ConstructorArgument("connectionString", "your conn string 1");
        }
        else
        {
            constructorArgument = new ConstructorArgument("connectionString", "your conn string 2");
        }

        return _container.Get<ObjectContext>(constructorArgument);
    }
}

public class ObjectContextFactoryProvider : Provider<ObjectContextFactory>
{
    protected override ObjectContextFactory CreateInstance(IContext context)
    {
        var idNum = context.Parameters.FirstOrDefault(p => p.Name == "idNum");
        var idNumValue = idNum.GetValue(context, null) as string;

        var factory = new ObjectContextFactory(context.Kernel, idNumValue);
        return factory;
    }
}

And the bindings:

public override void Load()
{
    Bind<DBProviderBase>().To<DBProviderB>();
    Bind<ObjectContextFactory>().ToProvider<ObjectContextFactoryProvider>();
}

When retrieving the factory:

var factory = kernel.Get<ObjectContextFactory>(new Parameter("idNum", "1", true));

Another way to achieve this would be via Named Bindings. Just do a kind of translator from your idNum to a string (or use it directly) and call it when getting an instance.

Nesselrode answered 31/3, 2014 at 17:40 Comment(3)
Hmmm, not sure this would work the way I want. In my actual code, A has it's own dependencies that I want wired up, but also, it's the exact same constructor being called in either case, and y/z would both be strings. It's not that the types going into the constructor are different, it's the values of the exact same constructor parameter that are different (a connection string for a DBContext). I will change my example to better match what's going on in my code, sorry about that.Adonis
You can always replace return new A(..) by kernel.Get<A>(new ConstructorArgument("Z","your string")) depending in your case. If A is self-bindable, you'll get what you need. I suppose them that A has other dependencies that need to be injected in the constructor as well?Nesselrode
Thanks for the help. I added my own answer as well in case anyone finds that useful too.Adonis
A
2

I needed to use ToMethod() to get access to the appropriate context.

Bind<DBProviderBase>().ToMethod(
    ctx => {
        var idNum = ctx.Parameters.First(p => p.Name == "idNum")
            .GetValue(ctx, null) as string
        ;

        if (idNum == 2) {
            return new DBProviderB(new EF_DBEntities(
                ConfigurationManager.ConnectionStrings["EF_DB_b_conn1"]
            .ToString()));
        } else {
            return new DBProviderB(new EF_DBEntities(
                ConfigurationManager.ConnectionStrings["EF_DB_b_conn2"]
            .ToString()));
        }
    };
);

//later...
var db = kernel.Get<DBProviderBase>(
    new Ninject.Parameters.Parameter("idNum", idNum, true)
);
Adonis answered 2/4, 2014 at 13:8 Comment(0)
N
1

Edit: After your comments and edits, I've reformulated the answer. The suggestion below is just one way to go about this. I'm implementing a Provider<ObjectContextFactory> that will inspect the parameter sent to the request when using kernel.Get<ObjectContextFactory>(). This parameter has nothing to do with constructor, it is only used as a "context variable" so you can decide what to do with it.

Here is the code:

public class ObjectContextFactory
{
    private readonly string _idNum;
    private readonly IResolutionRoot _container;

    public ObjectContextFactory(IResolutionRoot container, string idNum)
    {
        _container = container;
        _idNum = idNum;
    }

    public ObjectContext CreateObjectContext()
    {
        //Use _idNum here as you wish, here is a sample implementation
        ConstructorArgument constructorArgument;
        if (_idNum.Substring(0,1) == "2")
        {
            constructorArgument = new ConstructorArgument("connectionString", "your conn string 1");
        }
        else
        {
            constructorArgument = new ConstructorArgument("connectionString", "your conn string 2");
        }

        return _container.Get<ObjectContext>(constructorArgument);
    }
}

public class ObjectContextFactoryProvider : Provider<ObjectContextFactory>
{
    protected override ObjectContextFactory CreateInstance(IContext context)
    {
        var idNum = context.Parameters.FirstOrDefault(p => p.Name == "idNum");
        var idNumValue = idNum.GetValue(context, null) as string;

        var factory = new ObjectContextFactory(context.Kernel, idNumValue);
        return factory;
    }
}

And the bindings:

public override void Load()
{
    Bind<DBProviderBase>().To<DBProviderB>();
    Bind<ObjectContextFactory>().ToProvider<ObjectContextFactoryProvider>();
}

When retrieving the factory:

var factory = kernel.Get<ObjectContextFactory>(new Parameter("idNum", "1", true));

Another way to achieve this would be via Named Bindings. Just do a kind of translator from your idNum to a string (or use it directly) and call it when getting an instance.

Nesselrode answered 31/3, 2014 at 17:40 Comment(3)
Hmmm, not sure this would work the way I want. In my actual code, A has it's own dependencies that I want wired up, but also, it's the exact same constructor being called in either case, and y/z would both be strings. It's not that the types going into the constructor are different, it's the values of the exact same constructor parameter that are different (a connection string for a DBContext). I will change my example to better match what's going on in my code, sorry about that.Adonis
You can always replace return new A(..) by kernel.Get<A>(new ConstructorArgument("Z","your string")) depending in your case. If A is self-bindable, you'll get what you need. I suppose them that A has other dependencies that need to be injected in the constructor as well?Nesselrode
Thanks for the help. I added my own answer as well in case anyone finds that useful too.Adonis

© 2022 - 2024 — McMap. All rights reserved.