使用IoC容器时如何将参数传递给构造函数?

Tan*_*uki 6 .net ninject ioc-container unity-container

Arrrgh!我把头发拉过来.我一直试图使用IoC容器一点点,所有看起来都很好和花花公子,直到你遇到一些你认为非常基本的问题,比如将参数传递给构造函数.

假设我的某个类有一个可以由IoC解决的引用类和可以在运行时解析的值类型(或其他类型):

public NFLFeedUnitOfWork(NFLFileType fileType, object feed, IConverterMappings<NFLFileType> nflConverterMappings, IDbContext context)
    : base(fileType, feed, nflConverterMappings, context, ContextType.NFL)
{
    //new NFLContext(connstringname, setAutoDetectChanges)
}
Run Code Online (Sandbox Code Playgroud)

在这个特定的例子中,我传入Enum(NFLFileType),对象实例,2个接口参数,并将一个额外的硬编码属性传入基础构造函数(ContextType.NFL)

如何以所有神的名义在任何IoC容器中执行此操作?

问题实际上是双重的:

1.)如何传入仅在运行时已知的对象?比如说,目前调用代码看起来像这样:

protected override IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed, string connectionString)
{
    return new NFLFeedUnitOfWork(fileType, feed, new NFLConverterMappings(), new NFLContext(connectionString));
}
Run Code Online (Sandbox Code Playgroud)

如何将此代码转换为使用IoC?也许是这样的?

protected override IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed, string connectionString)
{
    return IFLFeedUnitOfWork(fileType, feed);
}
Run Code Online (Sandbox Code Playgroud)

最后2个参数自动解决的地方,我自己提供的第1个2个参数?

2.)如何使用IoC将Enum,对象,值类型传递给构造函数?(或者在这个特定的例子中可能不使用它?)

无论如何,非常感谢任何帮助,特别是在第一点.我目前正在使用Unity,但任何其他IoC容器也都可以.

我也不想将IoC容器传入代码,我只想在顶层的一个地方指定它.

Ste*_*ven 7

我有一个类,其中有一些引用类可以由IoC和值类型(或其他类型)解决,只能在运行时解析

这是你出错的地方.在编写组件时,不应将编译时依赖项与运行时数据混合在一起.您的对象图应该是静态的(最好是无状态的),并且在构造完整的对象图之后,应使用方法调用将运行时数据传递到对象图中.这可以极大地简化应用程序的开发,因为它允许对象图形进行静态验证(使用工具,单元测试或使用Pure DI),它可以防止您遇到麻烦.

通常,您有两种方法可以解决此问题:

  1. 您通过方法调用将数据传递给较低级别​​的组件.
  2. 您可以通过调用注入组件上的方法来检索数据.

采取哪种解决方案取决于具体情况.

如果数据特定于处理的请求并且将成为您正在处理的用例的一部分,您通常会选择选项1.例如:

public interface IFeedUnitOfWorkProvider
{
    IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed);
}
Run Code Online (Sandbox Code Playgroud)

这里IFeedUnitOfWorkProvider包含一个GetUnitOfWork需要运行时参数作为输入的方法.实现可能如下所示:

public class FeedUnitOfWorkProvider : IFeedUnitOfWorkProvider
{
    private readonly IConverterMappings converterMappings;
    private readonly IContext context;

    public FeedUnitOfWorkProvider(IConverterMappings converterMappings,
        IContext context) {
        this.converterMappings = converterMappings;
        this.context = context;
    }

    public IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed) {
        return new NFLFeedUnitOfWork(fileType, feed, this.converterMappings,
            this.context);
    }       
}
Run Code Online (Sandbox Code Playgroud)

这里有几点需要注意:

  • 所有静态已知的依赖项都是通过构造函数注入的,而运行时值是通过方法调用注入的.
  • connectionString值未知FeedUnitOfWorkProvider.它不是直接依赖,提供者不必知道它的存在.
  • 假设这connectionString是一个在运行时没有改变的配置值(通常存储在应用程序的配置文件中),它可以像注入NFLContext其他依赖项一样注入.请注意,配置值与运行时值不同,因为它在应用程序的生命周期内不会更改.

当您处理上下文信息时,第二个选项特别有用.这是对实现很重要的信息,但不应像前面的示例那样传递.一个很好的例子就是代表请求运行的用户信息.对此的典型抽象看起来像这样:

public interface IUserContext {
    string CurrentUserName { get; }
}
Run Code Online (Sandbox Code Playgroud)

该接口可以注入任何消费者.使用此抽象,使用者可以在运行时查询用户名.使用其余的请求数据传递用户名通常会很麻烦,因为这会允许调用者更改(或忘记)用户名,使代码更难以使用,更难测试,更容易出错.相反,我们可以使用IUserContext:

public IFeedUnitOfWork GetUnitOfWork(NFLFileType fileType, object feed) {
    if (this.userContext.CurrentUserName == "steven") {
        return new AdminUnitOfWork(this.context);
    }
    return new NFLFeedUnitOfWork(fileType, feed, this.converterMappings,
        this.context);
}
Run Code Online (Sandbox Code Playgroud)

IUserContext实现应该如何看起来将在很大程度上取决于您正在构建的应用程序的类型.对于ASP.NET,我想象这样的事情:

public class AspNetUserContext : IUserContext {
    string CurrentUserName {
        get { return HttpContext.Current.User.Name; }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1.这显示了OP的痛点并提供了很好的建议.Unity还能够提供ResolverOverrides,允许您在解析时覆盖现有注册(如[使用覆盖解析对象](https://msdn.microsoft.com/en-us/library/ff660920(v = pandp) 0.20)的.aspx)).我不愿提及这一点,因为我认为上面的建议应该是第一种方法. (2认同)