在之前的文章中我们提到了Mussel插件的层次结构:插件(Addin)包括了插件项目的装载节点(AddinNode),装载节点又包括了插件项目(AddinItem),而每个插件项目又可以包括无限下级的子项目:

Mussel的加载器在加载时,会尝试读取当前文件夹以下(包含子文件夹)的所有*.addin文件,每个addin文件被Mussel加载器识别、加载后,便会形成如上图所示的IAddin对象,如果每个插件都是独立的,这个过程就非常简单。但是插件之间必须然会有一些联系,例如,如果系统的工具栏是一个稳定的插件(addin),如果金山词霸有一个扩展,能添加一个按钮到系统的工具栏中,我们先来看看摸拟的配置文件,为了方便说明,我们假定ClassKey指示的类型都存在并忽略掉程序集引入的配置部分。

Toolbar的addin配置文件(toolbar.addin
<?xml version="1.0" encoding="utf-8" ?>
<Addin Name="Toolbar">
<AddinNode Path="/Core/MainForm">
<AddinItem ClassKey="Toolbar"
Name="MainFormToolbar">
<AddinItem ClassKey="NewButtonItem"
Name="New"
Caption="新建"/>
<AddinItem ClassKey="EditButtonItem"
Name="Edit"
Caption="编辑"/>
</AddinItem>
</AddinNode>
</Addin>

词霸扩展插件的配置(ciba.addin)
<?xml version="1.0" encoding="utf-8" ?>
<Addin Name="CibaAddin" InsertAfter="Toolbar">
<AddinNode Path="/Core/MainForm" Container="Toolbar">
<AddinItem ClassKey="CibaButtonItem"
Name="Ciba"
Caption="启动词霸"
InsertAfter="Edit" />
</AddinNode>
</Addin>
分析一下插件配置文件:
首先,词霸的扩展插件无疑是依赖Toolbar插件的,因为他需要在Toolbar上加入新的按钮,所以Toolbar插件的信息必须优先于词霸的插件被系统识别,因此,我们在Addin的元素上用InsertAfter来标识。
另外,由于词霸插件需要放入到ToolBar插件,作为一个子插件出现,所以我们可以看到在 ciba.addin的AddinNode节点有特殊的配置,引入了一个Container。AddinNode的 Path 属性指示插件节点路径的配置,而Container 属性则指示放入该节点的具体哪个插件中作为其子插件,插件的层次以 . 分隔,例如,如果Toolbar插件下面还有一个子插件为ExtendMenu,而我们的词霸扩展插件需要放入这个ExtendMenu中作为其子插件,则Container的值为 Toolbar.ExtendMenu。
由于加载器需要维护一个统一的拼合后的插件树结构,Mussel引入了一个ContainerNode节点的类型,这个类与AddinNode不同,AddinNode只是针对所在的插件来描述结构,而ContainerNode是所有Addin拼合后的结果。我们分别看看这些相关联类型的类图:





接下来,我们写个测试程序来测试一下Mussel的插件树,在Mussel.Core程序集中,有一个默认提供的插件“BasicFormatter”,这是一个格式化器,我们先不用关心这个插件如何用及干什么用的,我们先拿它来测试一下Mussel的插件树,通过装载器装载后,输出各种信息,让我们对Mussel的插件树结构有一个更感性的认识。

主程序的代码
using System;
using System.Collections.Generic;
using Mussel.Addins;
namespace Mussel.AddinTree


{
internal class Program

{

/**//// <summary>
/// 显示AddinItem的信息
/// </summary>
private static void DisplayAddinItem(
KeyValuePair<string, IAddinItem> itemPair, int deep)

{
Console.WriteLine("{0}Current AddinItem:{1},Count:{2}",
string.Empty.PadLeft(deep, '\t'),
itemPair.Key,
itemPair.Value.Childs.Count);
foreach (KeyValuePair<string, IAddinItem> pair
in itemPair.Value.Childs)

{
DisplayAddinItem(pair, deep + 1);
}
}

/**//// <summary>
/// 显示AddinNode的信息
/// </summary>
private static void DisplayAddinNode(
KeyValuePair<string, IAddinNode> nodePair, int deep)

{
Console.WriteLine("{0}Current AddinNode:{1}",
string.Empty.PadLeft(deep, '\t'), nodePair.Key);
foreach (KeyValuePair<string, IAddinNode> addinNodePair
in nodePair.Value.Childs)

{
DisplayAddinNode(addinNodePair, deep + 1);
}
foreach (KeyValuePair<string, IAddinItem> itemPair
in nodePair.Value.AddinItems)

{
DisplayAddinItem(itemPair, deep + 1);
}
}

/**//// <summary>
/// 显示Addin的信息
/// </summary>
/// <param name="addinPair"></param>
private static void DisplayAddin(
KeyValuePair<string, IAddin> addinPair)

{
Console.WriteLine("Current AddinItem:{0},AppDomain Name:{1}",
addinPair.Key,
addinPair.Value.CurrentAppDomain.FriendlyName);
foreach (KeyValuePair<string, IAddinNode> valuePair
in addinPair.Value.AddinNodes)

{
DisplayAddinNode(valuePair, 1);
}
}

/**//// <summary>
/// 显示ContinerNode的信息
/// </summary>
private static void DisplayContainerNode(
KeyValuePair<string, ContainerNode> nodePair, int deep)

{
Console.WriteLine("{0}Current ContainerNode:{1}",
string.Empty.PadLeft(deep, '\t'), nodePair.Key);
foreach (KeyValuePair<string, IAddinItem> itemPair
in nodePair.Value.AddinItems)

{
DisplayAddinItem(itemPair, deep + 1);
}
foreach (KeyValuePair<string, ContainerNode> childPair
in nodePair.Value.Childs)

{
DisplayContainerNode(childPair, deep + 1);
}
}
private static void Main()

{
Console.WriteLine(string.Empty.PadLeft(79, '='));
MusselService service = new MusselService();
service.Start();
ShowAddinInfo(service);
Console.WriteLine(string.Empty.PadLeft(39, '★'));
service.Container.ReleaseAddin("Data1");
service.Container.ReleaseAddin("Data");
ShowAddinInfo(service);
Console.ReadLine();
service.Dispose();
}
private static void ShowAddinInfo(MusselService service)

{
bool isHeader = true;
foreach (KeyValuePair<string, ContainerNode> node
in service.Container.ContainerNodes)

{
if (!isHeader) Console.WriteLine(string.Empty.PadLeft(79, '-'));
isHeader = false;
DisplayContainerNode(node, 0);
}
Console.WriteLine(string.Empty.PadLeft(79, '='));
isHeader = true;
foreach (KeyValuePair<string, IAddin> addinPair
in service.Container.Addins)

{
if (!isHeader) Console.WriteLine(string.Empty.PadLeft(79, '-'));
isHeader = false;
DisplayAddin(addinPair);
}
}
}
}

第一个插件的配置文件(Core.addin)
<?xml version="1.0" encoding="utf-8" ?>
<Addin Name="Core" CreateNewDomain="true" DomainName="Core0">
<AddinNode Path="/Mussel/Core">
<AddinItem ClassKey="BasicFormatter" Name="Root">
<AddinItem ClassKey="BasicFormatter" Name="Child1">
<AddinItem ClassKey="BasicFormatter" Name="Child1_1"/>
</AddinItem>
<AddinItem ClassKey="BasicFormatter" Name="Child2"/>
</AddinItem>
</AddinNode>
</Addin>

第二个插件的配置文件(Data.addin)
<?xml version="1.0" encoding="utf-8" ?>
<Addin Name="Data" CreateNewDomain="true" DomainName="Core1" InsertAfter="Core">
<AddinNode Path="/Mussel/Core" Container="Root">
<AddinItem ClassKey="BasicFormatter" Name="Child3"/>
</AddinNode>
<AddinNode Path="/RootNode/NodeOne">
<AddinItem ClassKey="BasicFormatter" Name="Child4"/>
</AddinNode>
</Addin>

第三个插件的配置文件(Data1.addin)
<?xml version="1.0" encoding="utf-8" ?>
<Addin Name="Data1" CreateNewDomain="true" DomainName="Core2" InsertAfter="Data">
<AddinNode Path="/Mussel/Core" Container="Root.Child3">
<AddinItem ClassKey="BasicFormatter" Name="Child3_1"/>
<AddinItem ClassKey="BasicFormatter" Name="Child3_2"/>
</AddinNode>
<AddinNode Path="/RootNode/NodeOne" Container="Child4">
<AddinItem ClassKey="BasicFormatter" Name="Child4_1"/>
</AddinNode>
</Addin>
在这个程序中,我们放了三个配置文件给Mussel加载,并且这三个插件配置文件中都有相应的关联信息,我们来看看输出的结果
输出的结果

程序输出的结果
===============================================================================
Current ContainerNode:Mussel
Current ContainerNode:Core
Current AddinItem:Root,Count:3
Current AddinItem:Child1,Count:1
Current AddinItem:Child1_1,Count:0
Current AddinItem:Child2,Count:0
Current AddinItem:Child3,Count:2
Current AddinItem:Child3_1,Count:0
Current AddinItem:Child3_2,Count:0
-------------------------------------------------------------------------------
Current ContainerNode:RootNode
Current ContainerNode:NodeOne
Current AddinItem:Child4,Count:1
Current AddinItem:Child4_1,Count:0
===============================================================================
Current AddinItem:Core,AppDomain Name:Core0
Current AddinNode:Mussel
Current AddinNode:Core
Current AddinItem:Root,Count:3
Current AddinItem:Child1,Count:1
Current AddinItem:Child1_1,Count:0
Current AddinItem:Child2,Count:0
Current AddinItem:Child3,Count:2
Current AddinItem:Child3_1,Count:0
Current AddinItem:Child3_2,Count:0
-------------------------------------------------------------------------------
Current AddinItem:Data,AppDomain Name:Core1
Current AddinNode:Mussel
Current AddinNode:Core
Current AddinItem:Child3,Count:2
Current AddinItem:Child3_1,Count:0
Current AddinItem:Child3_2,Count:0
Current AddinNode:RootNode
Current AddinNode:NodeOne
Current AddinItem:Child4,Count:1
Current AddinItem:Child4_1,Count:0
-------------------------------------------------------------------------------
Current AddinItem:Data1,AppDomain Name:Core2
Current AddinNode:Mussel
Current AddinNode:Core
Current AddinItem:Child3_1,Count:0
Current AddinItem:Child3_2,Count:0
Current AddinNode:RootNode
Current AddinNode:NodeOne
Current AddinItem:Child4_1,Count:0
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
Current ContainerNode:Mussel
Current ContainerNode:Core
Current AddinItem:Root,Count:2
Current AddinItem:Child1,Count:1
Current AddinItem:Child1_1,Count:0
Current AddinItem:Child2,Count:0
===============================================================================
Current AddinItem:Core,AppDomain Name:Core0
Current AddinNode:Mussel
Current AddinNode:Core
Current AddinItem:Root,Count:2
Current AddinItem:Child1,Count:1
Current AddinItem:Child1_1,Count:0
Current AddinItem:Child2,Count:0

点击下载此文件