.NET AddIn Framework With Backwards Compatibility Part 2 - Events

Posted on: March 18, 2016 1:16:34 AM

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.cs
public 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.cs
public interface IV2
{
...
	event EventHandler OnEvent;
}

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.cs
using ContractV2.V2;
using System;
using System.AddIn.Pipeline;

namespace HostAdapterV2
{
	internal class EventHandlerViewToContractAdapter : ContractBase, IEventHandlerContractV2
	{
		private Action _event;

		public EventHandlerViewToContractAdapter(Action @event)
		{
			_event = @event;
		}

		public void Handle(string message)
		{
			_event(message);
		}
	}
}
HostAdapter.cs
public class HostAdapter : IV2
{
...
	public event EventHandler OnEvent
	{
		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.cs
using 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 EventHandler ContractToViewAdapter(IEventHandlerContractV2 handler)
		{
			return new EventHandlerContractToViewAdapter(handler).Handler;
		}
	}
}
AddinAdapter.cs
public class AddinAdapter : ContractBase, IContractV2
{
...
	public void OnEventAdd(IEventHandlerContractV2 handler)
	{
		EventHandler adaptedEvent = 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.cs
public 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.cs
private 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.cs
public class Addin : IV2
{
...
	public event EventHandler OnEvent;
...
	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.

Comments


No comments yet, be the first to leave one.

Leave a Comment