Vítejte na blog.vyvojar.cz Přihlásit | Registrovat | Pomoc

System.Addin - seznamte se

Už několik let píšu rozšiřitelné aplikace dosti primitivním stylem - jedna sdílená assembly mezi hostující aplikaci a addiny. Ostatně tento způsob jsem popisoval v článku C# aplikace s podporou pluginů. V průběhu času se ukázalo jako zásadní problém řešit kompatibilitu - stačí pozměnit rozhraní a všechno jde do kytek. Také bylo velice obtížné zpracovávat chyby v addinech. Naštěstí nám vývojáři .NET Frameworku 3.5 nadělili řešení podobě tzv. pipeline, která vypadá takto:

Pipeline mezi hostem a addiny

Samotná pipeline se skládá ze segmentů. Obvykle představuje každý segment jednu assembly. Pipeline také vyžaduje následující strukturu adresářů:

Struktura pipeline


Dle mého názoru se věci nejlépe vysvětlují na příkladě. Pro naše účely nám poslouží jednoduchá rozšířitelná konzolová aplikace.

Založíme si novou prázdnou Solution s názvem ExtensibleApp.

Krok 1: segment Contract

Jádrem pipeline je segment Contract. Jedná se o neverzovatelný protokol popisující komunikaci mezi hostovací aplikací a addiny. V praxi se implementuje jako rozhraní, které dědí od IContract. Dále musí být označeno atributem AddInContract. Založme si tedy nový projekt typu Class Library s názvem ExtensibleApp.Contracts. Mějme na paměti požadovanou adresářovou strukturu, takže v nastavení projektu změníme Output path na ..\output\Contracts.
Dále do projektu přidáme referenci na System.AddIn a System.AddIn.Contract. Assembly obsahuje jedno rozhraní:
using System.AddIn.Contract;
using System.AddIn.Pipeline;
namespace ExtensibleApp.Contracts
{
      [AddInContract]
      public interface IAddInContract : IContract
      {
            string OperationName { get; }
            string Operate(string value);
      }
}
Kontraktem lze přenášet pouze serializovatelné datový typy z mscorlib ( boo, int, string apod. ), výčtové typy ( buď definované v mscorlib nebo v kontraktu ) a jiné kontrakty.

Krok 2: segment HostView

Tento segment reprezentuje pohled hostující aplikace na addiny a musí se jednat o ne-sealed třídu ( popř. rozhraní ). Nemusí být obsažen ve speciální assembly a proto my ho vložíme do samotné host. aplikace. Založme tedy nový projekt typu Console Application nazvaný ExtensibleApp . V nastavení projektu upravíme Output path na ..\output a přidáme referenci na System.AddIn. Pohled host. aplikace vypadá následovně:
namespace ExtensibleApp 
{ 
      public abstract class AddIn 
      { 
            public abstract string OperationName { get; } 
            public abstract string Operate(string value); 
      } 
} 

Krok 3: segment AddInView

Vytvoříme nový projekt typu Class Library nazvaný ExtensibleApp.AddInViews a opět upravíme jeho nastavení - Output path na ..\output\AddInViews . Taky nezapomeneme přidat referenci System.AddIn. Samotný pohled addinů na hostující aplikaci se téměř shoduje s pohledem hostující aplikace na addiny. Rozdíl je v tom, že tento pohled musí být označen atributem AddInBase.
using System.AddIn.Pipeline; 
namespace ExtensibleApp.AddInViews 
{ 
      [AddInBase] 
      public abstract class AddIn 
      { 
            public abstract string OperationName { get; } 
            public abstract string Operate(string value); 
      } 
} 

Krok 4: segment HostSideAdapter

Nyní musíme nějak provázat kontrakt s našimi pohledy - to mají na starosti adaptéry, které adaptují pohled na kontrakt a naopak.
Opět založíme nový projekt typu Class Library nazvaný ExtensibleApp.HostSideAdapters. Nastavíme Output path na ..\output\HostSideAdapters a přidáme reference na System.AddIn, System.AddIn.Contract, ExtensibleApp a ExtensibleApp.Contracts ( u posledních dvou nastavíme Copy local na False ). Adaptér vypadá následovně:
using System.AddIn.Pipeline; 
using ExtensibleApp.Contracts; 
namespace ExtensibleApp.HostSideAdapters 
{ 
      [HostAdapter] 
      class AddInContractToView : AddIn 
      { 
            private IAddInContract contract; 
            private ContractHandle handle; 
            public AddInContractToView(IAddInContract contract) 
            { 
                  this.contract = contract; 
                  this.handle = new ContractHandle(contract); 
            } 
            public override string OperationName 
            { 
                  get { return this.contract.OperationName; } 
            } 
            public override string Operate(string value) 
            { 
                  return this.contract.Operate(value); 
            } 
      } 
} 

Adaptér na hostující straně musí být označen atributem HostAdapter. Třída nemusí být veřejná a v konstruktoru se očekává instance adaptovaného typu ( nejedná se o nic jiného než návrhový vzor Adapter Wink ).

Krok 5: segment AddInSideAdapter

Založíme nový projekt typu Class Library nazvaný ExtensibleApp.AddInSideAdapters. Nastavíme Output path na ..\output\AddInSideAdapters a přidáme reference na System.AddIn, System.AddIn.Contract, ExtensibleApp.AddInViews a ExtensibleApp.Contracts ( u posledních dvou nastavíme Copy local na False ). Adaptér vypadá následovně:
using System.AddIn.Pipeline; 
using ExtensibleApp.AddInViews; 
using ExtensibleApp.Contracts; 
namespace ExtensibleApp.AddInSideAdapters 
{ 
      [AddInAdapter] 
      class AddInViewToContract : ContractBase, IAddInContract 
      { 
            private AddIn view; 
            public AddInViewToContract(AddIn view) 
            { 
                  this.view = view; 
            } 
            public string OperationName 
            { 
                  get { return this.view.OperationName; } 
            } 
            public string Operate(string value) 
            { 
                  return this.view.Operate(value); 
            } 
      } 
} 

Tato třída označená atributem AddInAdapter mimo jiné dědí od třídy ContractBase (což je výchozí implementaci rozhraní IContract ).

Řekli jsem si, že adaptér adaptuje kontrakt na pohled a naopak. Z této činnosti vyplývá, že pávě díky adaptérům máme k dispozici volné ruce při změnách v hostující aplikaci nebo addinech - pokud se změní kontrakt, stačí upravit adaptér a druhé strany se to vůbec nedotkne. O tom ale někdy jindy.
Máme tedy segment kontraktu a dva pohledy na něj z každé strany. Máme adaptéry které nám slepují celou pipeline. Zbývá tedy vytvořit hostující aplikaci a nějaké addiny. 

Krok 6: segment AddIn

Vytvoříme nový projekt typu Class Library nazvaný AddIn1 a nastavíme Ouput path na ..\output\AddIns\Addin1. Dále přidáme referenci na ExtensibleApp.AddInViews ( Copy local na False ) a System.AddIn. AddIn je odvozená třída od pohledu:

using ExtensibleApp.AddInViews; 
namespace ToUpper 
{ 
      [System.AddIn.AddIn("ToUpper add-in", Version = "1.0.0.0")] 
      public class ToUpper : AddIn 
      { 
            public override string OperationName 
            { 
                  get { return "vsechny pismena velka"; } 
            } 
            public override string Operate(string value) 
            { 
                  return value.ToUpper(); 
            } 
      } 
}


Třída musí být označena atributem AddIn, který obsahuje vlastnosti jako název rozšíření, verze, popis apod.

Krok 7: segment Host

V této chvíli je pipeline vlastně hotova. Projekt hostovací aplikace ExtensibleApp jsme již vytvořili v kroku 2, takže nyní jenom doplníme kód do metody Main.
Myšlenka aplikace je taková, že uživatel napíše libovolný text a ten bude předán každému addinu ke zpracování.

Console.WriteLine("Zadejte text ktery bude zpracovan kazdym modulem:"); 
string input = Console.ReadLine(); 
Console.WriteLine();

.NET Framework si uchovává informace o pipeline v cache a jako první krok v práci s addiny bychom měli cache aktualizovat:

AddInStore.Update(PipelineStoreLocation.ApplicationBase);

Najdeme všechny addiny, které vyhovují HostSideView:

IList<AddInToken> tokens = AddInStore.FindAddIns(typeof(AddIn), PipelineStoreLocation.ApplicationBase); 

V této chvíli máme kolekci tzv. tokenů, což jsou struktury popisující addiny ( tzn. že se nejedná o aktivní addiny ). Všechny v cyklu projdeme a aktivujeme je  metodou Activate. Pak máme k dispozici samotné instance addinů:

foreach (AddInToken token in tokens) 
{ 
      Console.WriteLine("Rozsireni: {0}, verze: {1}", token.Name, token.Version); 
      AddIn addin = token.Activate<AddIn>(AddInSecurityLevel.Internet); 
      Console.WriteLine("Operace: {0}", addin.OperationName); 
      Console.WriteLine("Vystup: {0}", addin.Operate(input)); 
      Console.WriteLine(); 
}

V této konfiguraci se každý addin načte do vlastní AppDomain.

Celý tento příklad je k dispozici v příloze. Jako základní pro pochopení doporučuji .NET Application Extensibility - Part 1 a .NET Application Extensibility - Part 2.

Zveřejněno 11. února 2008 21:56 by eXavera

Attachment(s): ExtensibleApp.zip

Komentář

# re: System.Addin - seznamte se

Zdravím, píšete, že přes Connector nelze přenášet jiné typy než jste napsal. Jakým stylem pak mohu přenést např. vlastní objekty, nebo seznamy apod.

A v případě teda událostí, co když bude parametr v události právě můj typ objektu?

Děkuji za odpověď.

20. srpna 2008 16:00 by bob001
Neregistrovaní uživatele nemužou přidávat komentáře.
 
Vyvojar.cz na prodej!