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! :)
Brak komentarzy:
Prześlij komentarz