Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 11:

Реализация паттерна MVVM с использованием IoC-контейнера, как метод избавления от зависимости между компонентами системы

< Лекция 10 || Лекция 11: 1234 || Лекция 12 >

Реализация INotifyPropertyChanged средствами аспектно-ориентированного программирования

Как было сказано ранее, так как создание экземпляров моделей представления происходит посредством IoC контейнера, возможно расширить логику создания объекта и дополнять его произвольными аспектами.

Данная функциональность недоступна в базовой версии MEF, и требует использования дополнительных библиотек, MefContrib и DynamicProxy (которые помимо перехвата создания объекта реализует некоторые другие полезные функции, выходящие за рамки лекции).

В целях лучшей расширяемости приложения следует объявить перечисление со всеми доступными аспектами (на текущий момент с 1 аспектом):

  1: [Flags]
  2: public enum Aspects
  3: {
  4:     NotifyPropertyChanged = 1,
  5: }

А также необходимо написать небольшой метод-расширение, добавляющий к контейнеру MEF логику перехвата создания объектов. Если в метаданных создаваемого экземпляра найден атрибут AspectMetadata и указан аспект NotifyPropertyChanged, он будет обернут прокси-объектом, реализующим логику INotifyPropertyChanged:

  1: public static class AopExtensions
  2: {
  3:     public const string AspectMetadata = "AspectMetadata";
  4:
  5:     public static InterceptionConfiguration AddAopInterception(
  6: 		   this InterceptionConfiguration interceptionConfiguration)
  7:     {
  8:         return interceptionConfiguration
  9:           .AddInterceptionCriteria(
 10:              new PredicateInterceptionCriteria(
 11:               new PropertyChangedDynamicProxyExportInterceptor(),
 12:               def => def.ExportDefinitions.Any(export =>
 13:                 export.Metadata.ContainsKey(AspectMetadata) &&
 14:                 ((Aspects)export.Metadata[AspectMetadata] &
 15:                 Aspects.NotifyPropertyChanged) != 0)));
 16:     }
 17: }
  1: internal class PropertyChangedDynamicProxyExportInterceptor :
  2:    IExportedValueInterceptor
  3: {
  4:     private static readonly ProxyGenerator Generator =
  5:        new ProxyGenerator();
  6:     private readonly Type[] _additionalInterfaces = new[]
  7:        { typeof(INotifyPropertyChanged) };
  8:
  9:     public object Intercept(object value)
 10:     {
 11:         object proxy = Generator.CreateClassProxy(
 12:            value.GetType(), _additionalInterfaces,
 13:            new[] { new PropertyChangedInterceptor() });
 14:
 15:         Type currentType = value.GetType();
 16:
 17:         do
 18:         {
 19:             FieldInfo[] fields =
 20:                 currentType.GetFields(BindingFlags.Instance |
 21:                     BindingFlags.NonPublic | BindingFlags.Public)
 22:             .Where(field =>
 23:               (field.Attributes & FieldAttributes.InitOnly) == 0)
 24:             .ToArray();
 25:
 26:             fields.Select(field => new { Field = field,
 27:                                  Value = field.GetValue(value) })
 28:                 .ForEach(desc =>
 29:                      desc.Field.SetValue(proxy, desc.Value));
 30:
 31:             currentType = currentType.BaseType;
 32:         } while (currentType != null);
 33:
 34:         return proxy;
 35:     }
 36: }
  1: internal class PropertyChangedInterceptor : IInterceptor
  2: {
  3:     private event PropertyChangedEventHandler PropertyChanged;
  4:
  5:     public void Intercept(IInvocation invocation)
  6:     {
  7:         string methodName = invocation.Method.Name;
  8:
  9:         if (invocation.Method.IsSpecialName)
 10:         {
 11:             if (invocation.Method.DeclaringType ==
 12:                              typeof(INotifyPropertyChanged))
 13:             {
 14:                 if (methodName.StartsWith("add_"))
 15:                 {
 16:                    PropertyChanged+=(PropertyChangedEventHandler)
 17:                       invocation.Arguments[0];
 18:                 }
 19:                 else
 20:                 {
 21:                    PropertyChanged-=(PropertyChangedEventHandler)
 22:                       invocation.Arguments[0];
 23:                 }
 24:
 25:                 return;
 26:             }
 27:
 28:             if (methodName.StartsWith("set_"))
 29:             {
 30:                 PropertyInfo propertyInfo = invocation.Proxy
 31:                  .GetType().GetProperty(methodName.Substring(4));
 32:                 object oldValue = propertyInfo.GetValue(
 33:                    invocation.Proxy,
 34:                    invocation.Arguments.SkipLast(1).ToArray());
 35:
 36:                 invocation.Proceed();
 37:
 38:                 if (oldValue == invocation.Arguments
 39:                                [invocation.Arguments.Length - 1])
 40:                 {
 41:                     return;
 42:                 }
 43:
 44:                 OnPropertyChanged(invocation.Proxy,
 45:                 new PropertyChangedEventArgs(propertyInfo.Name));
 46:
 47:                 return;
 48:             }
 49:         }
 50:
 51:         invocation.Proceed();
 52:     }
 53:
 54:     private void OnPropertyChanged(object sender,
 55:                                      PropertyChangedEventArgs e)
 56:     {
 57:         PropertyChangedEventHandler eventHandler=PropertyChanged;
 58:
 59:         if (eventHandler != null)
 60:         {
 61:             eventHandler(sender, e);
 62:         }
 63:     }
 64: }

Последним шагом является вызов метода-расширения для MEF контекста приложения:

  1: var catalog = new InterceptingCatalog(new DirectoryCatalog("."),
  2:    new InterceptionConfiguration().AddAopInterception());

Теперь любой класс, помеченный атрибутом [ExportMetadata( AopExtensions.AspectMetadata, Aspects.NotifyPropertyChanged)], при разрешении через IoC контейнер, автоматически получит реализацию INotifyPropertyChanged для своих виртуальных свойств.

Краткие итоги

В данной теме были рассмотрена архитектура композитного MVVM приложения, в котором соответствие между моделями представления и представлениями осуществляется при помощи IoC контейнера MEF. Также описано расширение контейнера, позволяющее автоматически реализовывать интерфейс INotifyPropertyChanged на всех моделях представления, помеченных атрибутом. Может показаться, что используемые приемы достаточно перегружены для реализации небольшого проекта, но необходимо осознавать, что при расширении проекта выстроенная архитектура принесет свои плоды и позволит сопровождать проект с минимальными усилиями.

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

Вопросы:

  1. Инверсия зависимости. Активная и пассивная.
  2. Назначение IoC контейнера
  3. Библиотека MEF
  4. Построение композитного MVVM приложения с применением IoC контейнера MEF
< Лекция 10 || Лекция 11: 1234 || Лекция 12 >