24 Обзор возможностей Visual Studio 2013. Aspect.NET

Аспектно-ориентированное программирование (АОП) - новый перспективный подход к разработке и модернизации программ, предназначенный для поддержки сквозной функциональности (cross-cutting concerns) в программах. Под сквозной функциональностью понимается функциональность, реализация которой представляет собой совокупность рассредоточенных фрагментов кода, реализующих эту общую функциональность, и которую полностью, по принципиальным соображениям, нельзя собрать в один традиционный модуль (или группу модулей). Примеры - проверки безопасности, трассировка, управление ошибочными ситуациями и др. Все они объединяются под одним общим названием надежные и безопасные вычисления (trustworthy computing).



Система Aspect.NET осуществляет внедрение аспектов в целевую сборку на уровне бинарного кода MSIL, без модификации исходного кода целевого приложения.

Библиотека Enterprise Library Integration Pack for Windows Azure - решение компании Microsoft для выделения "сквозной функциональности" при разработке облачных приложений (однако оно требует для этого модификации их исходного кода).

Библиотека Microsoft Enterprise Library представляет собой набор наиболее удачных образцов решения стандартных проблем и также предназначена для реализации "сквозной функциональности". В нее входят следующие функциональные блоки:
  • Caching Application Block для поддержки кэширования;
  • Cryptography Application Block для поддержки шифрования;
  • Data Access Application Block для поддержки работы с базами данных;
  • Exception Handling Application Block для реализации стратегий обработки исключений;
  • Logging Application Block для поддержки протоколирования;
  • Security Application Block для поддержки авторизации и безопасности приложений;
  • Validation Application Block для поддержки механизмов валидации данных бизнес-объектов;

В 2011 году компания Microsoft выпустила расширение Enterprise Library Integration Pack for Windows Azure, которое содержит функциональные блоки для управления производительностью (Autoscaling Application Block) и ограничения функциональности под нагрузкой (Transient Fault Handling Block). Программист может реализовать ту или иную "сквозную функциональность", если в исходном коде своего проекта вызовет методы из набора классов соответствующего функционального блока. С точки зрения АОП очевидно, что местоположение этих вызовов в исходном коде целевого приложения является совокупностью точек внедрения для действий соответствующего аспекта.

Чтобы облегчить внедрение (weaving) функциональных блоков в целевое приложение, компания Microsoft представила Unity Application Block - IOC-контейнер с ограниченной поддержкой АОП. Его механизмы позволяют во время исполнения целевой программы перехватывать вызовы заданных методов и передавать управление цепочке методов - "перехватчиков" (interceptors). Цепочка "перехватчиков" может вызываться как перед вызовом целевого метода, так и после него, имея возможность обратится к результату выполнения данного целевого метода.

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

Проект Aspect.NET, разрабатываемый коллективом авторов с 2004 г. в СПбГУ, также представляет собой аспектно-ориентированную среду разработки программ для платформы Microsoft.NET. Аспекты определяются на метаязыке Aspect.NET ML, либо с помощью пользовательских атрибутов в отдельных проектах MS Visual Studio, а их слияние с целевым кодом происходит на уровне сборок статически, т.е. после этапа компиляции. Вначале компилируется сборка с аспектами из которой компоновщик аспектов (weaver) извлекает правила внедрения каждого действия аспекта, содержащиеся в его пользовательском атрибуте AspectAction(). Затем компоновщик анализирует MSIL-код откомпилированной сборки целевого проекта, находит соответствующие места внедрения (сканирование) и вставляет туда действия из аспектной сборки. Операции сканирования и внедрения действий аспектов разделены, что позволяет пользователю просматривать и фильтровать точки внедрения. Весь анализ и модификация .NET - сборок производится с помощью сервисов рефлексии (reflection) и библиотеки Microsoft Phoenix, которая декомпилирует сборку и представляет ее в виде набора высокоуровневых инструкций.

Любое действие можно вставлять перед (ключевое слово %before), после (%after) или вместо (%instead) вызова заданного целевого метода. Название целевого метода задается с помощью регулярных выражений относительно его сигнатуры. Местонахождение точки внедрения внутри конкретного метода или класса можно фильтровать по ключевому слову %within.

Внутри действий можно использовать свойства базового класса Aspect, предоставляющие доступ к контексту точки внедрения, например, "this" и "target" объектам целевого метода, его метаданным (типа MethodInfo), отладочной информации, результату выполнения и пр. Причем компоновщик вставляет в итоговую сборку MSIL-код для передачи контекста только в том случае, если действие аспекта использует его. Аргументы целевого метода передаются через аргументы действия. Таким образом, аспект - это класс, производный от служебного базового класса Aspect, а его действиями будут открытые статические методы, помеченные атрибутом AspectAction(). В случае, когда определение аспекта задается на Aspect.NET ML, специальный конвертер переводит его в определение с пользовательскими атрибутами.

Пример 1

Рассмотрим упражнение "Hands-on Lab 1: Using the Logging Application Block with Windows Azure Storage", где путем добавления ссылок на сборки EL производится подключение функционального блока логгирования к исходному проекту, а затем вызов его метода для передачи сообщения в облачное хранилище диагностической информации WAD (листинг 17.1). Это дает возможность настраивать параметры сбора и хранения отладочных сообщений через графический интерфейс Logging Application Block, либо через его конфигурационные файлы.

//Веб-роль, на странице которой тестируется Logging Application Block
public partial class Default : System.Web.UI.Page {
//Сообщение отсылается в обработчике щелчка мыши по кнопке страницы
protected void LogButton_Click(object sender, EventArgs e) {
Microsoft.Practices.EnterpriseLibrary.Logging.
     Logger.Write("Message from the Logging Application Block");
     }
}

Итак, наша задача заключается в том, чтобы перенести все зависимости от EL и вызовы методов протоколирования в отдельный проект с аспектом. Применив затем с помощью Aspect.NET данный аспект к исходному проекту, мы получим его бесшовную интеграцию с Logging Application Block.

Традиционно, в Aspect.NET подобные задачи решаются размещением кода протоколирования в действии аспекта и вставкой его перед, после, или вместо вызова целевого метода в исходном проекте. В нашем случае, целевой метод - это обработчик события щелчка мыши LogButton_Click() класса веб-страницы Default, причем созданием объекта этого класса и отправкой ему событий занимается среда ASP.NET и сервер IIS. Это означает, что код вызова нашего целевого метода располагается вне сборки исходного проекта и недоступен Aspect.NET.

По мнению авторов, перехват вызовов методов, которые реагируют на внешние события, может быть осуществлен через наследование классов. Если в аспектном проекте создать класс, который наследует от целевого класса, а затем подменить им свой базовый класс в сборке исходного проекта, то требуемый перехват можно осуществить в переопределенном виртуальном методе. Специальный пользовательский атрибут [ReplaceBaseClass] предписывает компоновщику Aspect.NET заменить целевой класс своим аспектным наследником:
  1. Заменить в исходной сборке все вызовы методов базового целевого класса (в том числе и конструкторы) на вызовы методов его наследника в аспектной сборке.
  2. Принудительно объявить виртуальными те методы целевого класса, которые переопределены в замещающем его наследнике. Если они закрыты (private), то сделать их защищенными (protected).
  3. Если вызов этих методов в исходной сборке производится с помощью MSIL-инструкции call или ldftn, заменить их на callvirt и ldvirtftn соответственно.
  4. Объединить с помощью инструмента ILRepack (из проекта Mono.Cecil) сборки с аспектом и исходную.
  5. Присвоить какое-нибудь служебное имя базовому целевому классу, а его первоначальное имя - замещающему наследнику из аспекта.
Преимуществами такого алгоритма является простота подмены классов для пользователя, а также использование только штатного синтаксиса языка .NET. Теперь с помощью аспекта можно: уточнять поведение любого метода целевого класса, реализовывать в нем дополнительные интерфейсы, накладывать различные пользовательские атрибуты и т.п.

//Проект с замещающим аспектным наследником
[AspectDotNet.ReplaceBaseClass]
public class AspectClass : Default {
protected void LogButton_Click(object sender, EventArgs e) {           
Microsoft.Practices.EnterpriseLibrary.Logging.
     Logger.Write("Message from the Logging Application Block");
            base.LogButton_Click(sender, e);
      }
}
//Исходный проект, после отделения зависимости от Logging Application Block
public partial class Default : System.Web.UI.Page {
protected void LogButton_Click(object sender, EventArgs e) {}
}

Пример 2

Рассмотрим теперь "Hands-on Lab 6: Implementing Throttling Behavior", где иллюстрируется ограничение функциональности под нагрузкой с использованием сервисов функционального блока Autoscaling Application Block. Отдельный компонент Autoscaler занимается мониторингом диагностической информации и, в зависимости от текущей нагрузки на облако, устанавливает свойство ThrottlingMode в файле конфигурации исходного проекта. В зависимости от значения этого свойства какие-то из методов класса веб-страницы могут изменять свое поведение.

//Веб-роль, на странице которой тестируется Autoscaling Application Block
public partial class Default : System.Web.UI.Page {
protected override void OnPreRenderComplete(EventArgs e) {
            base.OnPreRenderComplete(e);
            string throttlingMode = 
                RoleEnvironment.GetConfigurationSettingValue("ThrottlingMode");
            switch (throttlingMode)
            {
                case "HighActivity":
                    this.ThrottlingLabel.Text = "Работа при высокой активности…";
                    break;
                default:
                    this.ThrottlingLabel.Text = "Работа при обычной  активности…";
 this.DoSomeUsualWork();
                    break;
            }
      }

 private void DoSomeUsualWork() {/*…*/}
}
 

Данный метод можно перенести в аспект и тогда целевой класс будет сконцентрирован только на решении своей задачи, в то время как компонент Autoscaler и бесшовная интеграция с аспектом обеспечит реакцию на повышенную нагрузку. Задачу можно было бы решить аналогично предыдущему примеру, но здесь есть препятствие в виде вызова закрытого в целевом классе метода DoSomeUsualWork(). Для того, чтобы он стал доступным замещающему наследнику, компоновщик аспектов мог бы принудительно сделать этот метод защищенным. Однако это нарушит инкапсуляцию целевого класса, и единственный способ сохранить ее - использовать рефлексию .NET. Закрытые члены целевого класса становятся полями его аспектного наследника, которые инициализируются в конструкторе. Также предположим, что в замещающем наследнике целевого класса нам понадобится вызвать метод OnPreRenderComplete следующего по иерархии базового класса System.Web.UI.Page. Защищенные и открытые методы целевого класса используются в его замещающем аспектном наследнике без ограничений. Итоговый аспект:

using System.Reflection;
[AspectDotNet.ReplaceBaseClass]
public class AspectClass : _Default {
MethodInfo DoSomeUsualWork;

      public AspectClass() {
      Type BaseType = this.GetType().BaseType;
//Получение ссылки на закрытый метод целевого класса _Default
            DoSomeUsualWork = BaseType.GetMethod("DoSomeUsualWork", 
BindingFlags.NonPublic | BindingFlags.Instance);
//Ссылка на метод базового класса System.Web.UI.Page
PageOnPreRenderComplete = base.GetType().BaseType.
GetMethod("OnPreRenderComplete", BindingFlags.NonPublic | BindingFlags.Instance);
      }
        
      protected override void OnPreRenderComplete(EventArgs e) {
  //Вызываем метод базового класса System.Web.UI.Page
        PageOnPreRenderComplete.Invoke(this, new object[] { e });
        string throttlingMode = 
                   RoleEnvironment.GetConfigurationSettingValue("ThrottlingMode");
              switch (throttlingMode) {
                case "HighActivity":
 //Использование в аспекте члена целевого класса _Default
                    this.ThrottlingLabel.Text = "Работа при высокой    активности…";
                    break;
                default:
                    this.ThrottlingLabel.Text = "Работа при обычной активности…";
   //Вызов закрытого члена целевого класса _Default
  DoSomeUsualWork.Invoke(this, null);
                    break;
         }
      }
}

Использование компоновщика аспектов для подмены целевой сборки на сборку с внедренными аспектами перед публикацией в облаке

Новая версия системы Aspect.NET, совместимая с новейшей интегрированной средой MS Vusial Studio 2013, доступна в настоящее время в виде компоновщика аспектов (который реализован как консольное приложение) и набора скриптов для управления процессом сборки решений, таким образом, чтобы обеспечить подмену исходной бинарной сборки на еее новую версию с внедненными аспектами перед публикацией в облаке. В настоящее время версия Aspect.NET Framework для VS 2013 (т.е. графический пользовательский интерфейс) находится в процессе разработки.

Вызов компоновщика в консольном варианте имеет вид:

weaver -aspects MyAspect.dll -in HelloWorld.exe -out AspectHelloWorld.exe
 
Рассмотрим алгоритм разработки и бесшовной интеграции аспектов в MS VS 2012 с помощью Aspect.NET:

Вход: Проект с исходными текстами целевого приложения, к которому нужно применить аспекты.


  • Создаем отдельный проект для аспекта (типа Class Library) и объединяем его с целевым проектом в рамках общего решения (solution).
  • Чтобы обеспечить интеграцию аспекта с целевой сборкой, в свойствах его проекта на вкладке Build Events добавляем соответствующий скрипт

:: Укажем папку со сборкой целевого проекта
set TargetAssemblyDir=C:\HelloWorld\HelloWorld\bin\Debug\
:: Название его сборки
set TargetAssembly=HelloWorld
:: Ее расширение
set TargetAssemblyExt=.exe
:: Для каждого нового аспекта или целевого проекта необходимо менять лишь 
:: вышеуказанные переменные
  
:: Зададим путь к директории Aspect.NET
set AspectDotNetDir=C:\AspectDotNet
set TargetAssemblyPath=%TargetAssemblyDir%%TargetAssembly%%TargetAssemblyExt%
set TargetAssemblyName=%TargetAssembly%%TargetAssemblyExt% 
cd %AspectDotNetDir%
weaver -aspects $(TargetPath) -in %TargetAssemblyPath% -out  %TargetAssemblyName%
:: Подмена сборки в целевом проекте результирующей
move /Y %TargetAssemblyName% %TargetAssemblyPath%

Набор для практики

Вопросы

  • Что такое АОП?
  • Что такое Aspect.NET?
  • Каковы методы применения АОП и Aspect.NET для облачных вычислений?
  • Что такое Microsoft Enterprise Library Integration Pack for Microsoft Azure?

Упражнения

  • Разработайте и опубликуйте с использованием Visual Studio свое облачное Web-приложение.Затем, применив систему Aspect.NET (http://www.aspectdotnet.org), разработайте аспект, модифицирующий поведение приложений при загрузке страницы в браузер и других типовых действиях. Опубликуйте в облаке модифицированное приложение и проверьте его в работе.
  • Изучите библиотеку Microsoft Enterprise Library Integration Pack for Microsoft Azure и попробуйте применить новую версию Aspect.NET для пропуска примеров, описанных в лекции.

Темы для курсовых работ, рефератов, эссе

  • Применение АОП для облачных вычислений (реферат).
  • Библиотека Microsoft Enterprise Library Integration Pack for Microsoft Azure (реферат).