0

I'm trying to make a simple plugin system in which a plugins are dynamically loaded from .dll files on application's startup and show up in the UI.

This Answer seems to be exactly what I'm looking for. It uses MEF to load the plugins. I tried to create a simple project and follow the instructions. My solution has the following structure:

  • MeftTest (contains main MefTest.exe, references only MefTest.SDK and not plugins)
  • MefTest.SDK (contains the IPlugin.cs and IPluginViewModel.cs and the Engine.cs which loads the plugins from the application's directory)
  • MefTest.Plugin1 (contains first plugin, references MefTest.SDK)
  • MefTest.Plugin2 (contains second plugin, references MefTest.SDK)

MefTest.SDK -> IPlugin.cs

public interface IPlugin
{
    IPluginViewModel ViewModel { get; }
    ResourceDictionary View { get; }
    string Title { get; }
}

MefTest.SDK -> Engine.cs

public class Engine
{
    [ImportMany]
    private IEnumerable<IPlugin> plugins { get; set; }

    public async Task<ObservableCollection<IPlugin>> GetPlugins()
    {
        try
        {
            var folder = AppDomain.CurrentDomain.BaseDirectory;
            var catalog = new AggregateCatalog();
            //catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            catalog.Catalogs.Add(new DirectoryCatalog(folder));
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);

            var result = new ObservableCollection<IPlugin>();

            foreach (var p in plugins)
            {
                result.Add(p);
            }

            return result;
        }
        catch (Exception ex)
        {
            //I get the exception here...
            var t = ex;
            throw;
        }
    }
}

MefTest.Plugin1 -> Plugin1.cs

[Export(typeof(IPlugin))]
public class Plugin1 : IPlugin
{
    private MainViewModel viewModel { get; set; }

    public IPluginViewModel ViewModel
    {
        get { return viewModel; }
    }

    public ResourceDictionary View
    {
        get { return viewDictionary; }
    }

    private ResourceDictionary viewDictionary = new ResourceDictionary();

    public string Title
    {
        get { return "Plugin 1"; }
    }

    [ImportingConstructor]
    public Plugin1()
    {
        //I get the error here. tried both of these, none of them work
        viewDictionary.Source =
        //    new Uri("pack://application:,,,/MefTest.Plugin1;component/views/main.xaml", UriKind.Absolute);
        new Uri("/MefTest.Plugin1;component/views/main.xaml", 
        UriKind.Relative);
    }

    public override string ToString()
    {
        return Title;
    }
}

however, I get the error Could not load file or assembly 'MefTest.Plugin1.dll, Culture=neutral' or one of its dependencies. The system cannot find the file specified.

The Main.xaml file is in MefTest.Plugin1\Views\Main.xaml folder. Output type of the project is ClassLibrary and Build Action of the xaml file is Page.

PS: I tried to reference the plugin directly and add it without the MEF (Plugins.Add(new Plugin3.Plugin3());) and it still threw the same exception. So I don't think the problem is with the MEF part of the solution.

How can I fix this? Also, is there a better option to this approach?

Alireza Noori
  • 14,961
  • 30
  • 95
  • 179
  • The problem doesn't seem to be the ResourceDictionary Source URI, but the missing assembly file or one of its dependencies. Does it work if you (just for a test) add a reference to the plugin assembly to your project? – Clemens Jul 28 '17 at 07:16
  • @Clemens I tried it and unfortunately it didn't work either. (read the PS part at the end) – Alireza Noori Jul 28 '17 at 07:29
  • 1
    @Clemens exactly what I did. – Alireza Noori Jul 28 '17 at 07:41

1 Answers1

1

i do it this way in an xbap application...to retreive distant xaml. Try to do the same with your local resourtce

private ResourceDictionary LoadDictionary(string source)
    {
        Stream streamInfo = null;
        ResourceDictionary dictionary = null;

        try
        {
            streamInfo = DistantManager.Instance.GetResource(source);

            if (streamInfo != null)
            {
                Uri baseUri = DistantManager.Instance.GetUri(source);

                dictionary = XamlReader.Load(streamInfo) as ResourceDictionary;

                dictionary.Source = baseUri;
            }
        }
        catch (Exception e)
        {
            BusinessLogger.Manage(e);
            return null;
        }

        return dictionary;
    }

something like

Uri baseUri = new Uri(Mysource);
                dictionary = XamlReader.Load(XXXX) as ResourceDictionary;
                dictionary.Source = baseUri;

but on the other hand I do not understand why you want a ResourceDictionary as your plugin view...? just create the plugin user control ??

GCamel
  • 612
  • 4
  • 8
  • or the uri is the trouble because the link you gave is different _viewDictionary.Source = new Uri("/Extension.MyPlugin;component/View.xaml", UriKind.RelativeOrAbsolute); – GCamel Jul 28 '17 at 08:25
  • What is `DistantManager`? And what should the `XXXX` be? If the uri is the problem, how can I fix it? To be honest your answer is more confusing than my question LOL – Alireza Noori Jul 28 '17 at 11:36
  • As i told, why not just creating the user control in the plugin (new MyUCPluginView(); ) instead or exposing ResourceDictionary ?? DistantManager is special forxbap; but it open a ressource as stream and XXX is the stream = Application.GetResourceStream – GCamel Jul 28 '17 at 12:09
  • If possible, give me an example of the code you'd suggest (meaning usercontrol) – Alireza Noori Jul 28 '17 at 18:56
  • take a look a this : https://code.msdn.microsoft.com/windowsdesktop/Creating-a-WPF-Visual-3eb4e5dc – GCamel Jul 31 '17 at 08:26
  • Thanks. It wasn't exactly what I wanted but I combined the concept with the MVVM. – Alireza Noori Jul 31 '17 at 17:21