According to the documentation of Simple Injector one can use the following code to load assemblies dynamically
string pluginDirectory =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
var pluginAssemblies =
from file in new DirectoryInfo(pluginDirectory).GetFiles()
where file.Extension.ToLower() == ".dll"
select Assembly.Load(AssemblyName.GetAssemblyName(file.FullName));
container.Collection.Register<IPlugin>(pluginAssemblies);
Let's say each plugin has its properties (name, language, ...) which are specifed in App.Config in the following manner
<Plugins>
<Element name="SamplePlugin" filename="SamplePlugin.dll" language="pl-PL" modeId="1" />
</Plugins>
These values are necessary as they are used in constructor of every plugin.
Currently there is a PluginSettings
class which gets ConfigurationElements
looping through a ConfigurationElementCollection
and later on in the main ViewModel there is a loop going through the collection contained in the mentioned settings.
public class PluginSettings
{
readonly Configuration _config = ConfigurationManager
.OpenExeConfiguration(ConfigurationUserLevel.None);
public LoaderSection PluginAppearanceConfiguration
{
get
{
return (LoaderSection)_config.GetSection("pluginSection");
}
}
public PluginsCollection PluginsCollection
{
get
{
return PluginAppearanceConfiguration.PluginElement;
}
}
public IEnumerable<PluginElement> PluginElements
{
get
{
foreach (PluginElement selement in PluginsCollection)
{
if (selement != null)
yield return selement;
}
}
}
}
Current loading plugins in ViewModel
try
{
var factory = ModuleLoader
.LoadPluginFactory<PluginFactoryBase>(Path.Combine(pluginsPath, plug.Filename));
var configAppSettings = new ConfigAppSettings(plug.FactoryId, plug.ModeId, plug.Language);
Application.Current.Dispatcher.BeginInvoke((Action)delegate
{
plugins.Add(new Plugin(plug.Name, factory,
configSettings, configAppSettings));
});
}
catch (ReflectionTypeLoadException ex)
{
foreach (var item in ex.LoaderExceptions)
{
MessageBox.Show(item.Message, "Loader exception",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
Each plugin has its own PluginFactory
defined. Code is a few years old and wasn't written by anyone still present. Further refactoring would include making common Factory in the main application as I don't see reasons for the current state. Plugins are listed in the main app and are initialized (by plugins PluginFactory
) only when the specific one is selected (clicked) and then displayed.
How to adapt the code to follow good etiquette and process plugins in composition root and inject them?
This question is hard to answer, because it depends on what the ViewModel needs to do with this information.
When it comes to Simple Injector, however, all plugins that you might use during the execution of your application need to be registered up front. So, that means that if the ViewModel decides to only use 2 out of 5 defined plugins, you still need to register all 5. (* see note below)
But it likely means you need to do a few things:
IEnumerable<IPlugin>
to the ViewModel.For instance, if the ViewModel requires the plugin with language pl-PL
, it needs to find out what the index is of the pl-PL
plugin from the plugin information and use that index to get that plugin from the IEnumerable<IPlugin>
, e.g.:
var info = pluginInformation.First(p => p.Language == "pl-PL");
int index = pluginInformation.IndexOf(info);
IPlugin plugin = plugins.ElementAt(index);
plugin.DoAwesomeStuff();
Note that with Simple Injector, a call to ElementAt
on an injected IEnumerable<T>
will be an optimized O(1) operation. So even if the collection contains hundreds of plugins, only one plugin will be created at that time.
*This isn't completely true, because you could load and register plugins just-in-time, but this is an advanced topic which you likely don't need.