"Is it safe to use the same IoC container for your own and third-party
services?"
No, you never expose the IoC container to the public. It doesn't matter if it is your library or an external library. The container is only accessible at the composition root. It never leaves its library/application.
Instead of passing around the container (inside the application), you usually inject factories (see Abstract Factory pattern).
But in case of allowing external libraries to extend the application, you must follow the IoC principle (the "I" in SOLID).
There is absolutely no reason to share the IoC container.
If you have designed your application to follow the IoC principle, then the application (you), the owner of the IoC container, is responsible to pass dependencies to the 3rd party libray code as method arguments.
The problem is solved by implementing the IoC principle: you let the library implement a certain interface or abstract class or extend existing classes. Then you call the external implementation from your code and pass in required arguments into the invoked method implementation. These interfaces and classes are the extension points of the application.
This is how IoC works. This how frameworks in general work. IoC allows to extend the existing framework/application code by calling new implementations of the client.
In other words: in an IoC scenario, you inject the 3rd party code into the application/framework and not the application (or service container or dependencies in general) into the 3rd party client library.
The application/framework calls client code - the client code never calls application/framework code.
When talking about the framework, in this context we mean the internals that make the framework. By definition, a framework is a library that adheres to the IoC principle and calls client code to extend its functionality. For the client of a framework, the framework is a black box, that has input and output.
The 3rd party client must register his library with your application (plug-in).
The recommended framework to do this dynamically i.e. at runtime is MEF. MEF allows to load external DLLs and inject them into the running application. Common Dependency Injection implementations like IServiceCollection
only allow static application composition, where all dependencies must be knwon when instantiating and configuring the container.
Example
The following simple example allows a client library to extend the application by controlling the output content.
The new functionality is realized by forcing the 3rd party client library to implement corresponding interfaces.
The application's extension point
The implentation of this interface is called by the application. This way the client's code is invoked.
// The interface that the library must implement in order to extend the application with new functionality.
public interface IMessageGenerator
{
// ISpellChecker is a dependency that is provided by the calling application.
string Generate(ISpellChecker spellChecker);
}
The extending 3rd party client code
Code that extends the existing application by implementing the interface IMessageGenerator
that is provided/required by the application. The implementation provides the new functionality that is used/executed by the application (IoC).
class ExtendingClientCode : IMessageGenerator
{
// The ISpellChecker dependency is not injected,
// but provided as a parameter by the calling application.
public string Generate(ISpellChecker spellChecker)
{
string message = CreateMessage();
string fixedMessage = spellChecker.CheckAndFix(message);
return fixedMessage;
}
}
The extended application code
The extended application code that calls 3rd party client code to extend the application's functionality.
The 3rd party client library must be registered with application's IoC container.
This way the 3rd party library code is injected into the application.
class ClientCodeCaller
{
private IMessageGenerator MessageGenerator { get; }
private ISpellChekcer SpellChekcer { get; }
// Instances are injected by the IoC container.
// The implementation is provided by an extending library code.
// The extending library code was registered (e.g. plug-in) and resolved by the IoC container, that injects the registered code into the application's constructor.
public ClientCodeCaller(IMessageGenerator messageGenerator, ISpellChekcer spellChecker)
{
this.MessageGenerator = messageGenerator;
this.SpellChekcer = spellChecker;
}
private void SendMessageToConsole()
{
// Call extending 3rd party client code in an IoC manner.
// Instead of injecting the dependency via Dependency Injection,
// we pass it in as an argument of the extending method implementation.
string externalMessage = this.MessageGenerator.Generate(this.SpellChecker);
Console.WriteLine(externalMessage);
}
}