In part 2 of the MAF example, I'm going to walk through adding an event. Events can be very tricky to move across the isolation layer and still have it act like an event. I'm going to continue on the project from the first example which is also located in GitHub. I'm going to be only showing code snippets necessary to get the events working, there is more to the project than what is being shown like the class attributes necessary for the pipeline to work. These are shown in the first example.
I'm going to start in the ContractV2 project. We're going to need to add in 2 methods in the interface and add a new interface to handle the delegate pass through.
IContractV2.cspublic interface IContractV2 : IContract { ... void OnEventAdd(IEventHandlerContractV2 handler); void OnEventRemove(IEventHandlerContractV2 handler); } public interface IEventHandlerContractV2 : IContract { void Handle(string message); }
We now need to update the Views on both the adapter and the host side with the same new event.
IV2.cspublic interface IV2 { ... event EventHandlerOnEvent; }
Next we're going to modify the HostAdapterV2 project. We're going to modify HostAdapter.cs and add a new class to handle the conversion of the event. Starting with the new conversion class.
EventHandlerViewToContractAdapter.csusing ContractV2.V2; using System; using System.AddIn.Pipeline; namespace HostAdapterV2 { internal class EventHandlerViewToContractAdapter : ContractBase, IEventHandlerContractV2 { private ActionHostAdapter.cs_event; public EventHandlerViewToContractAdapter(Action @event) { _event = @event; } public void Handle(string message) { _event(message); } } }
public class HostAdapter : IV2 { ... public event EventHandlerOnEvent { add { if (_OnEvent == null) { _contract.OnEventAdd(new EventHandlerViewToContractAdapter(FireOnEvent)); } _OnEvent += value; } remove { _OnEvent -= value; if (_OnEvent == null) { _contract.OnEventRemove(new EventHandlerViewToContractAdapter(FireOnEvent)); } } } private event EventHandler _OnEvent; private void FireOnEvent(string message) { if (_OnEvent != null) { _OnEvent(this, message); } } }
Next we are going to play in the AddinAdapter project. This is going to be very similar to the HostAdapter with a few changes
EventHandlerContractToViewAdapter.csusing ContractV2.V2; using System.AddIn.Pipeline; namespace AddinAdapterV2 { internal class EventHandlerContractToViewAdapter { private IEventHandlerContractV2 _contract; private ContractHandle _handle; public EventHandlerContractToViewAdapter(IEventHandlerContractV2 contract) { _contract = contract; _handle = new ContractHandle(_contract); } internal void Handler(object sender, string message) { _contract.Handle(message); } } }EventHandlerAdapter.cs
using ContractV2.V2; using System; namespace AddinAdapterV2 { internal class EventHandlerAdapter { internal static EventHandlerAddinAdapter.csContractToViewAdapter(IEventHandlerContractV2 handler) { return new EventHandlerContractToViewAdapter(handler).Handler; } } }
public class AddinAdapter : ContractBase, IContractV2 { ... public void OnEventAdd(IEventHandlerContractV2 handler) { EventHandleradaptedEvent = EventHandlerAdapter.ContractToViewAdapter(handler); _eventHandlers.Add(handler, adaptedEvent); _view.OnEvent += adaptedEvent; } public void OnEventRemove(IEventHandlerContractV2 handler) { EventHandler adaptedEvent; if (_eventHandlers.TryGetValue(handler, out adaptedEvent)) { _eventHandlers.Remove(handler); _view.OnEvent -= adaptedEvent; } } }
Now because this is a V2, we need to modify the V1 to V2 adapter.
AddinAdapterV1ToV2.cspublic class AddinAdapterV1ToV2 : ContractBase, IContractV2 { ... public void OnEventAdd(IEventHandlerContractV2 handler) { } public void OnEventRemove(IEventHandlerContractV2 handler) { } }
Now we can register to the event from the Views and fire the event just like we normally would. Starting with the ConsoleHost project.
Program.csprivate static void Main(string[] args) { ... if (token != null) { // Activate the selected AddInToken in a new application domain // with the Internet trust level. IV2 v2 = token.Activate(AddInSecurityLevel.Internet); v2.OnEvent += (sender, message) => { Console.WriteLine("Event Fired: {0}", message); }; v2.Initialize(new CallbackHandler()); // Run the add-in. v2.WriteToConsole("Hello World From Host!"); Console.WriteLine(v2.GetName()); //var test = (Stopwatch)v2.GetSource(); //Task.Delay(500).Wait(); //test.Stop(); //Console.WriteLine(test.ElapsedTicks); } }
Lastly, we fire the event, we'll set it up to fire on the initialize method because it's always going to be called.
Addin.cspublic class Addin : IV2 { ... public event EventHandlerOnEvent; ... public void Initialize(ICallbackV2 callback) { _callback = callback; if (OnEvent != null) { OnEvent(this, "Initialized called from Plugin"); } } }
There we have it, an event all the way through the pipeline that fires and handles as if it was done within the same AppDomain.