【问题标题】:Can't get value of Ninject ConstructorArgument (passed in as parameter to kernel.Get)无法获取 Ninject ConstructorArgument 的值(作为参数传递给 kernel.Get)
【发布时间】:2014-03-31 15:50:01
【问题描述】:

无法获取传递给 kernel.Get() 的 ConstructorArgument 参数的值。我想使用参数的值来确定两个字符串值中的哪一个将传递给构造函数。该参数实际上在预期的时候就在那里,我只是无法获得它的值。在调用参数的 GetValue 方法后,我最终得到了一个 null ref 异常:

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

【问题讨论】:

  • 在您编辑后,我认为您的代码存在逻辑错误。在您当前的实现中,return UseEF_DB_b_conn1() || UseEF_DB_b_conn2(); 始终返回 true。所以我不确定你什么时候应该使用DBProviderB的绑定。
  • 做了另一个编辑,希望能澄清更多。 ProviderB 是现在唯一的一个。我的情况是我们有两个(或更多)具有相同架构的数据库(或足够接近以具有通用接口)。在 ProviderB 的情况下,它只是一组实体框架类,唯一会改变的是 connectionString。我只是希望能够检查运行时值 (idNum) 以确定将使用哪个数据库。

标签: parameters dependency-injection inversion-of-control ninject constructor-injection


【解决方案1】:

我需要使用ToMethod() 来访问适当的上下文。

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

【讨论】:

    【解决方案2】:

    编辑:在您的 cmets 和编辑之后,我重新制定了答案。下面的建议只是解决此问题的一种方法。我正在实现一个Provider&lt;ObjectContextFactory&gt;,它将在使用kernel.Get&lt;ObjectContextFactory&gt;() 时检查发送到请求的参数。此参数与构造函数无关,它只是用作“上下文变量”,因此您可以决定如何处理它。

    代码如下:

    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;
        }
    }
    

    还有绑定:

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

    检索工厂时:

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

    实现此目的的另一种方法是通过Named Bindings。只需将您的idNum 转换为字符串(或直接使用它)并在获取实例时调用它。

    【讨论】:

    • 嗯,不确定这是否能如我所愿。在我的实际代码中,A 具有我想要连接的自己的依赖项,而且在任何一种情况下调用的构造函数都是完全相同的,并且 y/z 都是字符串。并不是进入构造函数的类型不同,而是完全相同的构造函数参数的值不同(DBContext 的连接字符串)。我将更改我的示例以更好地匹配我的代码中正在发生的事情,对此感到抱歉。
    • 您可以随时将return new A(..) 替换为kernel.Get&lt;A&gt;(new ConstructorArgument("Z","your string")),具体取决于您的情况。如果A 是可自绑定的,您将得到所需的。我想他们A 还需要在构造函数中注入其他依赖项?
    • 感谢您的帮助。我也添加了我自己的答案,以防有人发现它也有用。
    猜你喜欢
    • 1970-01-01
    • 2015-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    • 1970-01-01
    • 2012-12-12
    相关资源
    最近更新 更多