Динамическое разрешение сборки / управление - PullRequest
14 голосов
/ 27 декабря 2011

Короткая версия

У меня есть приложение, которое использует инфраструктуру плагинов. Плагины имеют настраиваемые свойства, которые помогают им знать, как выполнять свою работу. Плагины сгруппированы в профили, чтобы определить, как выполнить задачу, а профили хранятся в файлах XML, сериализованных DataContractSerializer. Проблема заключается в том, что при чтении файлов конфигурации десериализация приложения должна знать обо всех подключаемых модулях, определенных в файле конфигурации. Я ищу способ справиться с разрешением неизвестных плагинов. См. Раздел предлагаемого решения ниже для нескольких идей, которые я рассмотрел в реализации, но я открыт практически для всего (хотя я бы предпочел не изобретать приложение заново).


Detail

Фон

Я разработал своего рода Систему автоматизации бизнес-процессов для внутреннего использования в компании, в которой я сейчас работаю, в C # 4. Она полностью использует «плагины» для определения всего ( от задач, которые должны быть выполнены, до определения единиц работы) и в значительной степени опирается на модель динамической конфигурации, которая в свою очередь опирается на динамические объекты C # 4 / DLR для выполнения заданий. Это немного тяжело при выполнении из-за его динамического характера, но оно работает последовательно и работает достаточно хорошо для наших нужд.

Он включает пользовательский интерфейс конфигурации WinForms, который широко использует Reflection для определения настраиваемых свойств / полей плагинов, а также свойств / полей, которые определяют каждую единицу работы, подлежащую обработке. Пользовательский интерфейс также построен на основе движка BPA, поэтому он имеет полное представление о (свободной) объектной модели, которая позволяет движку выполнять свою работу, что, по совпадению, привело к нескольким улучшениям пользовательского интерфейса, таким как выполнение специальных заданий и проверка времени ввода пользовательских данных. Опять же, есть возможности для совершенствования, однако, похоже, он делает свою работу.

Пользовательский интерфейс конфигурации использует DataContractSerializer для сериализации / десериализации указанных параметров, поэтому любые подключаемые модули, на которые ссылается конфигурация, должны быть загружены до (или во время) загрузки конфигурации.

Структура

Механизм BPA реализован в виде общей сборки (DLL), на которую ссылаются служба BPA (служба Windows), пользовательский интерфейс конфигурации (приложение WinForms) и тестер плагинов (версия консольного приложения службы Windows). ). Каждое из трех приложений, которые ссылаются на общую сборку, содержит только минимальный объем кода, необходимый для выполнения их конкретной цели. Кроме того, все плагины должны ссылаться на очень тонкую сборку, которая в основном просто определяет интерфейс (ы), который должен реализовывать плагин.

Проблема

Из-за модели расширяемости, используемой в приложении, всегда существовало требование, чтобы пользовательский интерфейс конфигурации запускался из того же каталога (на том же ПК), что и приложение-служба. Таким образом, пользовательский интерфейс всегда знает обо всех сборках, о которых знает Служба, поэтому их можно десериализовать, не сталкиваясь с отсутствующими сборками. Теперь, когда мы приближаемся к развертыванию системы, наши сетевые администраторы в целях безопасности обратились с просьбой разрешить удаленный интерфейс конфигурации на любом ПК в нашей сети. Как правило, это не было бы проблемой, если бы всегда был известный набор сборок для развертывания, однако, с возможностью расширения приложения с помощью сборок, созданных пользователем, должен быть способ разрешения сборок, из которых подключаемые модули может быть создан / использован.

Предлагаемое (потенциально очевидное) решение

Добавьте службу WCF в приложение-службу, чтобы разрешить типичные операции CRUD для конфигураций, о которых знает этот экземпляр службы, и переработать пользовательский интерфейс конфигурации, чтобы он действовал больше как SSMS с моделью Connect / Disconnect.Это на самом деле не решает проблему, поэтому нам также нужно было бы представить какой-то ServiceContract из приложения-службы, чтобы разрешить запрашивать сборки, о которых он знает / имеет доступ.Это нормально и довольно просто, однако возникает вопрос: «Когда пользовательский интерфейс должен узнать о сборках, о которых Служба знает?»При подключении мы могли бы отправлять все сборки из Сервиса в пользовательский интерфейс, чтобы гарантировать, что он всегда знает обо всех сборках, которые делает сервис, но это мешает управлению AppDomain (возможно, излишне) и конфликтам версий сборок.Поэтому я предложил подключиться к событиям AppDomain.AssemblyResolve / AppDomain.TypeResolve, чтобы загружать только сборки, о которых клиент пока не знает, и только по мере необходимости.Это не обязательно устраняет проблемы управления AppDomain, но определенно помогает решить конфликты версий и связанные с этим проблемы.

Вопрос

Если вы застряли со мной так долгоЯ аплодирую и благодарю вас, но теперь я, наконец, подхожу к актуальному вопросу здесь.После нескольких месяцев исследований и, наконец, приходя к выводу, мне интересно, сталкивался ли кто-нибудь здесь с подобной проблемой, и как вы справились с ошибками и недостатками?Есть ли стандартный способ справиться с этим, который я полностью пропустил, или у вас есть какие-либо рекомендации, основанные на том, как вы видели это успешно обработанным в прошлом?Видите ли вы какие-либо проблемы с предлагаемыми подходами или можете предложить альтернативу?

Я знаю, что не все живут в моей голове, поэтому, пожалуйста, дайте мне знать, если вам нужны дополнительные разъяснения / объяснения.Спасибо!

Обновление

Я слегка встряхнул MEF и чувствую, что это слишком упрощенно для моих целей.Дело не в том, что его нельзя было согнуть, чтобы удовлетворить требования плагинов для моего приложения, проблема в том, что это будет слишком громоздким и грязным, чтобы сделать его возможным.Это хорошее предложение, и у него есть большой потенциал, но в его нынешнем состоянии его просто еще нет.

Есть другие идеи или отзывы о моих предложенных решениях?

Обновление

Я не знаю, является ли проблема, с которой я сталкиваюсь, слишком локализованной, если я не смог правильно описать то, что я пытаюсь достичь, или этот вопрос слишком неоправданно длинныйбыть прочитанным полностью;но те немногие ответы, которые я получил, оказались достаточно полезными, чтобы помочь мне по-другому осмыслить проблему и выявить некоторые недостатки в том, к чему я стремлюсь.

Короче, я пытаюсь сделать триприложения, которые в своем текущем состоянии обмениваются информацией (конфигурация / сборки), используя общую структуру каталогов, и пытаются заставить эти приложения работать в сети с минимальным влиянием на удобство использования и архитектуру.

Общий доступ к файлам кажется очевидным ответом на эту проблему (как @SimonMourier предложил в комментариях), но их использование приводит к отсутствию контроля и отладки , когда что-то идет не так.Я вижу их как жизнеспособное краткосрочное решение, но в долгосрочной перспективе они просто кажутся неосуществимыми.

Ответы [ 4 ]

4 голосов
/ 27 декабря 2011

tl; dr, но я на 90% уверен, что вам стоит взглянуть на MEF .
Когда я впервые увидел его, я выглядел как «ааа, еще одна аббревиатура», но вы увидите, что это очень просто, и он встроен в .NET 4. Лучше всего, он даже работает без проблем на моно, и это вопрос меньше, чем час (включая перерыв на кофе) между слушанием об этом и составлением приветств, чтобы привыкнуть к этим функциям. Это действительно так просто.

По сути, вы «экспортируете» что-то в сборку и «импортируете» это в другое (все с помощью простых атрибутов) и выбираете, где искать (например, в каталоге приложений, папке плагинов и т. Д.). ).

Редактировать: что если вы попытаетесь загрузить и загрузить (и, возможно, кэшировать) плагины на лету при загрузке конфигурации?

1 голос
/ 31 декабря 2011

Я думаю, что вы могли бы пропустить относительно простое решение, которое в некоторой степени основано на подходе Microsoft web.config:

В конфигурационном файле есть два раздела:

Раздел 1 содержит достаточно информации о плагине (то есть имя, версия), чтобы вы могли загрузить его в домен приложения.

Раздел 2 содержит информацию, сериализованную плагином.

При загрузке плагина передайте информацию в разделе 2 и дайте плагину десериализовать ее в соответствии со своими потребностями.

0 голосов
/ 31 декабря 2011

Я не уверен, что полностью понимаю проблему, но я думаю, что эта ситуация требует «сериализации с сохранением типов», то есть сериализованный файл содержит достаточно информации о типах для десериализации обратно в исходный граф объектов без каких-либоподсказки вызывающего приложения о том, какие типы задействованы.

Я использовал Json.NET , чтобы сделать это, и я настоятельно рекомендую библиотеку для сохранения типов сериализацииграфы объектов.Похоже, что NetDataContractSerializer также может сделать это из Замечания MSDN

NetDataContractSerializer отличается от DataContractSerializer одним важным способом: NetDataContractSerializer включает информацию о типе CLR в сериализованном XMLтогда как DataContractSerializer - нет.Поэтому NetDataContractSerializer можно использовать только в том случае, если оба конца сериализации и десериализации имеют одинаковые типы CLR.

Я выбрал Json.NET, поскольку он может сериализовать POCO без каких-либо специальных атрибутов или интерфейсов.Как Json.NET, так и NetDataContractSerializer позволяют вам использовать пользовательский SerializationBinder - здесь вы можете поместить любую логику в отношении загрузки сборок, которые еще не могут быть загружены.

К сожалению, изменение схем сериализации можетбудьте «самым решающим» изменением, чтобы предложить, потому что все ваши существующие файлы станут несовместимыми.Возможно, вы сможете написать утилиту преобразования, которая десериализует файл, используя старый метод, и сериализует полученный граф объектов, используя новый метод.

0 голосов
/ 31 декабря 2011

Может быть, вы можете разделить эту проблему на две

  1. администратор позволяет пользователям загружать одну из предопределенных конфигураций (набор библиотек), а MEF помогает вводить необходимые зависимости
  2. каждое действие от пользователя должно проходить через прокси-сервер безопасности, подключаемые модули не могут напрямую вызывать BL. Прокси может соответствовать настраиваемому атрибуту безопасности и разрешенным действиям. т.е.

    [MyRole (Name = new [] {"Security.Action"})] void BlockAccount (string accountId) {} ​​

    [MyRole (Name = new [] {"Manager.Action"})] void CreateAccount (строка userName) {}

    [MyRole (Name = new [] {"Security.View", "Manager.View"})] List <> AcountList (Predicate p) {}

и разрешить группы AD (некоторое абстрактное описание)

  1. corp \ securityOperators = "Security. *" // разрешить вызовы для всех манипуляций с безопасностью
  2. corp \ HQmanager = "Manager.View" // разрешить доступ только для просмотра
  3. corp \ Operator = "Менеджер. *"
...