Implementing Plugin Architecture in WPF Without External Tools
This implementation demonstrates loading and executing a WPF application as a plugin with in a host application, using only native .NET capabilities.
The solution involves three projects: AbstractionLayer for defining interfaces, WPFIPluginDemo as the main application, and WpfPlugin3 as the loadable plugin module.
The AbstractionLayer project defines the IPlugin interface:
using System;
namespace AbstractionLayer
{
public interface IPlugin
{
void Initialize();
}
}
The main application's MainWindow.xaml inculdes two buttons for discovering and displaying plugins:
<Window x:Class="WPFIPluginDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Button x:Name="btnSearchPlugins" Content="Discover Plugins" Click="BtnSearchPlugins_OnClick"/>
<Button x:Name="btnShowPlugin" Content="Display Plugin" Click="BtnShowPlugin_OnClick" Margin="0,10,0,0"/>
</StackPanel>
</Window>
The corresponding code-behind handles plugin discovery and execution:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using AbstractionLayer;
using Path = System.IO.Path;
namespace WPFIPluginDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BtnSearchPlugins_OnClick(object sender, RoutedEventArgs e)
{
LoadPlugins(GetCurrentProgramPath() + "\\Plugins");
}
public static string GetCurrentProgramPath()
{
var entryAssembly = Assembly.GetEntryAssembly();
var location = entryAssembly.Location;
return Path.GetDirectoryName(location);
}
public List<IPlugin> PluginList = new List<IPlugin>();
public void LoadPlugins(string directory)
{
try
{
var files = Directory.GetFiles(directory, "*.exe", SearchOption.TopDirectoryOnly);
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFrom(file);
var types = assembly.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface);
foreach (var type in types)
{
IPlugin instance = (IPlugin)Activator.CreateInstance(type);
instance.Initialize();
PluginList.Add(instance);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
private void BtnShowPlugin_OnClick(object sender, RoutedEventArgs e)
{
Type pluginType = PluginList[0].GetType();
MethodInfo method = pluginType.GetMethod("Show");
method.Invoke(PluginList[0], null);
}
}
}
The plugin module WpfPlugin3 implements the IPlugin interface:
using System;
using AbstractionLayer;
namespace WpfPlugin3
{
public class WpfPlugin : IPlugin
{
public MainWindow WindowInstance = null;
public void Initialize()
{
WindowInstance = new MainWindow();
}
public void Show()
{
if (WindowInstance != null)
{
WindowInstance.ShowDialog();
}
}
}
}
The plugin's main window is defined as follows:
<Window x:Class="WpfPlugin3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800" WindowStyle="None">
<Grid>
<Button Content="WpfPlugin3" Width="200" Height="200" />
</Grid>
</Window>
To execute this setup, place the WpfPlugin3.exe file inside the Plugins folder under the debug output directory of WPFIPluginDemo.