Аспектно-ориентированное программирование (AOP) или почему мы отказались от использования самого маститого вендора AOP — PostSharp. Часть 4

Внутреннее устройство плагинов Fody

Lisa Botty
ITA Labs
4 min readSep 11, 2018

--

В предыдущей части статьи мы рассмотрели возможности Fody и его плагина MethodDecorator. И сделали вывод о том, что их возможностей не хватает для того, чтобы покрыть все возможности, предоставляемые PostSharp.

Поэтому для полноценной замены нам пришлось вносить изменения в плагин Fody: MethodDecorator. Этот проект является open source и имеет MIT-лицензию, позволяющую использование модифицированных исходников в коммерческих проектах.

Прежде чем описать сделанные нами модификации, мы кратко обсудим архитектуру плагинов Fody, чтобы иметь общее представление о том, как эти модификации реализовывать.

На рисунке ниже продемонстрирована общая структура зависимостей Fody. Плагины используют базовые классы, определенные в библиотеке Fody. В свою очередь эти классы дают возможность модификации обрабатываемых сборок, предоставляя доступ к объектной модели сборки с помощью библиотеки Mono.Cecil.

В общих чертах, при обработке сборки Fody:

1. Загружает объектную модель обрабатываемой сборки, используя для этого библиотеку Mono.Cecil.

2. Загружает свои плагины согласно конфигурационному файлу FodyWeavers.xml.

3. Передает каждому плагину объектную модель Mono.Cecil для обработки.

4. Сохраняет модифицированную версию обрабатываемой сборки.

Стоит отметить, что если модифицируемая сборка подписана, то после таких модификаций, её необходимо заново подписать. Fody берёт на себя эту задачу и автоматически подписывает обработанные сборки, если в соответствующем проекте стоит настройка SignAssembly и указан корректный путь до ключа, которым подписывается сборка.

Теперь подробнее рассмотрим, что представляют собой плагины Fody.

Устройство плагинов Fody

Каждый плагин Fody — это отдельная .NET сборка, обязательно содержащая класс с названием ModuleWeaver в любом пространстве имен.

В процессе сборки Fody ищет сборки плагинов в соответствии с содержимым файла FodyWeavers.xml, формат которого был описан в предыдущей части статьи. При этом, если плагинов несколько, то модифицируемая сборка будет обрабатываться каждым плагином последовательно.

Класс ModuleWeaver должен наследоваться от класса BaseModuleWeaver, определенной в сборке FodyHelpers, которую нужно добавить как референс в проект плагина. Сделать это можно используя соответствующий NuGet-пакет: https://www.nuget.org/packages/FodyHelpers.

В самом классе ModuleWeaver достаточно переопределить два метода: Execute и GetAssembliesForScanning.

Метод Execute должен содержать реализацию логики плагина.

Метод GetAssembliesForScanning должен возвращать названия сборок, содержащих типы, которые используются в генерируемом плагином коде. То есть, если в коде нам необходимо сгенерировать поле или переменную с типом System.Object, то это метод должен возвращать сборку mscorlib, где этот тип содержится.

Простейший пример реализации ModuleWeaver выглядит так:

Данный код генерирует пустой класс внутри сборки, которая будет обработана этим плагином. Как видно, для этого используется некое свойство ModuleDefinition, определенное в классе BaseModuleWeaver, и тип TypeDefinition. Все эти классы представлены библиотекой Mono.Cecil, используемой плагинами Fody для генерации кода. Подробнее о её возможностях мы напишем ниже.

Официальной документации по тому, как писать плагины для Fody на данный момент не существует, но в репозитории Fody есть проект, содержащий пример проекта плагина: https://github.com/Fody/BasicFodyAddin.

Mono.Cecil

Mono.Cecil — это библиотека с открытым исходным кодом, реализующая возможность модификации кода существующих .NET сборок.

Эта библиотека предоставляет достаточно интуитивную объектную модель сборки. Диаграмма классов этой модели изображена на рисунке ниже.

Здесь,

· AssemblyDefinition — метаданные сборки,

· ModuleDefinition — метаданные модуля сборки. Каждая сборка может содержать один или более модуль.

· TypeDefinition — метаданные типов сборки: классов, перечислений и т.п.

· FieldDefinition — метаданные полей внутри типов сборки.

· PropertyDefinition — метаданные свойств.

· MethodDefinition — метаданные методов.

Также мы можем работать с отдельными методами на более низком уровне, добавляя в них IL-инструкции с помощью вспомогательного класса CilWorker.

Fody предоставляет доступ к классу ModuleDefinition внутри класса ModuleWeaver, который соответствует главному модулю обрабатываемой сборки. Используя объектную модель Mono.Cecil потенциально мы можем вносить любые изменения в обрабатываемую сборки, например:

· Добавлять новые классы и типы;

· Генерировать новые методы;

· Модифицировать существующие методы, вплоть до полной замены их кода;

· И много других возможностей.

Заключение

Итак, мы вкратце рассмотрели, что представляют собой плагины Fody.

В общих чертах, для того, чтобы внести изменения в плагин MethodDecorator, нам необходимо найти класс ModuleWeaver внутри проекта плагина и внести необходимые нам изменения.

В этом классе Fody предоставляет нам доступ к объектной модели обрабатываемой сборки, которую мы можем модифицировать по своему усмотрению, используя все возможности библиотеки Mono.Cecil.

В следующей части статьи мы подробно рассмотрим все модификации, сделанные нами в плагине MethodDecorator.

--

--