Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Simple Injector have a way to RegisterConditional with object factory?

I see Simple Injector's Container has this method

public void RegisterConditional<TService, TImplementation>(
    Predicate<PredicateContext> predicate
)

But I want to use the different object of the same implementation for different service, so what overloaded method I need would look like this

public void RegisterConditional<TService>(
    Func<TService> instanceCreator,
    Predicate<PredicateContext> predicate
)

But the SimpleInjector doesn't have it. I am trying to find the other Container's methods to register instance creator with condition for the service. Is there the other ways else I can do?

Or, is what I am trying to do not the good design, so the developers don't implement it?

Edited: Added example and the more detailed question.

Example

class CSVFileScanner
{
    public CSVFileScanner(IFileLocator fileLocator) { }
}

class XMLFileScanner
{
    public XMLFileScanner(IFileLocator fileLocator) { }
}

class DefaultLogFileLocator: ILogFileLocator
{
    public DefaultLogFileLocator(string directoryPath, string searchPattern) { }
}

var locatorForCSVFileScanner = new DefaultLogFileLocator("C:\CSVLogDir", "*.csv")
var locatorForXMLFileScanner = new DefaultLogFileLocator("C:\XMLLogDir", "*.xml")

From the example source code, how can I register them to get locatorForCSVFileScanner object passed to the CSVFileScanner constructor when CSVFileScanner getting created and locatorForXMLFileScanner object passed to the XMLFileScanner constructor when XMLFileScanner getting created?

like image 760
wrongite Avatar asked Dec 04 '15 11:12

wrongite


1 Answers

Or, is what I am trying to do not the good design, so the developers don't implement it?

After seeing your example I have to conclude that there might be a design flaw. The main issue with the design is that you seem to violate the Liskov Substitution Principle (LSP). LSP is one of the SOLID principles and states that sub classes (or implementations of interfaces) should be interchangeable for one another, without affecting the consumer. In your application however, the XMLFileScanner seems to break when it is supplied with a CSV file.

So from a perspective of the LSP, this means that both file scanner implementations deserve their own abstraction. Once you give both their own abstraction, the problem will go away completely.

If however swapping the file locators has no effect on the working of the file scanners (for instance because they don't read, but just write), LSP is not violated and the design is okay.

If altering the abstractions is not feasible or LSP is not violated, an option is to register the file scanners using a factory delegate or by simply creating it once as singleton. This gives you full control over the composition of that part of the object graph. For instance:

container.RegisterSingleton<CSVFileScanner>(
    new CSVFileScanner(new DefaultLogFileLocator("C:\CSVLogDir", "*.csv")));

container.RegisterSingleton<XMLFileScanner>(
    new XMLFileScanner(new DefaultLogFileLocator("C:\XMLLogDir", "*.xml")));

But the SimpleInjector doesn't have it. I am trying to find the other Container's methods to register instance creator with condition for the service. Is there the other ways else I can do?

You can actually use the RegisterConditional methods to achieve this, but this feature is a bit hidden, and this is deliberate. Simple Injector tries to promote the construction of object graphs that are completely known in the startup phase and discourages building object graphs based on runtime conditions. The use of a Func<TService> instanceCreator delegate allows making runtime conditions and that's why such overload is missing.

The way to do this however is as follows:

var csv = Lifestyle.Singleton.CreateRegistration<IFileLocator>(
    () => new DefaultLogFileLocator("C:\\CSVLogDir", "*.csv"), container);

var xml = Lifestyle.Singleton.CreateRegistration<IFileLocator>(
    () => new DefaultLogFileLocator("C:\\XMLLogDir", "*.csv"), container);

container.RegisterConditional(typeof(IFileLocator), csv, WhenInjectedInto<CSVFileScanner>);
container.RegisterConditional(typeof(IFileLocator), xml, WhenInjectedInto<XMLFileScanner>);

// Helper method.
static bool WhenInjectedInto<T>(PredicateContext c) =>
    c.Consumer.ImplementationType == typeof(T);
like image 51
Steven Avatar answered Sep 22 '22 18:09

Steven