Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject Constructor argument check parameter existence

I was wondering if there is anyway, how to do a parameter existence check with Ninject?

What I am referring to: let's have a theoretical class and an interface:

public interface IFileExistenceCheck
{
    bool FileExist();
}

public class FileExistenceChecker : IFileExistenceCheck
{
    private readonly string filePath;
    private readonly IFileSystem fileSystem;

    public FileExistenceChecker(IFileSystem fileSystem, string filePath)
    {
        this.fileSystem = fileSystem;
        this.filePath = filePath;
    }

    public bool FileExist()
    {
        return this.fileSystem.File.Exists(this.filePath);
    }
}

Then somewhere in the code, I will be getting an instance of IFIleExistenceCheck interface via kernel like so:

public class Foo()
{
    public void Bar()
    {
        // do something
        var filePath = SomeMagicString;

        var filePathArgument = new ConstructorArgument("filePath", filePath); // <- This part I do not really like
        var checker = Kernel.Get<IFileExistenceCheck>(filePathArgument);
        var fileExist = checker.FileExist();

        // do the rest of code
    }
}

This will work just fine, problem is, that it will only work as long as the name of file path argument stays the same. Let's say one day somebody will decide, that filePath is unnecessary and renames it just to path. The code itself will still compile, but it will not cause any error until somebody will actually do the call to the Bar() method.

Is there any way how to prevent this from happening?

I do not really want to expose the filePath. I still want it to be passed as an argument of a constructor. I do not want to change signature of FileCheck() to accept filePath as an argument and I do not even want to change filePath to publicly accessible field.

like image 594
Kajiyama Avatar asked Oct 22 '25 04:10

Kajiyama


2 Answers

This is abusing the DI container as a service locator, and yet another reason why it service locator is considered to be anti-pattern.

Your contrived example would not happen if you were using the dependency injection pattern. Refactoring your example to use dependency injection, it would look like this:

public interface IFileExistanceCheck
{
    bool FileExist(string filePath);
}

public class FileExistanceChecker : IFileExistanceCheck
{
    private readonly IFileSystem fileSystem;

    public FileExistanceChecker(IFileSystem fileSystem)
    {
        if (fileSystem == null)
            throw new ArgumentNullException(nameof(fileSystem));
        this.fileSystem = fileSystem;
    }

    // Pass runtime data through the method parameters!
    public bool FileExist(string filePath)
    {
        // Prevent an empty file path from being used
        if (string.IsNullOrEmpty(filePath))
            throw new ArgumentNullException(nameof(filePath));

        return this.fileSystem.File.Exists(filePath);
    }
}

Foo Class

public class Foo
{
    private readonly IFileExistanceCheck fileExistanceCheck;

    public Foo(IFileExistanceCheck fileExistanceCheck)
    {
        if (fileExistanceCheck == null)
            throw new ArgumentNullException(nameof(fileExistanceCheck));
        this.fileExistanceCheck = fileExistanceCheck;
    }
    public void Bar()
    {
        // do something
        var filePath = SomeMagicString;

        var fileExist = fileExistanceCheck.FileExist(filePath);

        // do the rest of code
    }
}

At the composition root, Ninject would tie it all together and get Foo running.

class Program
{
    static void Main(string[] args)
    {
        // Begin composition root

        var kernel = new StandardKernel();

        kernel.Bind<IFileSystem>().To<FileSystem>();
        kernel.Bind<IFileExistanceCheck>().To<FileExistanceChecker>();

        var app = kernel.Get<Foo>();

        // End composition root

        app.Bar();
    }
}

If there is a need to check for the existence of the filePath parameter, this check can be done using a guard clause. You only need to use the dependency injection pattern, and pass the runtime data (the file path) through a method parameter.

If you want the filePath to be a configuration value that is passed through the constructor at application startup, then having a service to check for file existence seems rather pointless. In that case, you should check whether the file exists before allowing your application to run.

class Program
{
    static void Main(string[] args)
    {
        var filePath = "SomeFileThatShouldExist.txt";

        // Check configuration
        if (!File.Exists(filePath))
            throw new InvalidOperationException("Invalid configuration");

        // Begin composition root

        var kernel = new StandardKernel();

        kernel.Bind<Foo>().To(new Bar(filePath));
        // Register other services...

        var app = kernel.Get<Foo>();

        // End composition root

        app.Bar();
    }
}
like image 135
NightOwl888 Avatar answered Oct 23 '25 20:10

NightOwl888


If your parameter is known only in runtime you can inject factory and create your FileExistenceChecker with it:

public interface IFileExistenceCheckerFactory
{
    IFileExistenceCheck Create(string path);
}

...

var kernel = new StandardKernel();
kernel.Bind<IFileExistenceCheck>().To<FileExistenceChecker>();
kernel.Bind<IFileSystem>().To<FileSystem>();
kernel.Bind<IFileExistenceCheckerFactory>().ToFactory(() => new TypeMatchingArgumentInheritanceInstanceProvider());

var factory = kernel.Get<IFileExistenceCheckerFactory>();
var checker = factory.Create("SomeMagicString");
var fileExist = checker.FileExist();

Then even if your parameter names don't match, TypeMatchingArgumentInheritanceInstanceProvider ensure that parameters will be matched by its type.

like image 34
Jan Muncinsky Avatar answered Oct 23 '25 19:10

Jan Muncinsky



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!