Skip to content

Commit 1413dc0

Browse files
authored
Added and fixed many things related to plugin tagging (weikio#29)
* Work with allowing Type Catalogs to be tagged. * Added and fixed many things related to plugin tagging.
1 parent 07dc8e3 commit 1413dc0

22 files changed

+653
-158
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ The following Plugin Framework samples are available from GitHub:
7777

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

80+
#### [Tagging & WinForms App](https://github.com/weikio/PluginFramework/tree/master/samples/WinFormsApp)
81+
82+
## Background and introduction
83+
84+
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/).
85+
8086
## Main concepts
8187

8288
Using Plugin Framework concentrates mainly around two concepts: **Plugins** and **Plugin Catalogs**.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Weikio.PluginFramework.Samples.Shared;
2+
3+
namespace WinFormsApp
4+
{
5+
public class DivideOperator : IOperator
6+
{
7+
public int Calculate(int x, int y)
8+
{
9+
return x / y;
10+
}
11+
}
12+
}

samples/WinFormsApp/Form1.cs

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,101 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Threading.Tasks;
34
using System.Windows.Forms;
45
using Weikio.PluginFramework.Abstractions;
56
using Weikio.PluginFramework.Catalogs;
67
using Weikio.PluginFramework.Catalogs.Delegates;
78
using Weikio.PluginFramework.Samples.Shared;
9+
using Weikio.PluginFramework.TypeFinding;
810

911
namespace WinFormsApp
1012
{
1113
public partial class Form1 : Form
1214
{
15+
private CompositePluginCatalog _allPlugins = new CompositePluginCatalog();
16+
1317
public Form1()
1418
{
1519
InitializeComponent();
1620
}
1721

1822
private async void Form1_Load(object sender, EventArgs e)
1923
{
20-
// 1. Uses folder catalog to add calculation operations inside the app. Mimics the WPF-sample.
21-
await AddCalculationOperators();
24+
// Demonstrates how tags can be used with Plugin Framework.
25+
// Single _allPlugins catalog is created from multiple different catalogs. All the plugins are tagged.
26+
await CreateCatalogs();
27+
28+
// 1. Adds calculation operators using tag 'operator'
29+
AddCalculationOperators();
2230

23-
// 2. Another folder catalog is used to add other forms inside the app. For each form a button is added into the toolstrip
24-
// Note: WinFormsPluginsLibrary must be manually built as it isn't referenced by this sample app
25-
// 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.
26-
await AddDialogs();
31+
// 2. Adds dialogs using 'dialog' tag
32+
AddDialogs();
2733

28-
// 3. Lastly, DelegateCatalog is used for creating an exit-button
29-
await AddExitButton();
34+
// 3. Adds buttons using 'button' tag
35+
AddButtons();
3036
}
3137

32-
private async Task AddCalculationOperators()
38+
private async Task CreateCatalogs()
3339
{
40+
// 1. Uses folder catalog to add calculation operations inside the app. Mimics the WPF-sample.
3441
var folderPluginCatalog = new FolderPluginCatalog(@"..\..\..\..\Shared\Weikio.PluginFramework.Samples.SharedPlugins\bin\debug\netcoreapp3.1",
35-
type => { type.Implements<IOperator>(); });
36-
37-
var assemblyPluginCatalog = new AssemblyPluginCatalog(typeof(Form1).Assembly, type => typeof(IOperator).IsAssignableFrom(type));
38-
39-
var pluginCatalog = new CompositePluginCatalog(folderPluginCatalog, assemblyPluginCatalog);
40-
await pluginCatalog.Initialize();
41-
42-
var allPlugins = pluginCatalog.GetPlugins();
42+
type => { type.Implements<IOperator>().Tag("operator"); });
43+
44+
_allPlugins.AddCatalog(folderPluginCatalog);
45+
46+
var assemblyPluginCatalog = new AssemblyPluginCatalog(typeof(Form1).Assembly, builder =>
47+
{
48+
builder.Implements<IOperator>()
49+
.Tag("operator");
50+
});
51+
52+
_allPlugins.AddCatalog(assemblyPluginCatalog);
53+
54+
// 2. Another folder catalog is used to add other forms inside the app. For each form a button is added into the toolstrip
55+
// Note: WinFormsPluginsLibrary must be manually built as it isn't referenced by this sample app
56+
var folderCatalog = new FolderPluginCatalog(@"..\..\..\..\WinFormsPluginsLibrary\bin\debug\netcoreapp3.1",
57+
type =>
58+
{
59+
type.Implements<IDialog>()
60+
.Tag("dialog");
61+
});
62+
63+
_allPlugins.AddCatalog(folderCatalog);
64+
65+
// 3. Lastly, DelegateCatalog is used for creating an exit-button
66+
var exitAction = new Action(() =>
67+
{
68+
var result = MessageBox.Show("This is also a plugin. Do you want to exit this sample app?", "Exit App?", MessageBoxButtons.YesNo);
69+
70+
if (result == DialogResult.No)
71+
{
72+
return;
73+
}
74+
75+
Application.Exit();
76+
});
77+
78+
var exitCatalog = new DelegatePluginCatalog(exitAction, options: new DelegatePluginCatalogOptions(){Tags = new List<string> { "buttons" }} , pluginName: "Exit" );
79+
_allPlugins.AddCatalog(exitCatalog);
4380

81+
// Finally the plugin catalog is initialized
82+
await _allPlugins.Initialize();
83+
}
84+
85+
private void AddCalculationOperators()
86+
{
87+
var allPlugins = _allPlugins.GetByTag("operator");
4488
foreach (var plugin in allPlugins)
4589
{
4690
listBox1.Items.Add(plugin);
4791
}
4892
}
49-
50-
private async Task AddDialogs()
51-
{
52-
var folderCatalog = new FolderPluginCatalog(@"..\..\..\..\WinFormsPluginsLibrary\bin\debug\netcoreapp3.1",
53-
type => { type.Implements<IDialog>(); });
5493

55-
await folderCatalog.Initialize();
56-
57-
foreach (var dialogPlugin in folderCatalog.GetPlugins())
94+
private void AddDialogs()
95+
{
96+
var dialogPlugins = _allPlugins.GetByTag("dialog");
97+
98+
foreach (var dialogPlugin in dialogPlugins)
5899
{
59100
var menuItem = new ToolStripButton(dialogPlugin.Name, null, (o, args) =>
60101
{
@@ -66,31 +107,18 @@ private async Task AddDialogs()
66107
}
67108
}
68109

69-
70-
private async Task AddExitButton()
110+
private void AddButtons()
71111
{
72-
var exitAction = new Action(() =>
73-
{
74-
var result = MessageBox.Show("This is also a plugin. Do you want to exit this sample app?", "Exit App?", MessageBoxButtons.YesNo);
75-
76-
if (result == DialogResult.No)
77-
{
78-
return;
79-
}
80-
81-
Application.Exit();
82-
});
83-
84-
var exitCatalog = new DelegatePluginCatalog(exitAction, "Exit");
85-
await exitCatalog.Initialize();
112+
var buttonPlugins = _allPlugins.GetByTag("buttons");
86113

87-
var exitPlugin = exitCatalog.Single();
88-
89-
menuStrip1.Items.Add(new ToolStripButton(exitPlugin.Name, null, (sender, args) =>
114+
foreach (var buttonPlugin in buttonPlugins)
90115
{
91-
dynamic instance = Activator.CreateInstance(exitPlugin);
92-
instance.Run();
93-
}));
116+
menuStrip1.Items.Add(new ToolStripButton(buttonPlugin.Name, null, (sender, args) =>
117+
{
118+
dynamic instance = Activator.CreateInstance(buttonPlugin);
119+
instance.Run();
120+
}));
121+
}
94122
}
95123

96124
private void button1_Click(object sender, EventArgs e)

src/Weikio.PluginFramework.Abstractions/IPluginCatalogExtensions.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Threading.Tasks;
34

45
namespace Weikio.PluginFramework.Abstractions
@@ -26,5 +27,16 @@ public static Plugin Get(this IPluginCatalog catalog)
2627
{
2728
return catalog.Single();
2829
}
30+
31+
/// <summary>
32+
/// Gets the plugins by tag.
33+
/// </summary>
34+
/// <param name="catalog">The catalog from which the plugin is retrieved.</param>
35+
/// <param name="tag">The tag.</param>
36+
/// <returns>The plugin</returns>
37+
public static List<Plugin> GetByTag(this IPluginCatalog catalog, string tag)
38+
{
39+
return catalog.GetPlugins().Where(x => x.Tags.Contains(tag)).ToList();
40+
}
2941
}
3042
}

src/Weikio.PluginFramework.Abstractions/Plugin.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,21 @@ public class Plugin
4848
/// <summary>
4949
/// Gets the tag of the plugin
5050
/// </summary>
51-
public string Tag { get; }
51+
public string Tag
52+
{
53+
get
54+
{
55+
return Tags.FirstOrDefault();
56+
}
57+
}
5258

59+
/// <summary>
60+
/// Gets the tags of the plugin
61+
/// </summary>
62+
public List<string> Tags { get; }
63+
5364
public Plugin(Assembly assembly, Type type, string name, Version version, IPluginCatalog source, string description = "", string productVersion = "",
54-
string tag = "")
65+
string tag = "", List<string> tags = null)
5566
{
5667
Assembly = assembly;
5768
Type = type;
@@ -60,7 +71,17 @@ public Plugin(Assembly assembly, Type type, string name, Version version, IPlugi
6071
Source = source;
6172
Description = description;
6273
ProductVersion = productVersion;
63-
Tag = tag;
74+
Tags = tags;
75+
76+
if (Tags == null)
77+
{
78+
Tags = new List<string>();
79+
}
80+
81+
if (!string.IsNullOrWhiteSpace(tag))
82+
{
83+
Tags.Add(tag);
84+
}
6485
}
6586

6687
public static implicit operator Type(Plugin plugin)

src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public AssemblyPluginCatalog(Assembly assembly) : this(null, assembly)
2626
{
2727
}
2828

29-
public AssemblyPluginCatalog(string assemblyPath, AssemblyPluginCatalogOptions options = null) : this(assemblyPath, null, null, null, null, null, options)
29+
public AssemblyPluginCatalog(string assemblyPath, AssemblyPluginCatalogOptions options = null) : this(assemblyPath, null, null, null, null, null,
30+
options)
3031
{
3132
}
3233

@@ -89,7 +90,7 @@ public AssemblyPluginCatalog(string assemblyPath = null, Assembly assembly = nul
8990
{
9091
throw new ArgumentNullException($"{nameof(assembly)} or {nameof(assemblyPath)} must be set.");
9192
}
92-
93+
9394
_options = options ?? new AssemblyPluginCatalogOptions();
9495

9596
SetFilters(filter, taggedFilters, criteria, configureFinder);
@@ -98,25 +99,32 @@ public AssemblyPluginCatalog(string assemblyPath = null, Assembly assembly = nul
9899
private void SetFilters(Predicate<Type> filter, Dictionary<string, Predicate<Type>> taggedFilters, TypeFinderCriteria criteria,
99100
Action<TypeFinderCriteriaBuilder> configureFinder)
100101
{
101-
if (_options.TypeFinderCriterias == null)
102+
if (_options.TypeFinderOptions == null)
102103
{
103-
_options.TypeFinderCriterias = new Dictionary<string, TypeFinderCriteria>();
104+
_options.TypeFinderOptions = new TypeFinderOptions();
104105
}
105-
106+
107+
if (_options.TypeFinderOptions.TypeFinderCriterias == null)
108+
{
109+
_options.TypeFinderOptions.TypeFinderCriterias = new List<TypeFinderCriteria>();
110+
}
111+
106112
if (filter != null)
107113
{
108114
var filterCriteria = new TypeFinderCriteria { Query = (context, type) => filter(type) };
115+
filterCriteria.Tags.Add(string.Empty);
109116

110-
_options.TypeFinderCriterias.Add(string.Empty, filterCriteria);
117+
_options.TypeFinderOptions.TypeFinderCriterias.Add(filterCriteria);
111118
}
112119

113120
if (taggedFilters?.Any() == true)
114121
{
115122
foreach (var taggedFilter in taggedFilters)
116123
{
117124
var taggedCriteria = new TypeFinderCriteria { Query = (context, type) => taggedFilter.Value(type) };
125+
taggedCriteria.Tags.Add(taggedFilter.Key);
118126

119-
_options.TypeFinderCriterias.Add(taggedFilter.Key, taggedCriteria);
127+
_options.TypeFinderOptions.TypeFinderCriterias.Add(taggedCriteria);
120128
}
121129
}
122130

@@ -127,21 +135,33 @@ private void SetFilters(Predicate<Type> filter, Dictionary<string, Predicate<Typ
127135

128136
var configuredCriteria = builder.Build();
129137

130-
_options.TypeFinderCriterias.Add("", configuredCriteria);
138+
_options.TypeFinderOptions.TypeFinderCriterias.Add(configuredCriteria);
131139
}
132140

133141
if (criteria != null)
134142
{
135-
_options.TypeFinderCriterias.Add("", criteria);
143+
_options.TypeFinderOptions.TypeFinderCriterias.Add(criteria);
144+
}
145+
146+
if (_options.TypeFinderCriterias?.Any() == true)
147+
{
148+
foreach (var typeFinderCriteria in _options.TypeFinderCriterias)
149+
{
150+
var crit = typeFinderCriteria.Value;
151+
crit.Tags = new List<string>() { typeFinderCriteria.Key };
152+
153+
_options.TypeFinderOptions.TypeFinderCriterias.Add(crit);
154+
}
136155
}
137156

138-
if (_options.TypeFinderCriterias?.Any() != true)
157+
if (_options.TypeFinderOptions.TypeFinderCriterias.Any() != true)
139158
{
140159
var findAll = TypeFinderCriteriaBuilder
141160
.Create()
161+
.Tag(string.Empty)
142162
.Build();
143163

144-
_options.TypeFinderCriterias.Add(string.Empty, findAll);
164+
_options.TypeFinderOptions.TypeFinderCriterias.Add(findAll);
145165
}
146166
}
147167

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

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

163186
var finder = new TypeFinder();
164187

165-
foreach (var typeFinderCriteria in _options.TypeFinderCriterias)
188+
foreach (var typeFinderCriteria in _options.TypeFinderOptions.TypeFinderCriterias)
166189
{
167-
var pluginTypes = finder.Find(typeFinderCriteria.Value, _assembly, _pluginAssemblyLoadContext);
190+
var pluginTypes = finder.Find(typeFinderCriteria, _assembly, _pluginAssemblyLoadContext);
168191

169192
foreach (var type in pluginTypes)
170193
{
171-
var typePluginCatalog = new TypePluginCatalog(type, new TypePluginCatalogOptions() { PluginNameOptions = _options.PluginNameOptions });
194+
var typePluginCatalog = new TypePluginCatalog(type,
195+
new TypePluginCatalogOptions()
196+
{
197+
PluginNameOptions = _options.PluginNameOptions,
198+
TypeFindingContext = _pluginAssemblyLoadContext,
199+
TypeFinderOptions = _options.TypeFinderOptions
200+
});
201+
172202
await typePluginCatalog.Initialize();
173203

174204
_plugins.Add(typePluginCatalog);

0 commit comments

Comments
 (0)