Plugin-system: How to handle events between host a

2019-09-02 17:53发布

问题:

Note: I need to use .NET 3.5

My test application is in three parts (different projects and different dlls); host-application, plugin-sdk and test-plugin.

The host application is a WPF application with an area to load in views from the plugins. Plugins is loaded through Reflection. This part work as supposed.

The SDK contains common interfaces that a plugin can implement. One of my interfaces has an event that the plugin must implement and when it's triggered, the main application need to listen for it, to perform an action based on the a custom EventArgs. The SDK is referenced from both the host-application and the plugin.

First my generic plugin-loader:

public static class GenericPluginLoader<T>
{
    public static ICollection<T> LoadPlugins(string path)
    {
        string[] dllFileNames = null;

        if (Directory.Exists(path))
        {
            dllFileNames = Directory.GetFiles(path, "*.dll");

            var assemblies = new List<Assembly>(dllFileNames.Length);
            foreach (var dllFile in dllFileNames)
            {
                var an = AssemblyName.GetAssemblyName(dllFile);
                var assembly = Assembly.Load(an);
                assemblies.Add(assembly);
            }

            var pluginType = typeof(T);
            var pluginTypes = new List<Type>();
            foreach (var assembly in assemblies)
            {
                if (assembly != null)
                {
                    Type[] types = assembly.GetTypes();

                    foreach (var type in types)
                    {
                        if (type.IsInterface || type.IsAbstract)
                        {
                            continue;
                        }
                        else
                        {
                            if (type.GetInterface(pluginType.FullName) != null)
                            {
                                pluginTypes.Add(type);
                            }
                        }
                    }
                }
            }

            var plugins = new List<T>(pluginTypes.Count);
            foreach (var type in pluginTypes)
            {
                var plugin = (T)Activator.CreateInstance(type);
                plugins.Add(plugin);
            }

            return plugins;
        }

        return null;
    }
}

The interface with an event I want my plugin til implement and the custom event args too

public class JournalLineEventArgs : EventArgs
{
    public string LineNumber { get; set; }
}

public interface IJournalPlugin
{
    event EventHandler<JournalLineEventArgs> LineAdding;
}

The event handler from my plugin

public event EventHandler<JournalLineEventArgs> LineAdding;

In my host application I load the plugins to a list and then add the event handler

private void InitializeIJournalPlugins()
{
    foreach (var plugin in journalPlugins) 
        plugin.LineAdding += OnJournalAdd;
}

The main problem here is that when I raise the event from my plugin it gives me a null-reference exception. I think that this may be because the host application knows about the plugin event but the plugin does not know that anything is listening to it - am I right? ;) But how can I make this work?

---- UPDATE 2015-05-26 15:08 ----

I'm for the time being calling it through a Command that's bound to a WPF button:

public ICommand JournalLineCommand
{
    get
    {
        var eventArgs = new JournalLineEventArgs()
        {
            LineNumber = TextField
        };             
        return new RelayCommand(() => LineAdding(this, eventArgs));
    }
}

When I try to raise it the event is null.

回答1:

I would advise you to rethink the architecture of your application to use standard solutions to your problem.

As Aron points, you can make use of an EventAggregator to publish and subscribe to events within your whole application. You can make use of any of the many implementations that are out there, here is a simple example using the lightweight Caliburn.Micro.EventAggregator:

Sample event:

public class SampleEvent
{
    public SampleEvent(int lineNumber)
    {
        LineNumber = lineNumber;
    }

    public int LineNumber { get; private set; }
}

Sample publisher:

public class Publisher
{
    public Publisher(IEventAggregator eventAggregator)
    {
        EventAggregator = eventAggregator;            
    }

    public void PublishLineNumber(int lineNumber)
    {
        EventAggregator.Publish(new SampleEvent(lineNumber));
    }

    public IEventAggregator EventAggregator { get; private set; }
}

Sample subscriber:

public class Subscriber : IHandle<SampleEvent>
{
    public Subscriber(IEventAggregator eventAggregator)
    {
        EventAggregator = eventAggregator;
        EventAggregator.Subscribe(this);
    }
    public IEventAggregator EventAggregator { get; private set; }

    public void Handle(SampleEvent message)
    {
        Console.WriteLine(message.LineNumber);
    }
}

Main method:

static void Main(string[] args)
    {
        EventAggregator eventAggregator = new EventAggregator();

        Publisher publisher = new Publisher(eventAggregator);
        Subscriber subscriber = new Subscriber(eventAggregator);

        publisher.PublishLineNumber(5);

        Console.ReadLine();
    }

As for loading the plugins, I would recommend you to use MEF to export/import them.

You can use both of them with .NET 3.5



回答2:

You should not reinvent the wheel. The Microsoft Patterns team have already got a library for writing WPF applications with a plugin architecture.

Install-Package Prism.PubSubEvents

https://msdn.microsoft.com/en-us/library/ff921122.aspx

Edit: For those who are still using .net 2.0, there is of course Prism 2.x

https://msdn.microsoft.com/en-us/library/ff648611.aspx