2

I've written a plugin manager so

public class WeinCadPluginManager : NinjectModule, IEnableSerilog
{
    public override void Load()
    {

        var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase);
        var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath);
        var dirPath = Path.GetDirectoryName(codeBasePath);
        Debug.Assert(dirPath != null, "dirPath != null");
        var path = dirPath;

        var types = Directory
            .GetFiles(path, "*.dll")
            .Select (Assembly.LoadFile)
            .SelectMany (assembly =>
                {
                    try
                    {
                        return assembly.GetExportedTypes();
                    }
                    catch (Exception e)
                    {
                        this.Serilog()
                            .Error
                            (e, "Failed to load assembly {assembly}", assembly);
                        return new Type[]
                        {
                        };
                    }
                })
            .Where(type=>typeof(IWeinCadPlugin).IsAssignableFrom(type))
            .ToList();

        foreach (var assembly in types)
        {
            Kernel.Bind<IWeinCadPlugin>().To(assembly).InSingletonScope();
        }
    }
}

Now this pretty much duplicates

Kernel.Load("*.dll")

except if there are any errors exporting the types from an assembly then Kernel.Load crashes with no error handling possible. I wouldn't want the failure to load a single plugin assembly to crash my app. Is my solution the only viable way or does Ninject have some error handling available?

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217

1 Answers1

1

I think a try { } catch { } around each Kernel.Load("specificplugin.dll") should suffice. You would still have to find all the assemblies yourself, but would have to write less code.

var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase);
var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath);
var dirPath = Path.GetDirectoryName(codeBasePath);

var dllPaths = Directory.GetFiles(dirpath, "*.dll");

foreach(string dllPath in dllPaths)
{
     try
     {
         kernel.Load(dllPath);
     }
     catch (Exception e)
     {
         this.Serilog()
             .Error(e, "Failed to load assembly {assembly}", assembly);
     }
}

Drawback: ninject must be adding the binding to the kernel either on .Bind() or on .To(..), because all the rest of the fluent syntax methods are optional. So if there would be an exception in the .When(), .InScope(),.. any other optional method, you would be left with a non-complete binding and thus, most likely, a faulty software.

(However i suspect that most errors will not materialize when creating the binding, but rather when activating the binding. And you are not protected against that.)

As far as i know there is no way to remove bindings from ninject once you've added them. Except for the .Rebind() - but that is always replacing a binding with a different one. So no, i don't think there's something like "rollback on exception".

So we must be looking for an alternative solution. There is one: child kernels. https://github.com/ninject/ninject.extensions.childkernel

Load each plugin into it's own child kernel. in case the fooplugin.dll load fails, dispose the child kernel. In case it works.. well you're good to go! :) This also has the advantage that plugin's can't influence one another. Imagine two plugins would do IBindingRoot.Bind<string>().ToConstant("some constant") ;-)

(please note that i haven't tested whether disposing the child kernel before the parent kernel works "as expected", so you should test it out first. Simple enough, right?)

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68