0

I have the the following code in my Ninject modules repeated several times over. What methods and techniques can I use to reduce this repeated code?

public override void Load()
{
    Bind<IDataReader<IList<Price>>>()
        .To<PricesDataReader>().Named("ValDatePrices");
    Bind<IDataConnection<IList<PricesCsvRecord>>>()
        .To<PricesXLConnection>().WhenParentNamed("ValDatePrices")
        .Named("ValDatePricesXLConnection");
    Bind<IDirectoryBuilder>()
        .ToMethod(DefaultValDatePricesDirectory)
        .WhenParentNamed("ValDatePricesXLConnection");

    Bind<IDataReader<IList<Price>>>()
        .To<PricesDataReader>().Named("EDDatePrices");
    Bind<IDataConnection<IList<PricesCsvRecord>>>()
        .To<PricesXLConnection>().WhenParentNamed("EDDatePrices")
        .Named("EDDatePricesXLConnection");
    Bind<IDirectoryBuilder>()
        .ToMethod(DefaultEDDatePricesDirectory)
        .WhenParentNamed("EDDatePricesXLConnection");
}

The main differences occur when requesting an IDirectoryBuilder whose main function is to determine the location of a file based on configuration settings through the use of an IDirectory implementation.

In the example above, I return a DefaultDirectoryBuilder, however I have several other implementations of these (see the EdNrrDirectoryBuilder method) below.

public IDirectoryBuilder DefaultValDatePricesDirectory(IContext arg) 
{
    return new DefaultDirectoryBuilder(
         ConfigurationManager.AppSettings["VALDATE_PRICES_DIR"],
         ConfigurationManager.AppSettings["VALDATE_PRICES_FILENAME"]);
}

public IDirectoryBuilder DefaultEDDatePricesDirectory(IContext arg) 
{
    return new DefaultDirectoryBuilder(
         ConfigurationManager.AppSettings["EDDATE_PRICES_DIR"],
         ConfigurationManager.AppSettings["EDDATE_PRICES_FILENAME"]);
}

public IDirectoryBuilder EdNrrDirectoryBuilder(IContext arg)
{
    return new ExternalDirectoryBuilder(
         ValuationDate,
         ConfigurationManager.AppSettings["NRRDATE_DIR"],
         ConfigurationManager.AppSettings["NRRDATE_PRICES_FILENAME"]);
}

My issue is that I need values from my configuration files. Right now, all configuration related requests are limited in my Ninject modules.

If I use a Ninject Factory approach to create IDirectoryBuilders, the way I see it is that I will need to have ConfigurationManager related calls scattered throughout my codebase.

If I use a Ninject Provider approach, I will need providers for all implementations of IDirectoryBuilders, and also updated my constructors and implementations of IDataConnection . My code now also looks like (not very DRY and similar to my current approach).

Bind<IDirectoryBuilder>().ToProvider<DefaultDirectoryBuilderProvider>()
    .WhenParentNamed("EDDatePricesXLConnection")
    .WithConstructorArgument("baseDir", "someConfigValue")
    .WithConstructorArgument("fileName", "someOtherConfigValue");

My code has a very consistent dependency chain at the moment (using NamedArguments): ICalculator->IDataReader->IDataConnection->IDirectoryBuilder - this leads me to believe that there must be some way to create this chain repeatedly without having to repeat the setup code - which I can not seem to figure out. There is the added limitation, in that I often require two instances of the same dependency chain - the only difference being different configuration values.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
Ahmad
  • 22,657
  • 9
  • 52
  • 84

2 Answers2

0

There's no reason to rely on any Ninject specific techniques (though in some cases, a Provider may be appropriate (example of a provider here).

The simple answer is that you create extension methods for what the preceding component of the binding expression returns (similar question).


On re-reading your question, I'd suggest looking at Ninject.Extensions.Conventions which hass lots of Bind extension methods that will allow you to do bullk Bindings in the manner you suggest. If it doesn't I'd recommend commenting and identifying the bits you feel it doesn't address.

Community
  • 1
  • 1
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • Hi Ruben - can you please provide a quick example or some starting guidance on using Extensions in my case. I have read the Wiki but nothing really strikes me as a workable solution. – Ahmad May 02 '12 at 07:41
  • @Ahmad Not talking about anything earth shattering. Step 1 is just a method that wraps each block of 3 Binds which belong together. Then after that, if there are duplicate When/With sequences remaining, make an extension method that does the two together. Your use of `ValuationDate` in `EdNrrDirectoryBuilder` suggests things arent completely uniform though. On reflection, it's hard to say whether your configuration sequences are regular enough to make usage of Conventions appropriate. Will delete this answer soon as on reflection your question is too broad for a clean answer. – Ruben Bartelink May 02 '12 at 11:29
  • I have created a solution based on your comment - http://stackoverflow.com/a/10429406/268667 – Ahmad May 03 '12 at 10:21
0

Based on Ruben's comment, this is my current solution. The one thin I did was use the concept of conventions, thus retrieving my Config settings became easier. This filtered through most of the other code that used Ninject Named parameters.

public void Load(){
    BindDependencies<IDataReader<IList<Price>>, PricesDataReader
      , IDataConnection<IList<PricesCsvRecord>>, PricesXLConnection
      , DefaultDirectoryBuilder>
      ("ValDatePrices");

    BindDependencies<IDataReader<IList<Price>>, PricesDataReader
      , IDataConnection<IList<PricesCsvRecord>>, PricesXLConnection
      , DefaultDirectoryBuilder>
      ("EDDatePrices");
       // etc etc 
}

 public void BindDependencies<
     TReaderBase, TReaderImpl,
     TDataConnectionBase, TDataConnectionImpl,
     TDirectoryBuilderFactoryImpl>
     (string baseName)
         where TReaderImpl : TReaderBase
         where TDataConnectionImpl : TDataConnectionBase
 {
     DirectoryBuilderInfo dirInfor = GetSettings(baseName);

     Bind<TReaderBase>()
           .To(typeof(TReaderImpl))
           .Named(baseName);
     Bind<TDataConnectionBase>().To(typeof(TDataConnectionImpl))
            .WhenParentNamed(baseName)
            .Named(baseName + "XLConnection");
     Func<IDirectoryBuilder> directoryBuilder = CreateDirectoryBuilderFunc<TDirectoryBuilderFactoryImpl>(dirInfor);

     Bind<IDirectoryBuilder>()
            .ToMethod(d => directoryBuilder())
            .WhenParentNamed(baseName + "XLConnection");
    }

private Func<IDirectoryBuilder> CreateDirectoryBuilderFunc<TDirectoryBuilderFactoryImpl>(DirectoryBuilderInfo dirInfor)
{
    Func<IDirectoryBuilder> directoryBuilder = 
         () => CreateDefaultDirectoryBuilder(dirInfor.BaseDirectory, dirInfor.FileName);

    if (typeof(TDirectoryBuilderFactoryImpl) == typeof(RiskDirectoryBuilderFactory))
    {
        directoryBuilder = 
         () => CreateRiskDirectoryBuilder(ValuationDate, dirInfor.BaseDirectory, dirInfor.FileName);
    }
    return directoryBuilder;
}

private DirectoryBuilderInfo GetSettings(string baseName)
{
    var settingsName = baseName.ToUpperInvariant();
    return new DirectoryBuilderInfo()
    {
        BaseDirectory = ConfigurationManager.AppSettings[settingsName + "_DIR"],
        FileName = ConfigurationManager.AppSettings[settingsName + "_FILENAME"]
     };
 }
Ahmad
  • 22,657
  • 9
  • 52
  • 84
  • Looks good. One thing: Dependency Inversion Principle:- you should pass in a `Func` into `BindDependencies` instead of having to have a helper it calls with a Special Case. Then do an overload that passes the default one and use that in most cases. And get manning.com/seemann if you havent already – Ruben Bartelink May 03 '12 at 10:31