This article is all about using the .NET 3.5 System.Addin
namespace. This article owes much to the fabulous WPF book by Mathew McDonald (who is an uber genius in my opinion).
What this article is attempting to demonstrate is fairly simple actually.
There is an AddIn contract (INumberProcessorContract
implementing IContract
) that defines what all add-ins should do. There is also another Contract that specifies how the add-in should communicate back to the Host application. This Contract is called IHostObjectContract
, which again implements IContract
.
There is a pipeline to support the AddIn model, and finally, there is a host application that will host the add-ins. In essence, that is all this article is about.
Before I start diving into the details, it's probably worth mentioning what the add-in(s) provided by the demo application actually do. I have created several add-ins that all provide some functionality that matches the method signature defined within the AddIn INumberProcessorContract
contract (implementing IContract
). Basically, all the add-ins in the demo app will provide a single method that has the following signature:
List<int> ProcessNumbers(int fromNumber, int toNumber)
I have created three separate add-ns, which do various things to do with numbers:
- Finobacci numbers add-in: Returns a list of Fibonacci numbers between
fromNumber
and toNumber
. - Loop numbers add-in: Returns a list of numbers between
fromNumber
and toNumber
- Prime numbers add-in: Returns a list of prime numbers between
fromNumber
and toNumber
And within the attached demo application, I wanted to show how the add-in could report progress back to the host application, so this is also part of the attached demo code.
We will now go on to discuss how the .NET 3.5 System.Addin
namespace allows us to create add-ins (not easily, but it does allow it).
The following subsections will go into this in a little more detail:
AddIns (sometimes called Plugins) are separately compiled components that an application can locate, load, and make use of at runtime (dynamically). An application that has been designed to use add-ins can be enhanced (by developing more add-ins) without the need for the original application to be modified or recompiled and tested (though the add-ins should be tested). For some time, .NET has allowed developers to create applications that use add-ins, but this more than likely used the System.Reflection
namespace, and was probably a fair bit of work. With .NET 3.5 comes a new namespace, namely the System.Addin
namespace. The .NET team has created a very flexible framework for building add-ins, this is known as the AddIn Pipeline (System.AddIn.Pipeline
). By using the AddIn Pipeline, the necessary plumbing is already in place to allow the AddIn model to work. So that's a good thing. The down side to this is that you must implement at least 7 different components to implement the most basic AddIn model. The next section will discuss the AddIn pipeline concept (though it will not discuss the System.AddIn.Pipeline
namespace), and shall discuss what happens in each of the 7 (minimum) components that are required in order to correctly setup an AddIn model enabled application.
Note: The AddIn model is equally at home in WinForms or WPF (and possibly ASP.NET, though I didn't try that). I shall be using WPF as I like it, though the topics discussed here after would be equally suited to working with WinForms.
The heart of the Addin model is the pipeline. The pipeline is a chain of components that allows the application to communicate with the add-in. At one end of the pipeline is the Host Application, and at the other end is the add-in, and in between are five other components that facilitate the add-in operation and interaction. Consider the following diagram:
At the center of this figure is the Contract (implements System.AddIn.Contract.IContract
), which includes one or more interfaces that define how the host application and the add-ins can interact. The Contract can also include serializable types that are planned to be used between the host application and the add-in. Microsoft designed the AddIn model with extensibility in mind, and as such, both the host application and the actual add-in don't actually use the Contract directly; rather they use their own respective versions of the Contract, called Views. The host application uses the host view, while the add-in uses the AddIn view. The views normally contain an abstract class that reflects the interfaces in the Contract interface (though the View doesn't inherit from the Contract or implement the System.AddIn.Contract.IContract
interface).
Although the Views and the Contract have similar method signatures, they are totally separate; it's up to the Adapters to join them together. There are two Adapters: one for the host application which inherits from the abstract host view class, whilst the AddIn adapter inherits from System.AddIn.Pipeline.ContractBase
and the actual application specific interface that is defined in the Contract component. One thing to note here is that because the AddIn adapter inherits from System.AddIn.Pipeline.ContractBase
, which in turn inherits from MarshalByRefObject
(MarshalByRefObject
enables access to objects across application domain boundaries in applications that support Remoting), the add-in is allowed to cross application domain boundaries.
The flow is something like this:
- The host application calls one of the methods in the host view. But the host view is really an abstract class. What is actually happening is that the host application is really calling a method on the host adapter though the host view. This is possible as the host adapter inherits from the host view.
- The host adapter then calls the correct method in the contract interface, which is implemented by the AddIn adapter.
- The AddIn adapter calls a method in the AddIn View. This method is implemented by the add-in that actually does the work.
Consider the following figure for a single add-in within the pipeline:
This diagram shows what the pipeline would look like for a single add-in. If we wish to create more add-ins (as the demo application does), we simply need to create more concrete classes that inherit from the abstract AddIn View class.
The AddIn model relies on a strict directory structure. However, the required directory structure is actually separate from the application, so it's fine to have the projects elsewhere as long as they build to a common folder. For example,s the demo application uses an Output folder under the solution, which must have the following subfolders:
The AddinIns folder above must also have a separate folder for each available add-in, as shown below (using the three add-ins for the demo application):
This folder structure is not optional at all. It must be exactly as shown in the order for the AddIn model to work correctly. This example assumes that the host application is deployed in the Output folder.
As the AddIn model file structure is mandatory, if you leave out or misname a particular project, you will get a runtime Exception. Here is a step by step Visual Studio guide of how I managed to get the demo application to work.
- Create a top level directory to hold all the seperate components. I called mine AddInTest.
- Create either a WinForms or WPF based host application. It doesn't matter what it is called, but it must be placed in the top level folder from step 1.
- Add a new Class Library project for each pipeline component. You will also need to create a project for at least one add-in component. This should give you something like the following within Visual Studio (note there are actually three add-ins in the solution shown here):
- Next, you need to create a build directory in the top level directory (AddInTest\ for the demo app). This is where the AddIn pipeline components will be placed once they are compiled. This folder is called "Output" in the demo application.
- As each of the pipeline components is constructed and built, you will need to modify the build path to point to the relevant subdirectory underneath the top level build folder. This is shown in the figure below (which we saw earlier):
The build path can be changed using Visual Studio by right clicking the project and getting up the project settings. See the figure below:
There is one last issue that you have to be aware of when building AddIn enabled applications. There will obviously be a need to reference one or more of the AddIn pipeline components within another pipeline component project. However, as the AddIn model relies on the strict mandatory file structure mentioned above, when adding a reference to another of the pipeline components, care needs to be taken that the referenced DLL is not copied, such that the AddIn model will only ever use the DLL that is found within the related AddIn model file system folder. To make sure this all works as expected, we need to make sure that any referenced pipeline DLL is set with the "Copy Local" property set to "False". This ensures that the correct file system placed DLL is used within the host application. Basically, the host application looks for the various components at certain file system locations. The "Copy Local" property may be changed by clicking on the referenced DLL and examining the value of the "Copy Local" property within the property grid. If it is set to "True", this needs to be changed to "False" for any referenced pipeline component. Let's see an example of that.
The following figure shows this for the Contract DLL when referenced within the HostSideAdapter project:
OK, OK, enough talk, you want to see some code, right? There are a lot of different projects, but the code is fairly simple I think. I'll go through each of them in turn.
This simply contains two interfaces that are implemented by the host and AddIn side adapters:
namespace Contract
{
[AddInContract]
public interface INumberProcessorContract : IContract
{
List<int> ProcessNumbers(int fromNumber, int toNumber);
void Initialize(IHostObjectContract hostObj);
}
public interface IHostObjectContract : IContract
{
void ReportProgress(int progressPercent);
}
}
This simply contains two classes. The NumberProcessorViewToContractAdapter
class allows the AddIn adapter to to interact with the View and the Contract. Whilst the HostObjectContractToViewAddInAdapter
class allows the AddIn adapter to interact with the Host view. In this case, this allows the AddIn to report progress.
namespace AddInSideAdapter
{
[AddInAdapter]
public class NumberProcessorViewToContractAdapter : ContractBase,
Contract.INumberProcessorContract
{
.......
.......
}
public class HostObjectContractToViewAddInAdapter : AddInView.HostObject
{
.......
.......
}
}
This simply contains two classes. The NumberProcessorContractToViewHostAdapter
class allows the Host side adapter to interact with the host View. Whilst the HostObjectViewToContractHostAdapter
class allows the Host adapter to interact with the Host view, which in this case allows the AddIn to report progress.
namespace HostSideAdapter
{
[HostAdapter]
public class NumberProcessorContractToViewHostAdapter :
HostView.NumberProcessorHostView
{
.......
.......
}
public class HostObjectViewToContractHostAdapter : ContractBase,
Contract.IHostObjectContract
{
.......
.......
}
}
This simply contains two abstract classes. The NumberProcessorAddInView
class is inherited by any AddIn concrete class. Whilst the HostObject
is inherited by an object that needs to communicate between the host Contract and View adapter.
namespace AddInView
{
[AddInBase]
public abstract class NumberProcessorAddInView
{
public abstract List<int> ProcessNumbers(int fromNumber, int toNumber);
public abstract void Initialize(HostObject hostObj);
}
public abstract class HostObject
{
public abstract void ReportProgress(int progressPercent);
}
}
This simply contains two abstract classes. The NumberProcessorHostView
class is inherited by any host side adapter concrete class. Whilst the HostObject
is inherited by a class within the host application that can make use of the reported progress.
namespace HostView
{
public abstract class NumberProcessorHostView
{
public abstract List<int> ProcessNumbers(int fromNumber, int toNumber);
public abstract void Initialize(HostObject host);
}
public abstract class HostObject
{
public abstract void ReportProgress(int progressPercent);
}
}
This is the WinForms or WPF (WPF, in this case) application that is hosting the AddIns found. It is also responsible for calling the selected add-in's methods, and allows the add-in to report progress by using an internal class AutomationHost
which inherits from the HostView.HostObject
class.
namespace ApplicationHost
{
public partial class Window1 : Window
{
#region Data
private AutomationHost automationHost;
private HostView.NumberProcessorHostView addin;
#endregion
.......
.......
#region Private Methods
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string path = Environment.CurrentDirectory;
AddInStore.Update(path);
string[] s = AddInStore.RebuildAddIns(path);
IList<AddInToken> tokens =
AddInStore.FindAddIns(typeof(HostView.NumberProcessorHostView), path);
lstAddIns.ItemsSource = tokens;
automationHost = new AutomationHost(progressBar);
}
private void btnUseAddin_Click(object sender, RoutedEventArgs e)
{
if (lstAddIns.SelectedIndex != -1)
{
AddInToken token = (AddInToken)lstAddIns.SelectedItem;
addin =
token.Activate<HostView.NumberProcessorHostView>(
AddInSecurityLevel.Internet);
addin.Initialize(automationHost);
Thread thread = new Thread(RunBackgroundAddIn);
thread.Start();
}
else
MessageBox.Show("You need to select an addin first");
}
private void RunBackgroundAddIn()
{
List<int> numbersProcessed = addin.ProcessNumbers(1, 20);
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate()
{
lstNumbers.ItemsSource = numbersProcessed;
progressBar.Value = 0;
addin = null;
}
);
}
.......
.......
}
internal class AutomationHost : HostView.HostObject
{
.......
.......
}
}
As I stated, this is WPF (but could be WinForms); the UI is not important and is a throw away. However, it looks like this:
Where there is a list of add-ins found, and then the user is able to apply the add-ins. This probably needs a little bit of an explanation. There is a class called AddInStore
which allows the .NET code to rebuild the add-in list. This results in a new file being created on the file system.
The AddInStore
also allows the app code to find add-ins. So this is exactly what I do with the following lines, where the add-ins are refreshed and then added to a ListBox
:
string path = Environment.CurrentDirectory;
AddInStore.Update(path);
string[] s = AddInStore.RebuildAddIns(path);
IList<AddInToken> tokens =
AddInStore.FindAddIns(typeof(HostView.NumberProcessorHostView), path);
So that really only leaves the add-in concrete classes themselves; these are fairly trivial (you'll be pleased to hear... as the add-in model is fairly scary, and not for the faint hearted, and without Mathew McDonald's excellent book on WPF, I could not have written this article). So, let's see the add-in's code.
Recall that there were three add-ins, each of which inherits from the abstract AddInView.NumberProcessorAddInView
class.
- Finobacci numbers add-in: Returns a list of Fibonacci numbers between
fromNumber
and toNumber
. - Loop numbers add-in: Returns a list of numbers between
fromNumber
and toNumber
. - Prime numbers add-in: Returns a list of Prime numbers between
fromNumber
and toNumber
.
Well, they all roughly work the same. I will put the code from them all for completeness, though this is the easy part.
Finobacci Numbers AddIn
Uses recursion to return a list of Fibonacci numbers between fromNumber
and toNumber
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.AddIn;
using System.Threading;
namespace FibonacciAddIn
{
[AddIn("Fibonacci Number Processor", Version = "1.0.0.0",
Publisher = "Sacha Barber",
Description = "Returns an List<int> of fiibonacci number integers within the " +
"to/from range provided to the addin")]
public class FibonacciNumberProcessor : AddInView.NumberProcessorAddInView
{
private AddInView.HostObject host;
public static int Fibonacci(int n)
{
if (n == 0 || n == 1)
return n;
else
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
public override List<int> ProcessNumbers(int fromNumber, int toNumber)
{
List<int> results = new List<int>();
double factor = 100 / toNumber - fromNumber;
for (int i = fromNumber; i < toNumber; i++)
{
host.ReportProgress((int)(i * factor));
results.Add(Fibonacci(i));
}
host.ReportProgress(100);
return results;
}
public override void Initialize(AddInView.HostObject hostObj)
{
host = hostObj;
}
}
}
Loop Numbers AddIn
Returns a list of of numbers between fromNumber
and toNumber
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.AddIn;
using System.Threading;
namespace LoopAddIn
{
[AddIn("Loop Number Processor", Version = "1.0.0.0", Publisher = "Sacha Barber",
Description = "Returns an List<int> of looped number integers within the " +
"to/from range provided to the addin")]
public class LoopNumberProcessor : AddInView.NumberProcessorAddInView
{
private AddInView.HostObject host;
public override List<int> ProcessNumbers(int fromNumber, int toNumber)
{
List<int> results = new List<int>();
double factor = 100 / toNumber - fromNumber;
for (int i = fromNumber; i < toNumber; i++)
{
host.ReportProgress((int)(i * factor));
results.Add(i);
}
host.ReportProgress(100);
return results;
}
public override void Initialize(AddInView.HostObject hostObj)
{
host = hostObj;
}
}
}
Prime Numbers AddIn
Returns a list of of prime numbers between fromNumber
and toNumber
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.AddIn;
using System.Threading;
namespace PrimeNumberAddIn
{
[AddIn("Prime Number Processor", Version = "1.0.0.0", Publisher = "Sacha Barber",
Description = "Returns an List<int> of prime number integers within the" +
"to/from range provided to the addin")]
public class PrimeNumberProcessor : AddInView.NumberProcessorAddInView
{
private AddInView.HostObject host;
public override List<int> ProcessNumbers(int fromNumber, int toNumber)
{
List<int> results = new List<int>();
int[] list = new int[toNumber - fromNumber];
double factor = 100 / toNumber - fromNumber;
for (int i = 0; i < list.Length; i++)
{
list[i] = fromNumber;
fromNumber += 1;
}
int maxDiv = (int)Math.Floor(Math.Sqrt(toNumber));
int[] mark = new int[list.Length];
for (int i = 0; i < list.Length; i++)
{
for (int j = 2; j <= maxDiv; j++)
if ((list[i] != j) && (list[i] % j == 0))
mark[i] = 1;
host.ReportProgress((int)(i * factor));
}
for (int i = 0; i < mark.Length; i++)
if (mark[i] == 0)
results.Add(list[i]);
host.ReportProgress(100);
return results;
}
public override void Initialize(AddInView.HostObject hostObj)
{
host = hostObj;
}
}
}
And here is a screenshot of the add-ins running inside a WPF app (I have made it look nice with a WPF DataTemplate
, but that's not important):
As you can see, this is a complicated arrangement, and it is quite a lot to get your head around, and quite a lot of work. The CLR AddIn team has listened to initial concerns that this model is too much work, and have dedicated a CodePlex page to a Visual Studio AddIn that aids in the development of creating AddIn enabled apps. They also supply a blank AddIn pipeline project for you to start with. The VS AddIn aids in the creation of both the host and AddIn side adapters. The CLR AddIn team's CodePlex page can be found at the following link: Managed Extensibility and Add-In Framework.
I think it's fair to say that the AddIn model is not that easy to understand, but this example shows you most of what you will need to know. I have not included all the code in this article, I have tried to only include what I consider to be the most important parts (Nish, if you are reading, see I've listened to you) of the code. As such, you will need to refer to the article's demo application in order to understand the full workings of the AddIn model. I hope you learnt something from this article, and if you liked it, please leave a vote for it.