Skip to content

Commit

Permalink
Added and fixed many things related to plugin tagging (#29)
Browse files Browse the repository at this point in the history
* Work with allowing Type Catalogs to be tagged.

* Added and fixed many things related to plugin tagging.
  • Loading branch information
mikoskinen committed Sep 8, 2020
1 parent 07dc8e3 commit 1413dc0
Show file tree
Hide file tree
Showing 22 changed files with 653 additions and 158 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ The following Plugin Framework samples are available from GitHub:

#### [Delegates & Plugin Framework & ASP.NET Core](https://github.com/weikio/PluginFramework/tree/master/samples/WebAppWithDelegate)

#### [Tagging & WinForms App](https://github.com/weikio/PluginFramework/tree/master/samples/WinFormsApp)

## Background and introduction

For more details related to Plugin Framework's background, please see the following [introduction article from InfoQ](https://www.infoq.com/articles/Plugin-Framework-DotNet/).

## Main concepts

Using Plugin Framework concentrates mainly around two concepts: **Plugins** and **Plugin Catalogs**.
Expand Down
12 changes: 12 additions & 0 deletions samples/WinFormsApp/DivideOperator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Weikio.PluginFramework.Samples.Shared;

namespace WinFormsApp
{
public class DivideOperator : IOperator
{
public int Calculate(int x, int y)
{
return x / y;
}
}
}
122 changes: 75 additions & 47 deletions samples/WinFormsApp/Form1.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,101 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Forms;
using Weikio.PluginFramework.Abstractions;
using Weikio.PluginFramework.Catalogs;
using Weikio.PluginFramework.Catalogs.Delegates;
using Weikio.PluginFramework.Samples.Shared;
using Weikio.PluginFramework.TypeFinding;

namespace WinFormsApp
{
public partial class Form1 : Form
{
private CompositePluginCatalog _allPlugins = new CompositePluginCatalog();

public Form1()
{
InitializeComponent();
}

private async void Form1_Load(object sender, EventArgs e)
{
// 1. Uses folder catalog to add calculation operations inside the app. Mimics the WPF-sample.
await AddCalculationOperators();
// Demonstrates how tags can be used with Plugin Framework.
// Single _allPlugins catalog is created from multiple different catalogs. All the plugins are tagged.
await CreateCatalogs();

// 1. Adds calculation operators using tag 'operator'
AddCalculationOperators();

// 2. Another folder catalog is used to add other forms inside the app. For each form a button is added into the toolstrip
// Note: WinFormsPluginsLibrary must be manually built as it isn't referenced by this sample app
// Note: Program.cs contains an initialization code which is required. Without it, we will get an exception about a missing reference. This is something we hope to get around in the future versions of Plugin Framework.
await AddDialogs();
// 2. Adds dialogs using 'dialog' tag
AddDialogs();

// 3. Lastly, DelegateCatalog is used for creating an exit-button
await AddExitButton();
// 3. Adds buttons using 'button' tag
AddButtons();
}

private async Task AddCalculationOperators()
private async Task CreateCatalogs()
{
// 1. Uses folder catalog to add calculation operations inside the app. Mimics the WPF-sample.
var folderPluginCatalog = new FolderPluginCatalog(@"..\..\..\..\Shared\Weikio.PluginFramework.Samples.SharedPlugins\bin\debug\netcoreapp3.1",
type => { type.Implements<IOperator>(); });

var assemblyPluginCatalog = new AssemblyPluginCatalog(typeof(Form1).Assembly, type => typeof(IOperator).IsAssignableFrom(type));

var pluginCatalog = new CompositePluginCatalog(folderPluginCatalog, assemblyPluginCatalog);
await pluginCatalog.Initialize();

var allPlugins = pluginCatalog.GetPlugins();
type => { type.Implements<IOperator>().Tag("operator"); });

_allPlugins.AddCatalog(folderPluginCatalog);

var assemblyPluginCatalog = new AssemblyPluginCatalog(typeof(Form1).Assembly, builder =>
{
builder.Implements<IOperator>()
.Tag("operator");
});

_allPlugins.AddCatalog(assemblyPluginCatalog);

// 2. Another folder catalog is used to add other forms inside the app. For each form a button is added into the toolstrip
// Note: WinFormsPluginsLibrary must be manually built as it isn't referenced by this sample app
var folderCatalog = new FolderPluginCatalog(@"..\..\..\..\WinFormsPluginsLibrary\bin\debug\netcoreapp3.1",
type =>
{
type.Implements<IDialog>()
.Tag("dialog");
});

_allPlugins.AddCatalog(folderCatalog);

// 3. Lastly, DelegateCatalog is used for creating an exit-button
var exitAction = new Action(() =>
{
var result = MessageBox.Show("This is also a plugin. Do you want to exit this sample app?", "Exit App?", MessageBoxButtons.YesNo);
if (result == DialogResult.No)
{
return;
}
Application.Exit();
});

var exitCatalog = new DelegatePluginCatalog(exitAction, options: new DelegatePluginCatalogOptions(){Tags = new List<string> { "buttons" }} , pluginName: "Exit" );
_allPlugins.AddCatalog(exitCatalog);

// Finally the plugin catalog is initialized
await _allPlugins.Initialize();
}

private void AddCalculationOperators()
{
var allPlugins = _allPlugins.GetByTag("operator");
foreach (var plugin in allPlugins)
{
listBox1.Items.Add(plugin);
}
}

private async Task AddDialogs()
{
var folderCatalog = new FolderPluginCatalog(@"..\..\..\..\WinFormsPluginsLibrary\bin\debug\netcoreapp3.1",
type => { type.Implements<IDialog>(); });

await folderCatalog.Initialize();

foreach (var dialogPlugin in folderCatalog.GetPlugins())
private void AddDialogs()
{
var dialogPlugins = _allPlugins.GetByTag("dialog");

foreach (var dialogPlugin in dialogPlugins)
{
var menuItem = new ToolStripButton(dialogPlugin.Name, null, (o, args) =>
{
Expand All @@ -66,31 +107,18 @@ private async Task AddDialogs()
}
}


private async Task AddExitButton()
private void AddButtons()
{
var exitAction = new Action(() =>
{
var result = MessageBox.Show("This is also a plugin. Do you want to exit this sample app?", "Exit App?", MessageBoxButtons.YesNo);
if (result == DialogResult.No)
{
return;
}
Application.Exit();
});

var exitCatalog = new DelegatePluginCatalog(exitAction, "Exit");
await exitCatalog.Initialize();
var buttonPlugins = _allPlugins.GetByTag("buttons");

var exitPlugin = exitCatalog.Single();

menuStrip1.Items.Add(new ToolStripButton(exitPlugin.Name, null, (sender, args) =>
foreach (var buttonPlugin in buttonPlugins)
{
dynamic instance = Activator.CreateInstance(exitPlugin);
instance.Run();
}));
menuStrip1.Items.Add(new ToolStripButton(buttonPlugin.Name, null, (sender, args) =>
{
dynamic instance = Activator.CreateInstance(buttonPlugin);
instance.Run();
}));
}
}

private void button1_Click(object sender, EventArgs e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Weikio.PluginFramework.Abstractions
Expand Down Expand Up @@ -26,5 +27,16 @@ public static Plugin Get(this IPluginCatalog catalog)
{
return catalog.Single();
}

/// <summary>
/// Gets the plugins by tag.
/// </summary>
/// <param name="catalog">The catalog from which the plugin is retrieved.</param>
/// <param name="tag">The tag.</param>
/// <returns>The plugin</returns>
public static List<Plugin> GetByTag(this IPluginCatalog catalog, string tag)
{
return catalog.GetPlugins().Where(x => x.Tags.Contains(tag)).ToList();
}
}
}
27 changes: 24 additions & 3 deletions src/Weikio.PluginFramework.Abstractions/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,21 @@ public class Plugin
/// <summary>
/// Gets the tag of the plugin
/// </summary>
public string Tag { get; }
public string Tag
{
get
{
return Tags.FirstOrDefault();
}
}

/// <summary>
/// Gets the tags of the plugin
/// </summary>
public List<string> Tags { get; }

public Plugin(Assembly assembly, Type type, string name, Version version, IPluginCatalog source, string description = "", string productVersion = "",
string tag = "")
string tag = "", List<string> tags = null)
{
Assembly = assembly;
Type = type;
Expand All @@ -60,7 +71,17 @@ public class Plugin
Source = source;
Description = description;
ProductVersion = productVersion;
Tag = tag;
Tags = tags;

if (Tags == null)
{
Tags = new List<string>();
}

if (!string.IsNullOrWhiteSpace(tag))
{
Tags.Add(tag);
}
}

public static implicit operator Type(Plugin plugin)
Expand Down
58 changes: 44 additions & 14 deletions src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public AssemblyPluginCatalog(Assembly assembly) : this(null, assembly)
{
}

public AssemblyPluginCatalog(string assemblyPath, AssemblyPluginCatalogOptions options = null) : this(assemblyPath, null, null, null, null, null, options)
public AssemblyPluginCatalog(string assemblyPath, AssemblyPluginCatalogOptions options = null) : this(assemblyPath, null, null, null, null, null,
options)
{
}

Expand Down Expand Up @@ -89,7 +90,7 @@ public AssemblyPluginCatalog(Assembly assembly, AssemblyPluginCatalogOptions opt
{
throw new ArgumentNullException($"{nameof(assembly)} or {nameof(assemblyPath)} must be set.");
}

_options = options ?? new AssemblyPluginCatalogOptions();

SetFilters(filter, taggedFilters, criteria, configureFinder);
Expand All @@ -98,25 +99,32 @@ public AssemblyPluginCatalog(Assembly assembly, AssemblyPluginCatalogOptions opt
private void SetFilters(Predicate<Type> filter, Dictionary<string, Predicate<Type>> taggedFilters, TypeFinderCriteria criteria,
Action<TypeFinderCriteriaBuilder> configureFinder)
{
if (_options.TypeFinderCriterias == null)
if (_options.TypeFinderOptions == null)
{
_options.TypeFinderCriterias = new Dictionary<string, TypeFinderCriteria>();
_options.TypeFinderOptions = new TypeFinderOptions();
}


if (_options.TypeFinderOptions.TypeFinderCriterias == null)
{
_options.TypeFinderOptions.TypeFinderCriterias = new List<TypeFinderCriteria>();
}

if (filter != null)
{
var filterCriteria = new TypeFinderCriteria { Query = (context, type) => filter(type) };
filterCriteria.Tags.Add(string.Empty);

_options.TypeFinderCriterias.Add(string.Empty, filterCriteria);
_options.TypeFinderOptions.TypeFinderCriterias.Add(filterCriteria);
}

if (taggedFilters?.Any() == true)
{
foreach (var taggedFilter in taggedFilters)
{
var taggedCriteria = new TypeFinderCriteria { Query = (context, type) => taggedFilter.Value(type) };
taggedCriteria.Tags.Add(taggedFilter.Key);

_options.TypeFinderCriterias.Add(taggedFilter.Key, taggedCriteria);
_options.TypeFinderOptions.TypeFinderCriterias.Add(taggedCriteria);
}
}

Expand All @@ -127,21 +135,33 @@ public AssemblyPluginCatalog(Assembly assembly, AssemblyPluginCatalogOptions opt

var configuredCriteria = builder.Build();

_options.TypeFinderCriterias.Add("", configuredCriteria);
_options.TypeFinderOptions.TypeFinderCriterias.Add(configuredCriteria);
}

if (criteria != null)
{
_options.TypeFinderCriterias.Add("", criteria);
_options.TypeFinderOptions.TypeFinderCriterias.Add(criteria);
}

if (_options.TypeFinderCriterias?.Any() == true)
{
foreach (var typeFinderCriteria in _options.TypeFinderCriterias)
{
var crit = typeFinderCriteria.Value;
crit.Tags = new List<string>() { typeFinderCriteria.Key };

_options.TypeFinderOptions.TypeFinderCriterias.Add(crit);
}
}

if (_options.TypeFinderCriterias?.Any() != true)
if (_options.TypeFinderOptions.TypeFinderCriterias.Any() != true)
{
var findAll = TypeFinderCriteriaBuilder
.Create()
.Tag(string.Empty)
.Build();

_options.TypeFinderCriterias.Add(string.Empty, findAll);
_options.TypeFinderOptions.TypeFinderCriterias.Add(findAll);
}
}

Expand All @@ -153,7 +173,10 @@ public async Task Initialize()
{
throw new ArgumentException($"Assembly in path {_assemblyPath} does not exist.");
}
}

if (_assembly == null && File.Exists(_assemblyPath) || File.Exists(_assemblyPath) && _pluginAssemblyLoadContext == null)
{
_pluginAssemblyLoadContext = new PluginAssemblyLoadContext(_assemblyPath, _options.PluginLoadContextOptions);
_assembly = _pluginAssemblyLoadContext.Load();
}
Expand All @@ -162,13 +185,20 @@ public async Task Initialize()

var finder = new TypeFinder();

foreach (var typeFinderCriteria in _options.TypeFinderCriterias)
foreach (var typeFinderCriteria in _options.TypeFinderOptions.TypeFinderCriterias)
{
var pluginTypes = finder.Find(typeFinderCriteria.Value, _assembly, _pluginAssemblyLoadContext);
var pluginTypes = finder.Find(typeFinderCriteria, _assembly, _pluginAssemblyLoadContext);

foreach (var type in pluginTypes)
{
var typePluginCatalog = new TypePluginCatalog(type, new TypePluginCatalogOptions() { PluginNameOptions = _options.PluginNameOptions });
var typePluginCatalog = new TypePluginCatalog(type,
new TypePluginCatalogOptions()
{
PluginNameOptions = _options.PluginNameOptions,
TypeFindingContext = _pluginAssemblyLoadContext,
TypeFinderOptions = _options.TypeFinderOptions
});

await typePluginCatalog.Initialize();

_plugins.Add(typePluginCatalog);
Expand Down
Loading

0 comments on commit 1413dc0

Please sign in to comment.