Пример дополнения MAF

45

Начнем с простого примера размещаемого приложения, которое может загружать дополнения калькулятора. Дополнения могут поддерживать разные вычислительные операции, предоставленные дополнениями.

Необходимо создать решение с шестью проектами библиотек и одним консольным приложением. Проекты примера приложения представлены в таблице. В таблице перечислены сборки, ссылки на которые понадобятся. Из-за ссылок на другие проекты внутри решения понадобится установить свойство Copy Local (Копировать локально) в False, чтобы сборки не копировались. Исключением является консольный проект HostApp, в котором нужна ссылка на проект HostView.

Эта сборка должна быть скопирована, чтобы ее можно было найти из размещаемого приложения. Также нужно будет изменить выходной путь сгенерированных сборок, чтобы сборки копировались в правильные каталоги конвейера:

Проект Ссылки Выходной путь Описание
CalcContract System.AddIn.Contract ..\Pipeline\Contracts\ Эта сборка содержит контракт для взаимодействия с дополнением. Контракт определен интерфейсом.
CalcView System.AddIn ..\Pipeline\AddInViews\ Сборка CalcView содержит абстрактный класс, на который ссылается дополнение. Это сторона контракта, относящаяся к дополнению.
CalcAddIn System.AddInCalcView ..\Pipeline\AddIns\CalcAddIn\ CalcAddIn — проект дополнения, ссылающийся на сборку представления дополнения. Эта сборка содержит реализацию дополнения.
CalcAddInAdapter System.AddIn, System.AddIn.Contract, CalcContract, CalcView ..\Pipeline\AddInSideAdapters\ CalcAddInAdapter соединяет представление дополнения и сборку контракта и отображает контракт на представление дополнения.
HostView Сборка, содержащая абстрактный класс представления хоста, не нуждается в ссылке на любую сборку дополнения, а также не имеет ссылок на другой проект в решении.
HostAdapter System.AddIn, System.AddIn.Contract, HostView, CalcContract ..\Pipeline\HostSideAdapters\ Адаптер хоста отображает представление хоста на контракт. Таким образом, он нуждается в ссылках на оба эти проекта.
HostApp System.AddIn, HostView Размещаемое приложение, активизирующее дополнение.

Контракт дополнения

Начнем с реализации сборки контракта. Сборки контракта содержат интерфейс контракта, определяющий протокол для коммуникаций между хостом и дополнением. В приведенном ниже коде показан контракт, определенный для примера приложения — калькулятора. Приложение определяет контракт с методами GetOperations() и Operate(). Метод GetOperations() возвращает список математических операций, поддерживаемых дополнением калькулятора.

Операция определена интерфейсом IOperationContract, представляющим собой контракт. IOperationContract определяет доступные только для чтения свойства Name и NumberOperands.

Метод Operate() вызывает операцию из дополнения и требует операции, определенной интерфейсом IOperation, и операндов в массиве double.

С таким контрактом возможна поддержка дополнением любых операций, принимающих любое количество операндов double и возвращающих значение double. Атрибут AddInContract используется AddInStore для построения кэша. Атрибут AddInContract помечает класс как интерфейс контракта дополнения.

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

using System.AddIn.Contract;
using System.AddIn.Pipeline;

namespace Wrox.ProCSharp.MAF
{

    [AddInContract]
    public interface ICalculatorContract : IContract
    {
        IListContract GetOperations();
        double Operate(IOperationContract operation, double[] operands);
    }

    public interface IOperationContract : IContract
    {
        string Name { get; }
        int NumberOperands { get; }
    }
}

Представление дополнения

Представление дополнения переопределяет контракт так, как он выглядит для дополнения. Этот контракт определен интерфейсами ICalculatorContract и IOperationContract. Для этого представление дополнения определяет абстрактный класс Calculator и конкретный класс Operation.

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

Абстрактный класс Calculator определяет методы, которые должны быть реализованы дополнениями. В то время как контракт определяет типы параметров и возврата, которые должны передаваться через границы доменов приложений и процессов, это не касается представления дополнения. Здесь можно использовать типы, которые облегчают написание дополнений разработчику. Метод GetOperations() возвращает IList<Operation> вместо IListOperation<IOperationContract>, как вы уже видели в сборке контракта. Атрибут AddInBase идентифицирует класс как представление дополнения для хранения.

using System.AddIn.Pipeline;
using System.Collections.Generic;

namespace Wrox.ProCSharp.MAF
{

   [AddInBase]
   public abstract class Calculator
   {
      public abstract IList GetOperations();
      public abstract double Operate(Operation operation, double[] operand);

   }

   public class Operation
   {
       public string Name { get; set; }
       public int NumberOperands { get; set; }
   }
}

Адаптер дополнения

Адаптер дополнения отображает контракт на представление дополнения. Эта сборка имеет ссылки как на сборку контракта, так и на сборку представления дополнения. Реализация адаптера требует отображения метода IListContract<IOperationContract> GetOperations() на метод представления IList<Operation> GetOperations().

Сборка включает классы OperationViewToContractAddInAdapter и CalculatorViewToContractAddInAdapter. Эти классы реализуют интерфейсы IOperationContract и ICalculatorContract. Методы базового интерфейса IContract могут быть реализованы наследованием от базового класса ContractBase. Этот класс предоставляет реализацию по умолчанию. OperationViewToContractAddInAdapter реализует другие члены интерфейса IOperationContract и просто переадресует вызовы к представлению Operation, присвоенному в конструкторе.

Класс OperationViewToContractAddInAdapter также содержит статические вспомогательные методы ViewToContractAdapter() и ContractToViewAdapter(), отображающие Operation на IOperationContract и наоборот.

using System.AddIn.Pipeline;

namespace Wrox.ProCSharp.MAF
{
   internal class OperationViewToContractAddInAdapter : ContractBase, IOperationContract
   {
      private Operation view;

      public OperationViewToContractAddInAdapter(Operation view)
      {
         this.view = view;

      }

      #region IOperationContract Members

      public string Name
      {
         get { return view.Name; }
      }

      public int NumberOperands
      {
         get { return view.NumberOperands; }
      }

      #endregion

      public static IOperationContract ViewToContractAdapter(Operation view)
      {
         return new OperationViewToContractAddInAdapter(view);
      }

      public static Operation ContractToViewAdapter(IOperationContract contract)
      {
         return (contract as OperationViewToContractAddInAdapter).view;
      }
   }
}

Класс CalculatorViewToContractAddInAdapter очень похож на OperationViewToContractAddInAdapter: он происходит от ContractBase, наследуя реализацию интерфейса IContract по умолчанию, и реализует интерфейс контракта. На этот раз интерфейс ICalculatorContract реализован методами GetOperations() и Operate().

Метод Operate() адаптера вызывает метод Operate() класса представления Calculator, где IOperationContract должен быть преобразован в Operation. Это делается вспомогательным статическим методом ContractToViewAdapter(), определенным в классе OperationViewToContractAddInAdapter.

Реализация метода GetOperations нуждается в преобразовании коллекции IListContract<IOperationContract> в IList<Operation>. Для таких преобразований коллекции класс CollectionAdapters определяет методы преобразования ToIList() и ToIListContract(). Здесь для преобразования используется метод ToIListContract(). Атрибут AddInAdapter идентифицирует класс как адаптер стороны дополнения:

using System.AddIn.Contract;
using System.AddIn.Pipeline;

namespace Wrox.ProCSharp.MAF
{
   [AddInAdapter]
   internal class CalculatorViewToContractAddInAdapter : ContractBase, ICalculatorContract
   {
      private Calculator view;

      public CalculatorViewToContractAddInAdapter(Calculator view)
      {
         this.view = view;
      }


      #region ICalculatorContract Members

      public IListContract<IOperationContract> GetOperations()
      {
         return CollectionAdapters.ToIListContract<Operation, IOperationContract>(view.GetOperations(),
             OperationViewToContractAddInAdapter.ViewToContractAdapter,
             OperationViewToContractAddInAdapter.ContractToViewAdapter);
      }

      public double Operate(IOperationContract operation, double[] operands)
      {
         return view.Operate(OperationViewToContractAddInAdapter.ContractToViewAdapter(operation),
             operands);
      }

      #endregion
   }
}

Теперь дополнение содержит реализацию некоторой функциональности. Дополнение реализовано классом CalculatorV1. Сборка дополнения имеет зависимость от сборки представления дополнения, поскольку это необходимо для реализации абстрактного класса Calculator.

Атрибут AddIn помечает класс как дополнение для хранилища дополнений и добавляет информацию об издателе, версии, а также описание. На стороне хоста эта информация доступна из AddInToken.

CalculatorV1 возвращает список поддерживаемых операций в методе GetOperations(). Метод Operate() вычисляет операнды в зависимости от операции:

using System;
using System.AddIn;
using System.Collections.Generic;

namespace Wrox.ProCSharp.MAF
{
   [AddIn("Simple Calc", Publisher="Wrox Press", Version="1.0.0.0", Description="Sample AddIn")]
   public class CalculatorV1 : Calculator
   {
      private List operations;

      public CalculatorV1()
      {
          operations = new List();
          operations.Add(new Operation() { Name = "+", NumberOperands = 2 });
          operations.Add(new Operation() { Name = "-", NumberOperands = 2 });
          operations.Add(new Operation() { Name = "/", NumberOperands = 2 });
          operations.Add(new Operation() { Name = "*", NumberOperands = 2 });

      }

      public override IList GetOperations()
      {
          return operations;
      }

      public override double Operate(Operation operation, double[] operand)
      {

          switch (operation.Name)
          {
              case "+":
                  return operand[0] + operand[1];
              case "-":
                  return operand[0] - operand[1];
              case "/":
                  return operand[0] / operand[1];
              case "*":
                  return operand[0] * operand[1];
              default:
                  throw new InvalidOperationException(String.Format("invalid operation {0}", operation.Name));
          }
      }
   }
}

Представление хоста

Продолжим рассмотрение и обратимся к представлению хоста на стороне хоста. Подобно представлению дополнения, представление хоста определяет абстрактный класс с методами, подобными контракту. Однако определенные здесь методы вызываются размещаемым приложением.

Оба класса — Calculator и Operation — являются абстрактными, а их члены реализованы адаптером хоста. Классы здесь должны реализовать интерфейс, чтобы их могло использовать размещаемое приложение:

using System.Collections.Generic;
namespace Wrox.ProCSharp.MAF
{

   public abstract class Calculator
   {
       public abstract IList GetOperations();
       public abstract double Operate(Operation operation, params double[] operand);

   }

   public abstract class Operation
   {
       public abstract string Name { get; }
       public abstract int NumberOperands { get; }
   }
}

Адаптер хоста

Сборка адаптера хоста ссылается на представление хоста и контракт для отображения представления контракта. Класс OperationContractToViewHostAdapter реализует члены абстрактного класса Operation. Класс CalculatorContractToViewHostAdapter реализует члены абстрактного класса Calculator.

В OperationContractToViewHostAdapter ссылка на контракт присваивается в конструкторе. Класс адаптера также содержит экземпляр ContractHandle, который добавляет ссылку времени жизни на contract, так что дополнение остается загруженным до тех пор, пока в нем нуждается размещаемое приложение:

using System.AddIn.Pipeline;

namespace Wrox.ProCSharp.MAF
{
   internal class OperationContractToViewHostAdapter : Operation
   {
      // internal IOperationContract contract;

      public IOperationContract Contract { get; private set; }

      private ContractHandle handle;

      public OperationContractToViewHostAdapter(IOperationContract contract)
      {
         this.Contract = contract;
         handle = new ContractHandle(contract);
      }

      public override string Name
      {
         get
         {
            return Contract.Name;
         }
      }

      public override int NumberOperands
      {
         get
         {
            return Contract.NumberOperands;
         }
      }
   }

   internal static class OperationHostAdapters
   {
      internal static IOperationContract ViewToContractAdapter(Operation view)
      {
         return ((OperationContractToViewHostAdapter)view).Contract;
      }

      internal static Operation ContractToViewAdapter(IOperationContract contract)
      {
         return new OperationContractToViewHostAdapter(contract);
      }
   }
}

Класс CalculatorContractToViewHostAdapter реализует методы абстрактного класса представления хоста Calculator и переадресует вызов контракту. Опять-таки, здесь присутствует ContractHandle, который хранит ссылку на контракт, что подобно преобразованиям типа адаптера стороны дополнения. На этот раз преобразование типа осуществляется в другом направлении, чем в случае адаптера дополнения.

Атрибут HostAdapter помечает класс как адаптер, который должен быть установлен в каталоге HostSideAdapters:

using System.Collections.Generic;
using System.AddIn.Pipeline;

namespace Wrox.ProCSharp.MAF
{
   [HostAdapter]
   internal class CalculatorContractToViewHostAdapter : Calculator
   {
      private ICalculatorContract contract;
      private ContractHandle handle;

      public CalculatorContractToViewHostAdapter(ICalculatorContract contract)
      {
          this.contract = contract;
          handle = new ContractHandle(contract);
      }



      public override IList GetOperations()
      {
          return CollectionAdapters.ToIList(
              contract.GetOperations(),
              OperationHostAdapters.ContractToViewAdapter,
              OperationHostAdapters.ViewToContractAdapter);

      }

      public override double Operate(Operation operation, double[] operands)
      {
          return contract.Operate(OperationHostAdapters.ViewToContractAdapter(operation),
              operands);
      }
   }
}

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

Кнопки в нижнем ряду служат для перестройки и обновления хранилища дополнений и для выхода из приложения:

Пользовательский интерфейс хоста калькулятора
Пройди тесты
Лучший чат для C# программистов