Zum System.AddIn Namespace habe ich bereits das ein oder andere geschrieben. Das Beispiel das ich benutze SayHello() ist mal wieder einfach ein "Hello, world." Beispiel. Des Weiteren habe ich in diesem Beispiel lediglich im Add-In eine Funktion aufgerufen, das Add-In selbst hat keinerlei Mechanismus gehabt um sich beim Host entsprechend auf Events zu registrieren.

Ich habe von Jesse Kaplan ein Beispiel für eine Definition gefunden, wie man Eventing im Interface deklarieren kann. Leider hat Jesse kein lauffähiges Demo mit gepostet. Insofern habe ich einfach mein TechTalk Beispiel genommen und das Eventing in einem anderen Add-In implementiert.

Mein Add-In registriert sich einfach auf einen Host Event und wird jedesmal durch den Host notifiziert, wenn das Ereignis eintritt. Hier ist zuerst einmal die Interfacedefinition:

   1: using System;
   2: using System.AddIn.Contract;
   3: using System.AddIn.Pipeline;
   4:  
   5: namespace Demo.Contracts
   6: {
   7:     
   8:     public interface HostEventsContract : IContract
   9:     {
  10:         void OnNotification( INotificationHandlerContract handler );
  11:         void StartNotifications( int numberOfNotifications );
  12:     }
  13:  
  14:     [AddInContract]
  15:     public interface RetrieveHostEventsContract : IContract
  16:     {
  17:         void Initialize( HostEventsContract hostEvents );
  18:         void Shutdown();
  19:     }
  20:  
  21:     public interface INotificationHandlerContract : IContract
  22:     {
  23:         void Handler( INotificationEventArgsContract args );
  24:     }
  25:  
  26:     public interface INotificationEventArgsContract : IContract
  27:     {
  28:         string GetMessage();
  29:     }
  30: }

Die verschiedenen Contracts definieren nun unsere Nachrichten die verschickt werden. Jetzt gilt es für jeden Contract noch einen View zu definieren. Der View muss jeweils auf der Add-In-Seite und auf der Host-Seite zur Verfügung stehen, obwohl er eigentlich identisch von der Signatur her ist. Hier als die entsprechenden View Basisklassen:

   1: using System;
   2: using System.AddIn.Pipeline;
   3:  
   4: namespace Demo.View.Addin
   5: {
   6:     public abstract class HostEvents
   7:     {
   8:         public System.EventHandler<NotificationEventArgs> OnNotification;
   9:         public abstract void StartNotifications( int numberOfNotifications );
  10:     }
  11:  
  12:     [AddInBase]
  13:     public abstract class RetrieveHostEvents
  14:     {
  15:         public abstract void Initialize( HostEvents hostEvents );
  16:         public abstract void Shutdown();
  17:     }
  18:  
  19:     public abstract class NotificationEventArgs : System.EventArgs
  20:     {
  21:         public abstract string GetMessage();
  22:     }
  23:  
  24: }

Dies ist der View des Add-Ins. Der des Hosts sieht identisch aus, man muss lediglich das AddInBaseAttribute aus Zeile 12 entfernen.

Nun benötigt man noch entsprechende Adapter. Die Adapter sorgen dafür das sich View und Contract miteinander verbinden können und der jeweiligen anderen Seite der Pipeline sich valide ansprechen lassen. Kommt man vom Add-In zum Host so spricht man den Contract  an, während die Implementierung auf Host-Seite mit dem View arbeitet. Der entsprechende Adapter sorgt einfach dafür das dies Reibungslos funktioniert. Das ist leider auch das Problem beim Schreiben von Adaptern und Views. Es ist richtig viel Fleißarbeit. Ich hoffe das sich bald jemand aus der Community erbarmt ein entsprechenden Code-Generator zur Verfügung zu stellen, der sich dann auch noch elegant steuern lässt.

Exemplarisch wird der Source des HostEvents-Adapters gezeigt.

   1: using System;
   2:  
   3: namespace Demo.Adapter.Addin
   4: {
   5:     public class HostEventsAddInAdapter
   6:     {
   7:         internal static Demo.View.Addin.HostEvents 
   8:             ContractToViewAdapter( Demo.Contracts.HostEventsContract contract )
   9:         {
  10:             return new HostEventsContractToViewAddInAdapter( contract );
  11:         }
  12:  
  13:         internal static Demo.Contracts.HostEventsContract 
  14:             ViewToContractAdapter( Demo.View.Addin.HostEvents view )
  15:         {
  16:             return new HostEventsViewToContractAddInAdapter( view );
  17:         }
  18:     }
  19: }

Der Adapter stellt je nach Richtung entsprechende Methoden zur Konvertierung der Schnittstelle zur Verfügung. Einmal von Contract nach View und dann noch von View nach Contract. Schaut man sich nun noch HostEventsContractToViewAddInAdapter an, wird man feststellen das hier weitere Adapter benötigt werden, nämlich jene, die die eigentlichen Events enthalten.

   1: using System;
   2: using System.AddIn.Pipeline;
   3:  
   4: namespace Demo.Adapter.Addin
   5: {
   6:     public class HostEventsContractToViewAddInAdapter : Demo.View.Addin.HostEvents
   7:     {
   8:         private Demo.Contracts.HostEventsContract contract;
   9:         private ContractHandle handle;
  10:  
  11:         public HostEventsContractToViewAddInAdapter(
  12:              Demo.Contracts.HostEventsContract contract )
  13:         {
  14:             this.contract = contract;
  15:             this.handle = new ContractHandle( contract );
  16:             this.contract.OnNotification( 
  17:                 new NotificationHandlerViewToContractAddInAdapter( this ) );
  18:         }
  19:  
  20:         public override void StartNotifications( int numberOfNotifications )
  21:         {
  22:             contract.StartNotifications( numberOfNotifications );
  23:         }
  24:  
  25:         internal Demo.Contracts.HostEventsContract GetSourceContract()
  26:         {
  27:             return this.contract;
  28:         }
  29:     }
  30: }

Zu sehen in Zeile 17.

Und so geht es weiter und weiter und weiter. Deshalb lege ich ein funktionierendes Beispiel hier ab. Viel Spaß damit!