Динамическое разрешение/управление
Краткая версия
У меня есть приложение, которое использует подключаемую инфраструктуру. Плагины имеют настраиваемые свойства, которые помогают им знать, как выполнять свою работу. Плагины сгруппированы в профили, чтобы определить, как выполнить задачу, а профили хранятся в файлах XML, сериализованных DataContractSerializer. Проблема в том, что при чтении конфигурационных файлов десериализация приложения должна знать все плагины, определенные в файле конфигурации. Я ищу способ справиться с разрешением неизвестных плагинов. См. Предлагаемый раздел решения ниже для нескольких идей, которые я изучил в реализации, но я открыт для всего, что угодно (хотя я бы предпочел не изобретать приложение).
Detail
Фон
Я разработал своего рода систему автоматизации бизнес-процессов для внутреннего использования для компании, в которой я сейчас работаю, в С# 4. Он использует исчерпывающее использование "плагинов" для определения всего (от задач, которые выполняются с определением единиц работы) и в значительной степени зависит от модели динамической конфигурации, которая, в свою очередь, полагается на динамические объекты С# 4/DLR для выполнения заданий. Он немного тяжелый, выполняя из-за своей динамической природы, но он работает последовательно и достаточно хорошо работает для наших нужд.
Он включает пользовательский интерфейс конфигурации WinForms, который широко использует Reflection для определения настраиваемых свойств/полей подключаемых модулей, а также свойств/полей, которые определяют каждую обрабатываемую единицу работы. Пользовательский интерфейс также построен на основе BPA-механизма, поэтому он полностью понимает (свободную) объектную модель, которая позволяет двигателю выполнять свою работу, что, по совпадению, привело к нескольким улучшениям в работе пользователя, например, ad-hoc выполнение заданий и проверка времени входа пользователя. Опять же есть место для улучшения, однако, похоже, он выполняет свою работу.
Пользовательский интерфейс конфигурации использует DataContractSerializer для сериализации/десериализации заданных параметров, поэтому любые подключаемые модули, на которые ссылается конфигурация, должны быть загружены до (или в момент) загрузки конфигурации.
Структура
Механизм BPA реализован как общая сборка (DLL), на которую ссылаются служба BPA (служба Windows), интерфейс конфигурации (приложение WinForms) и тестер подключаемого модуля (версия приложения консоли Windows Service). Каждое из трех приложений, которые ссылаются на общую сборку, включает только минимальный объем кода, необходимый для выполнения их конкретной цели. Кроме того, все плагины должны ссылаться на очень тонкую сборку, которая в основном просто определяет интерфейсы (интерфейсы), которые должен реализовать плагин.
Проблема
Из-за модели расширяемости, используемой в приложении, всегда существовало требование, чтобы пользовательский интерфейс конфигурации запускался из одного и того же каталога (на том же ПК) в качестве приложения-службы. Таким образом, пользовательский интерфейс всегда знает обо всех сборках, о которых знает Служба, поэтому их можно десериализовать без использования сборок. Теперь, когда мы приближаемся к развертыванию системы, требование удаленного доступа к конфигурационному интерфейсу на любом ПК в нашей сети появилось у наших сетевых администраторов в целях безопасности. Как правило, это не было бы проблемой, если бы всегда существовал известный набор сборок для развертывания, но с возможностью расширения приложения с использованием сборных сборок, должен быть способ разрешить сборки, из которых подключаемые модули может быть инстанцирован/использован.
Предлагаемое (потенциально очевидное) решение
Добавьте службу WCF в приложение "Служба", чтобы разрешить типичные операции CRUD с конфигурациями, которые этот экземпляр службы знает и перерабатывает интерфейс конфигурации, чтобы действовать больше как SSMS с моделью Connect/Disconnect. Это на самом деле не решает проблему, поэтому нам также нужно выставить какой-то ServiceContract из приложения "Сервис", чтобы разрешить запрос сборок, которые он знает о/имеет доступ. Это прекрасно и справедливо, но возникает вопрос: "Когда должен пользовательский интерфейс узнавать о собраниях, о которых служба знает?" При подключении мы могли бы отправить все сборки из Сервиса в пользовательский интерфейс, чтобы гарантировать, что он всегда знает обо всех собраниях, которые выполняет служба, но которые становятся беспорядочными с управлением AppDomain (потенциально неоправданно) и конфликтами версии сборки. Поэтому я предложил подключиться к событиям AppDomain.AssemblyResolve/AppDomain.TypeResolve, чтобы загружать только те сборки, о которых клиент еще не знает, и только по мере необходимости. Это не обязательно устраняет проблемы управления AppDomain, но это определенно помогает устранять конфликты версий и связанные с ними проблемы.
Вопрос
Если вы застряли со мной так долго, я приветствую и благодарю вас, но теперь я, наконец, добираюсь до настоящего вопроса. После нескольких месяцев исследований и, наконец, пришел к выводу, что мне интересно, нужно ли кому-либо здесь заниматься подобной проблемой и как вы справлялись с подводными камнями и недостатками? Есть ли стандартный способ справиться с этим, который я пропустил полностью, или у вас есть какие-либо рекомендации, основанные на том, как вы видели, как это успешно обрабатывалось в прошлом? Вы видите какие-либо проблемы с предлагаемыми подходами или можете предложить альтернативу?
Я знаю, что не все живут у меня в голове, поэтому, пожалуйста, дайте мне знать, если вам нужны дополнительные разъяснения/объяснения. Спасибо!
Обновление
Я дал MEF справедливое потрясение и почувствовал, что он слишком упрощен для моих целей. Это не значит, что он не может быть склонен обрабатывать требования к подключаемому модулю моего приложения, проблема в том, что это слишком громоздко и грязно, чтобы сделать это возможным. Это хорошее предложение, и у него много возможностей, но в его нынешнем состоянии его еще нет.
Любые другие идеи или отзывы о моих предлагаемых решениях?
Обновление
Я не знаю, слишком ли локализована проблема, с которой я сталкиваюсь, если я не смог правильно описать то, что я пытаюсь достичь, или если этот вопрос слишком необоснованно долго, чтобы его можно было прочитать целиком; но несколько ответов, которые я получил, были достаточно полезными, чтобы помочь мне разобраться в проблеме по-разному и определить некоторые недостатки в том, что я хочу.
Короче говоря, я пытаюсь сделать три приложения, которые в своем текущем состоянии обмениваются информацией (конфигурацией/сборками) с использованием общей структуры каталогов и пытаются заставить эти приложения работать в сети с минимальным воздействием на удобство использования и архитектуры.
Общий доступ к файлам выглядит как очевидный ответ на эту проблему (как @SimonMourier, предложенный в комментариях), но использование их приводит к отсутствию контроля и отладки , когда что-то идет не так. Я вижу их как жизнеспособное краткосрочное решение, но в долгосрочной перспективе они просто не кажутся выполнимыми.
Ответы
Ответ 1
tl; dr, но я на 90% уверен, что вы должны взглянуть на MEF.
Когда я впервые увидел это, я был похож на "aah, еще один аббревиатур", но вы увидите, что это очень просто, и он встроен в .NET 4. Лучше всего, он даже работает без проблем на моно, и это вопрос меньше, чем час (в том числе кофе-брейк) между прослушиванием и компиляцией миров привет, чтобы привыкнуть к функциям. Это действительно так просто.
В принципе, вы "экспортируете" что-то в сборку и "импортируете" ее в другую (все через простые атрибуты атрибутов), и вы выбираете, где ее искать (например, в каталоге приложений, папках плагинов и т.д.).
Изменить: Что делать, если вы пытаетесь загружать и загружать (и, возможно, кешировать) плагины "на лету" при загрузке конфигурации?
Ответ 2
Я думаю, что вы могли бы игнорировать относительно простое решение, которое несколько вытекает из подхода Microsoft web.config:
В файле конфигурации есть два раздела:
В разделе 1 содержится достаточно информации о плагине (например, имя, версия), чтобы вы могли загрузить его в домен приложения.
Раздел 2 содержит информацию, сериализованную плагином.
При загрузке плагина передайте информацию в разделе 2 и пусть плагин десериализует его в соответствии с его потребностями.
Ответ 3
Возможно, вы можете разделить эту проблему на две
-
Администратор
- позволяет пользователям загружать одну из предопределенных конфигураций (набор библиотек), а MEF помогает вносить требуемые зависимости
-
каждое действие от пользователя должно проходить через прокси-сервер безопасности, модули плагинов не допускаются напрямую к BL. Прокси может соответствовать пользовательскому атрибуту безопасности и разрешенным действиям.
то есть.
[MyRole (Name = new [] { "Security.Action" })]
void BlockAccount (строка accountId) {}
[MyRole (Name = new [] { "Manager.Action" })]
void CreateAccount (string userName) {}
[MyRole (Name = new [] { "Security.View", "Manager.View" })]
Список < > AcountList (Predicate p) {}
и разрешить группы AD (некоторое абстрактное описание)
- corp\securityOperators = "Безопасность. *" // разрешать вызовы всех манипуляций с безопасностью
- corp\HQmanager = "Manager.View" // разрешить доступ только к просмотру
- corp\Operator = "Менеджер. *"
Ответ 4
Я не уверен, что полностью понимаю проблему, но я думаю, что эта ситуация требует "сериализации, сохраняющей тип", то есть сериализованный файл содержит достаточно информации о типе, чтобы десериализоваться обратно на исходный граф объектов без каких-либо намеков вызывающего приложения относительно того, какие типы задействованы.
Я использовал Json.NET, чтобы это сделать, и я могу настоятельно рекомендовать библиотеку для сериализации объектных графов, сохраняющих тип. Похоже, что NetDataContractSerializer также может это сделать, от Замечания MSDN
NetDataContractSerializer отличается от DataContractSerializer одним важным способом: NetDataContractSerializer включает информацию типа CLR в сериализованном XML, тогда как DataContractSerializer этого не делает. Поэтому NetDataContractSerializer может использоваться только в том случае, если оба сериализационных и десериализационных конца имеют одни и те же типы CLR.
Я выбрал Json.NET, потому что он может сериализовать POCO без каких-либо специальных атрибутов или интерфейсов. И Json.NET, и NetDataContractSerializer позволяют вам использовать пользовательский SerializationBinder - здесь вы можете поставить любую логику относительно загрузки сборок, которые еще могут загрузиться.
К сожалению, изменение схем сериализации может быть изменением "ломающего", потому что все ваши существующие файлы станут несовместимыми. Возможно, вы сможете написать утилиту преобразования, которая десериализует файл с использованием старого метода и сериализует полученный граф объекта с помощью нового метода.