Придумал я себе такую задачу, как разработать к программе систему плагинов (add-in-ов). Немного поковыряв AddIn которые были предложены начиная с .NET 3.5, понял что они мне не подходят. Во-первых это не удобное расположение каталогов, много лишних. Во-вторых не возможно внедриться в какую нибудь часть процесса и сделать по своему, т.е. слабая расширяемость.
И решил изобрести велосипед...
В основу я поставил изолированность от главного приложения и возможность песочницы. Это все не сложно сделать создав новый AppDomain и сконфигурировав его на InternetZone. Но при разработке я столкнулся с проблемой при загрузке сборки с плагином в домен. Проблема заключалась в ошибке со связями. Для загрузки я использую AppDomain.Load(). Как позже оказалось этот метод мне не подходит. По-этому пришлось сделать не большой костыль, о котором я и расскажу.
Начнем с создания нового проекта и консольного приложения (я назвал его PluginHost) и добавим еще один проект библиотеку классов (назовем её PluginFramework). В библиотеку добавим класс:
И еще один класс, без которого было сложно реализовать необходимую мне логику:
Базовый каркас готов. Не большое отступление чего же я собственно хотел добиться:
1.Еще раз изолированность от основного приложения
2.В любой момент загрузка и выгрузка плагина. Если к примеру плагин упал, мы его просто выгрузим и заново загрузим
Что же у нас в Main:
Для начала я загружая сборку PluginFramework в режиме только для рефлексии и получаю Type нашего базового класса для всех плагинов:
Потом поиск по всем имеющимся папкам
И опять я загружаю сборку в режиме только для рефлексии, но уже с плагином и ищу класс который наследуется от базового класса Plugin. Что бы мы здесь могли загрузить сборку с плагином и сравним с базовым типом нам и пришлось в начале PluginFramework тоже загрузить только для рефлексии.
Теперь можно приступить к созданию нового домена, загрузить в него все необходимое и запустить наш плагин на выполнение
Вот и все!
И напишем тестовый плагин для проверки
Структура каталогов следующая

Ссылка на исходники PluginSystem.zip
P.S. Сборки для рефлексии я загружаю, потому что если просто загрузить сборку в новый домен, мы не сможем пройтись по ней из дочернего домена и найти нужный нам класс. CLR будет пытаться загрузить эту сборку в дочерний домен
И решил изобрести велосипед...
В основу я поставил изолированность от главного приложения и возможность песочницы. Это все не сложно сделать создав новый AppDomain и сконфигурировав его на InternetZone. Но при разработке я столкнулся с проблемой при загрузке сборки с плагином в домен. Проблема заключалась в ошибке со связями. Для загрузки я использую AppDomain.Load(). Как позже оказалось этот метод мне не подходит. По-этому пришлось сделать не большой костыль, о котором я и расскажу.
Начнем с создания нового проекта и консольного приложения (я назвал его PluginHost) и добавим еще один проект библиотеку классов (назовем её PluginFramework). В библиотеку добавим класс:
- public abstract class Plugin: MarshalByRefObject
- {
- public virtual void Initialize()
- {
- }
- }
* This source code was highlighted with Source Code Highlighter.
И еще один класс, без которого было сложно реализовать необходимую мне логику:
- public class AssemblyHelper: MarshalByRefObject
- {
- private AppDomain _currentDomain;
- public AssemblyHelper()
- {
- _currentDomain = AppDomain.CurrentDomain;
- _currentDomain.AssemblyResolve += new ResolveEventHandler(_currentDomain_AssemblyResolve);
- }
-
- Assembly _currentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
- {
- string[] nameSplit = e.Name.Split(',');
- string path = Path.Combine(SearchFolder, nameSplit[0] + ".dll");
- Assembly loadedAssembly;
- try
- {
- loadedAssembly = Assembly.LoadFile(path);
- }
- catch (Exception exc)
- {
- Exception exp = exc;
- throw;
- }
- if (loadedAssembly != null)
- {
- return loadedAssembly;
- }
- else
- {
- return null;
- }
- }
-
- public string SearchFolder { get; set; }
- }
* This source code was highlighted with Source Code Highlighter.
Базовый каркас готов. Не большое отступление чего же я собственно хотел добиться:
1.Еще раз изолированность от основного приложения
2.В любой момент загрузка и выгрузка плагина. Если к примеру плагин упал, мы его просто выгрузим и заново загрузим
Что же у нас в Main:
Для начала я загружая сборку PluginFramework в режиме только для рефлексии и получаю Type нашего базового класса для всех плагинов:
- Assembly frameworkReflect = Assembly.ReflectionOnlyLoadFrom(Path.Combine(Environment.CurrentDirectory, "PluginFramework.dll"));
- Type basePluginType = frameworkReflect.GetType("MyPluginSample.Framework.Plugin");
* This source code was highlighted with Source Code Highlighter.
Потом поиск по всем имеющимся папкам
- string pathPlugins = Path.Combine(Environment.CurrentDirectory, "Plugins");
- DirectoryInfo directoryPlugins = new DirectoryInfo(pathPlugins);
- foreach (var dir in directoryPlugins.GetDirectories())
- {
- FileInfo dllFile = dir.GetFiles(dir.Name + ".dll").First();
* This source code was highlighted with Source Code Highlighter.
И опять я загружаю сборку в режиме только для рефлексии, но уже с плагином и ищу класс который наследуется от базового класса Plugin. Что бы мы здесь могли загрузить сборку с плагином и сравним с базовым типом нам и пришлось в начале PluginFramework тоже загрузить только для рефлексии.
- Assembly reflectionAsm = Assembly.ReflectionOnlyLoadFrom(dllFile.FullName);
- Type typePlugin = reflectionAsm.GetTypes().First(t => t.IsSubclassOf(basePluginType));
* This source code was highlighted with Source Code Highlighter.
Теперь можно приступить к созданию нового домена, загрузить в него все необходимое и запустить наш плагин на выполнение
- AppDomain pluginDomain = AppDomain.CreateDomain(dir.Name + " plugin");
- AssemblyHelper helper = (AssemblyHelper)pluginDomain.CreateInstanceAndUnwrap("PluginFramework", "MyPluginSample.Framework.AssemblyHelper");
- helper.SearchFolder = dir.FullName;
- Plugin plugin = (Plugin)pluginDomain.CreateInstanceAndUnwrap(reflectionAsm.FullName, typePlugin.FullName);
- plugin.Initialize();
* This source code was highlighted with Source Code Highlighter.
Вот и все!
И напишем тестовый плагин для проверки
- public class MyPlugin1: Plugin
- {
- public override void Initialize()
- {
- Console.WriteLine("Executing in Plugin 1. Domain Id: {0}", AppDomain.CurrentDomain.Id);
- }
- }
* This source code was highlighted with Source Code Highlighter.
Структура каталогов следующая

Ссылка на исходники PluginSystem.zip
P.S. Сборки для рефлексии я загружаю, потому что если просто загрузить сборку в новый домен, мы не сможем пройтись по ней из дочернего домена и найти нужный нам класс. CLR будет пытаться загрузить эту сборку в дочерний домен