I am seeing that
I am seeing that
I'm not sure I follow the question. IUsers.Events.BeforeUpdate is unrelated to plugin initialization.
I'm not sure I follow the question. IUsers.Events.BeforeUpdate is unrelated to plugin initialization.
within the Initialize() method of the core plugin an event handler is setup IUsers.Events.BeforeUpdate, the main plugin is receiving these events before the related child plugins have been initialized. Therefore, the plugin group has not completed setup and things go wrong. The events should not be sent to a half-initialized set of plugins.
This is unchanged. The order of plugin initialization has never been deterministic, including whether or not plugins are child plugins.
One option in this case is to use the AfterInitialization event.
It is unchanged but because the SQL is now initialized in a plugin this becomes a problem as the code that is initialized in the core plugin is started to be active before the database information has been setup. I will do as you advise but this is something that will catch others out, so worth flagging.
What I usually do, myself, is to implement all "infrastructural" components of a plugin tree on the parent, itself. So, IConfigurable, ISqlServerConnectedPlugin, IInstallablePlugin, ISocket, etc all on a root plugin and then manage the state of their controllers explicitly, wrapping and/or making their controllers available elsewhere in my plugin. Then child plugins and plugin-defined services and apis can focus on specifics.
I have just tried it and it doens't work
that's great for simple plugins, but ones that are complex and need separation of concern or need to use common libraries such as a common SQL installer class this will not work. Ideally we need an event on IPluginGroup so that the system knows that this particular set of plugins is completely initialized.
or always initialize the ISqlServerConnectedPlugins first?
that's great for simple plugins, but ones that are complex and need separation of concern
I can assure you I've used this pattern on highly complicated plugins (including many components of the platform, itself) with clear separations of concerns, DI, etc. It's in service of that, not orthogonal to it. That said, it's just one of many possible approaches.
or always initialize the ISqlServerConnectedPlugins first?
This actually is the case. Let's back up for a moment:
For any given set of plugins, their controllers are injected before they're initialized. This includes ISqlServerConnectedPlugin, IConfigurablePlugin, etc.
The order is:
using Telligent.Evolution.Extensibility.Configuration.Version1;
using Telligent.Evolution.Extensibility.Version1;
using Telligent.Evolution.Extensibility.Version2;
namespace TestPlugins;
public class TestChildPluginB : IPlugin, ISqlServerConnectedPlugin, IConfigurablePlugin
{
public string Name => "Test Child Plugin B";
public string Description => Name;
public void Initialize()
{
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginB.Initialize");
PluginManager.AfterInitialization += (sender, args) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginB.AfterInitialization");
}
public void SetController(ISqlServerConnectedPlugionController controller) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginB.SetController");
public void Update(IPluginConfiguration configuration) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginB.Update");
public Version Version => new(1, 0, 0, 0);
public PropertyGroup[] ConfigurationOptions => [ new() { LabelText = "" } ];
public void Install(Version lastInstalledVersion) { }
public Task InstallAsync(Version lastInstalledVersion, CancellationToken cancellationToken) => Task.CompletedTask;
public void Uninstall() { }
public Task UninstallAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
public class TestParentPlugin : IPlugin, IPluginGroup, ISqlServerConnectedPlugin, IConfigurablePlugin
{
public string Name => "Test Parent Plugin";
public string Description => Name;
public IEnumerable<Type> Plugins => [typeof(TestChildPluginA), typeof(TestChildPluginB)];
public void Initialize()
{
System.Diagnostics.Debug.WriteLine("TestPlugins: TestParentPlugin.Initialize");
PluginManager.AfterInitialization += (sender, args) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestParentPlugin.AfterInitialization");
}
public void SetController(ISqlServerConnectedPlugionController controller) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestParentPlugin.SetController");
public void Update(IPluginConfiguration configuration) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestParentPlugin.Update");
public Version Version => new(1, 0, 0, 0);
public PropertyGroup[] ConfigurationOptions => [ new() { LabelText = "" } ];
public void Install(Version lastInstalledVersion) { }
public Task InstallAsync(Version lastInstalledVersion, CancellationToken cancellationToken) => Task.CompletedTask;
public void Uninstall() { }
public Task UninstallAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
public class TestChildPluginA : IPlugin, ISqlServerConnectedPlugin, IConfigurablePlugin
{
public string Name => "Test Child Plugin A";
public string Description => Name;
public void Initialize()
{
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginA.Initialize");
PluginManager.AfterInitialization += (sender, args) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginA.AfterInitialization");
}
public void SetController(ISqlServerConnectedPlugionController controller) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginA.SetController");
public void Update(IPluginConfiguration configuration) =>
System.Diagnostics.Debug.WriteLine("TestPlugins: TestChildPluginA.Update");
public Version Version => new(1, 0, 0, 0);
public PropertyGroup[] ConfigurationOptions => [ new() { LabelText = "" } ];
public void Install(Version lastInstalledVersion) { }
public Task InstallAsync(Version lastInstalledVersion, CancellationToken cancellationToken) => Task.CompletedTask;
public void Uninstall() { }
public Task UninstallAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}TestPlugins: TestParentPlugin.SetController TestPlugins: TestParentPlugin.Update TestPlugins: TestChildPluginA.SetController TestPlugins: TestChildPluginA.Update TestPlugins: TestChildPluginB.SetController TestPlugins: TestChildPluginB.Update TestPlugins: TestParentPlugin.Initialize TestPlugins: TestChildPluginA.Initialize TestPlugins: TestChildPluginB.Initialize TestPlugins: TestParentPlugin.AfterInitialization TestPlugins: TestChildPluginA.AfterInitialization TestPlugins: TestChildPluginB.AfterInitialization
Using AfterInitialization does solve the timing issue, so we can use that pattern elsewhere. What was catching me out was that the reference in PluginConnectionStrings had a bad namespace, not sure when that happened but it meant that SetController was never called. The issue here is that the SQL plugin and the Core plugin remained enabled, even though an exception had been thrown that meant they should not be working or enabled. If an exception is thrown within the main plugin or it's dependent plugins then I think they should all be automatically disabled as they are in an inconsistent state.