niedziela, 4 marca 2012

[MAF 02] Przykład prostej aplikacji z obsługą wtyczek z wykorzystaniem Managed Add-in Framework (System.AddIn) [PL]

W poprzednim wpisie przybliżyłem czytelnikom teorię związaną z MAF-em (patrz: "[MAF 01] Rzut okiem na Managed Aadd-in Framework (System.AddIn)"), w tym wpisie chciałbym przedstawić prosty przykład aplikacji z obsługą wtyczek z wykorzystaniem Managed Aadd-in Framework (System.AddIn).

Niniejszy przykład jest bardzo prostą aplikacją, która odnajduje wtyczki, a następnie z nich korzysta. Wszystkie zagadnienia będą maksymalnie uproszczone, mimo wszystko konieczne jest przygotowanie całego pipeline'u komunikacyjnego, co wiąże się z przygotowaniem 7 projektów w ramach solution w Visual Studio:
Przeglądanie kodu źródłowego rozpocznijmy od kontraktu:
  [AddInContract]
  public interface IDoSthContract: IContract
  {
    string DoSth();
  }
Jak widać jest on bardzo proty, zawiera jedną funkcję (DoSth), która zwraca jakiś łańcuch tekstowy. Do ważnych elementów należy zaliczyć konieczność oznaczenia kontraktu atrybutem AddInContract oraz dziedziczenie interfejsu stanowiącego kontrakt po interfejsie Icontract.
Do kontraktu bardzo podobne są interfejsy stanowiące widoki aplikacji host'a i wtyczek (add-in'ów).
  public interface IDoSthAppView
  {
    string DoSth();
  }

  [AddInBase]
  public interface IDoSthAddInView
  {
    string DoSth();
  }
Istotnym elementem jest oznaczenie add-in view atrybutem AddInBase, poza tym nie ma jakiś szczególnych wymagań do ich konstrukcji. Zauważmy, że nie są one (pod względem składni C#) powiązane z interfejsem w kontrakcie, a są one tylko do niego podobne.
Przejdźmy teraz do adapterów. Adapter pomiędzy kontraktem, a widokiem dla wtyczki, wygląda następująco:
  [AddInAdapter]
  class DoSthViewToContractAddInSideAdapter:
     ContractBase, IDoSthContract 
    {
        private IDoSthAddInView _view;

        public DoSthViewToContractAddInSideAdapter( IDoSthAddInView view ) 
        {
            _view = view;
        }

        public virtual string DoSth()
        {
          return _view.DoSth();
        }
    }
Adapter dla wtyczek musi być oznaczony atrybutem AddInAdapter. Dziedziczy on po klasie ContractBase i implementuje interfejs kontraktu (IDoSthContract). We wnętrzu adapter przechowuje referencję do implementacji widoku dla wtyczki, a w ramach implementacji kontraktu, tłumaczone są wszystkie wywołania z kontraktu na widok.
Adapter dla hosta wygląda następująco:
  [HostAdapter]
  public class DoSthHostSideAdapter: IDoSthAppView
  {
    private IDoSthContract _contract;
    private System.AddIn.Pipeline.ContractHandle _handle; //critical fo lifetime management
    public DoSthHostSideAdapter( IDoSthContract contract )
    {
      _contract = contract;
      _handle = new ContractHandle( contract );
    }
    public virtual string DoSth()
    {
      return _contract.DoSth();
    }
  }
Adapter dla host'a musi być oznaczony atrybutem HostAdapter, implementuje on interfejs widoku (IDoSthAppView). We wnętrzu adapter przechowuje referencję do implementacji kontraktu (IDoSthContract), a w ramach implementacji widoku, tłumaczone są wszystkie wywołania z widoku na kontrakt. Istotne jest również wytworzenie uchwytu (ContractHandle) i przechowywanie go przez cały czas życia adaptera (w przeciwnym razie wtyczka może zostać za wcześnie usunięta z pamięci).
Ostatecznie wtyczka może być napisana w następujący sposób:
  [AddIn( "DoSthV1SampleString" )]
  public class SampleString: IDoSthAddInView
  {
    public string DoSth()
    {
      return "Hello from Plugin Class";
    }
  }
Klasa stanowiąca implementację wtyczki musi być oznaczona atrybutem AddIn (razem z nazwą wtyczki) oraz dziedziczyć po interfejsie zdefiniowanym w widoku dla wtyczek (IdoSthAddInView).
Aplikacja natomiast może wyglądać następująco:
    static void Main( string[] args )
    {
      String pipelineInRoot = Environment.CurrentDirectory;
      string[] warnings = AddInStore.Rebuild( pipelineInRoot );
      foreach ( string warning in warnings )
      {
        Console.WriteLine( warning );
      }
      Collection<AddInToken> tokens =  AddInStore.FindAddIns( 
        typeof( IDoSthAppView ), pipelineInRoot );
      if ( tokens.Count > 0 )
      {
        AddInToken token = tokens[ 0 ];
        IDoSthAppView plugin =
            token.Activate<IDoSthAppView>( AddInSecurityLevel.Internet );
        Console.WriteLine( "Message from plugin: {0}", plugin.DoSth() );
      }
      else
        Console.WriteLine("No plugins are found");
      Console.WriteLine( "Press Enter to exit" );
      Console.ReadLine();
    }
Najpierw ustawiamy katalog, w którym znajduje się cały pipeline. Później wytwarzamy magazyny (stores) dla wtyczek i pipeline'u:AddInStore.Rebuild( …). Następnie wyszukujemy wszystkie wtyczki spełniające nasz interfejs widoku: AddInStore.FindAddIns(…), by później aktywować wybraną wtyczkę: token.Activate<IDoSthAppView>( AddInSecurityLevel.Internet ).
Dal kompletności przykładu należy jeszcze przypomnieć o właściwej strukturze katalogów, więc odpowiednie projekty, muszę być budowane do odpowiednich katalogów (poniżej przykład dla konfiguracji Debug):
Projekt
Katalog
MAFSample1.Application
..\bin\Debug\
MAFSample1.Application.View
..\bin\Debug\
MAFSample1.HostSideAdapters
..\bin\Debug\HostSideAdapters\
MAFSAmple1.Contracts
..\bin\Debug\Contracts\
MAFSAmple1.Plugin.PluginSideAdapter
..\bin\Debug\AddInSideAdapters\
MAFSAmple1.Plugin.View
..\bin\Debug\AddInViews\
MAFSAmple1.Plugin
..\bin\Debug\AddIns\DoSthV1SampleString\
Pamiętajmy również że do poszczególnych katalogów powinny trafić tylko właściwe pliki dll, bez plików związanych. Dlatego na wielu referencjach należy zaznaczyć włąściwość „copy local” na false.
W efekcie po uruchomieniu aplikacji powinniśmy otrzymać:
Message from plugin: Hello from Plugin Class
Press Enter to exit
Na koniec przyznajcie: Trochę skomplikowany ten prosty przykład! :)
Promuj

Brak komentarzy:

Prześlij komentarz

Posty powiązane / Related posts