0

I am developing an application with ASP.NET Core with the ability to dynamically add new libraries that implement the necessary functions. They can be included at the start.

I have already written several standard libraries myself. I wanted to use the same logger in the main application and in the pluggable libraries, so I pass IServiceCollection to all these libraries and they add everything that is needed for them to the DI container. I imagine someone can write similar library and add it to the application in the future. However, I'm not sure if it is okay to give access to the container of the main application to someone else's pluggable library. The main application suppose to be a black box.

Is it safe? What is the best practice for using one DI container in multiple places? Or is it better to each library have its own?

4
  • 1
    What is the context here? Who would add such a library? Is it the owner of the application? Is it a client of the owner of the application? Is it your colleague developer? This matters. What also matters is what your concern is. Is there a security threat? Does this allow the circumvention of an intentional business rule? Is this a matter of merely avoiding an annoying conflict?
    – Flater
    Nov 10, 2021 at 10:06
  • 1
    In shorter terms: define "safe". What specifically are you concerned about?
    – Flater
    Nov 10, 2021 at 10:12
  • @Flater My case: I released an app with one additional library. Gave it to the user. The user wanted more functionality, found another library on the Internet and connected it to the main application. The main application also passes the IServiceCollection to it. Nov 10, 2021 at 11:11
  • This adds some context but doesn't answer the core of the question. Is the user allowed to reconfigure the program as they see fit? Can they abuse this? E.g. if we're talking about a video game loading mods, there's a difference between breaking the game (modder's fault, modder should fix it) and circumventing critical sections (e.g. license verification, anti-cheating measures for online play).
    – Flater
    Nov 10, 2021 at 12:00

2 Answers 2

1

All libraries should use the same container so that components can be shared and don't have to be registered twice.

It's considered bad practice to pass the service container around, even though .net does it all the time with its static UseWhatever() methods. Who knows what the library might add or remove and you wont be able to override the setup.

Register the components required for each library in the service container, if the third party libs require a ILogger or ISpecialLogger create your own implementation and register it and they should pick it up

3
  • "It's considered bad practice to pass the service container around..." Absolutely. You should aim for just one composition root, ie one single place in the whole application where mappings are registered with the container. Passing the container around for various parts of the app to add their own mappings really is a bad idea.
    – David Arno
    Nov 10, 2021 at 12:05
  • This is not how you extend an application. The container is a private component. Following the IoC principle, you inject the external library code into your application where you force the external library code to implement/extend specific interfaces/classes. The implementations are injected into the application and members of this implementation are invoked by passing in required depndencies as arguments. Never would you allow a library to configure the application. Never. There architedctures like Plug-in to provide the required extensibility while hiding internals from the extending client.
    – BionicCode
    Nov 10, 2021 at 19:09
  • @BionicCode erm, I think thast what im saying? although .net app.UseXXX() effectively does this, so its not like everyone agrees with me
    – Ewan
    Nov 11, 2021 at 16:47
1

"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);
  }
}
9
  • This makes sense when you want the caller to be aware of and able to specify the dependencies. But what about private dependencies? Say, for example, some of the classes in your library require an injected IProprietaryEncryptionService. It's private and you want to be able to change or replace it without requiring people who use your library to worry about it. How/where would you register that dependency?
    – John Wu
    Nov 11, 2021 at 19:10
  • @JohnWu You always have to register your extension in order to be injected into the application/framework. Like any framework your application would provide an entry point. For example the external library would be required to implement IPlugin. The application would then call IPlugin.Initialize, where the library could execute internal dependency injection. If the library/plug-in is running independently like in its own process/AppDomain, IPlugin would have a IPlugin.Run method that would be the entry point of the library (where the library can compose its internal dependency graph).
    – BionicCode
    Nov 11, 2021 at 19:25
  • @JohnWu You can develop the library totally independent from the application it extends. You can implement your own application. Like every high-level framework that is used to develop applications, you must implement an entry point that is called by the framework. This is pure IoC. You design an application that should be extesible by independent external 3rd party libraries the same way - simply apply the IoC principle. The application does not have to care about the library internals and vise versa. The interfaces and abstract classes the application defines are the communication endpoints.
    – BionicCode
    Nov 11, 2021 at 19:39
  • @JohnWu You simply deploy your application and add the assemblies to a specified directory where the application can find them in order to register the dependencies (the library's implementations like IPlugin are the dependencies of the application).
    – BionicCode
    Nov 11, 2021 at 19:42
  • Perhaps I missed it but you didn't seem to answer the question. I am not talking about a pluggable architecture or separate app domain or anything like that. I am talking about a very common use case-- a referenced assembly that is a library that has classes with internal dependencies.
    – John Wu
    Nov 11, 2021 at 21:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.