65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的简单扩展框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.14/5 (5投票s)

2007年7月30日

CPOL

5分钟阅读

viewsIcon

42320

downloadIcon

520

通过为其他开发人员提供框架来扩展您的应用程序

引言

您想为您的应用程序用户提供一个可扩展的框架,让他们能够扩展您基础应用程序的功能吗?我们经常看到这种情况——在 Visual Studio 和 Office 产品中,它们被称为插件(add-ins);在 Eclipse 中,它们被称为插件(plug-ins);在其他应用程序中,我听说过它们被称为扩展(extensions)或快照(snap-ins)或模块(modules)。无论它们被称为什么,它们都只做一件事:允许您的应用程序功能通过您可能从未想过的新功能进行扩展。

在本文中,我将向您展示如何创建一个简单的可扩展性框架,您可以使用它来扩展您的应用程序的功能。

Using the Code

让其他开发人员扩展您的应用程序实际上非常简单。您所要做的就是提供一个可以继承的基础类,然后在加载扩展时,确保有一个类继承自该类。这个类可以是接口(Interface)或类(Class)。我选择使用接口,但如果您有希望从任何扩展中执行的通用代码,则应该使用类。如果您不使用接口,则需要提供另一种类型检查方法(请参阅下面的 `Extensions` 集合类中的 `searchDir` 方法)。

下面是简单扩展的基础类

using System.Drawing;
using System;
using System.Collections.Generic;
using System.Text;

namespace Extensibility {

    public interface IExtender {
        String Name {
            get;
        }

        String Description {
            get;
        }

        String MenuText {
            get;
        }

        String DLLPath {
            get;
        }

        Image Image {
            get;
        }

        String Provider {
            get;
        }

        Object Execute();
    }
}

您可以根据需要扩展任何属性和方法。我还喜欢为我的集合添加类型安全,所以我提供了一个 `Extensions` 集合类。这也可以让我有一个用于其他用途的类,例如填充菜单项(如果它们将从菜单访问),并提供一个事件来通知应用程序何时单击了扩展。下面是我的 `Collection` 类

using System.Windows.Forms;
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

namespace Extensibility {
    public class Extensions : System.Collections.CollectionBase {
    public delegate void extensionClickedEventHandler(object sender, 
        EventArgs e);
    public event extensionClickedEventHandler ExtensionClicked;
    public int add(IExtender extension) {
        return this.List.Add(extension);
    }
    public void remove(int index) {
        this.List.Remove(index);
    }
    public IExtender item(int index) {
        return (IExtender)this.List[index];
    }

    public IExtender item(String name) {
        IExtender ret = null;
        foreach (IExtender ext in this.List) {
            if(ext.Name.Equals(name)){
                ret = ext;
                break;
            }
        }
        return ret;
    }

    public int populate(String directory, bool searchSubs, 
        ToolStripMenuItem menuItem) {
        int ret = this.populate(directory, searchSubs);
        if (ret > 0) {
            foreach (IExtender ext in this.List) {
                ToolStripMenuItemEx item = new 
                    ToolStripMenuItemEx();
                item.Click += new EventHandler(item_Click);
                if (ext.MenuText != null) item.Text = 
                    ext.MenuText;
                if (ext.Image != null) item.Image = ext.Image;
                if (ext.Description != null) 
                    item.ToolTipText = ext.Description;
                item.Extension = ext;
                menuItem.DropDownItems.Add(item);
            }
        }
        return ret;
    }

    void item_Click(object sender, EventArgs e) {
        ExtensionClicked.Invoke(sender, e);
    }

    public int populate(String directory) {
        return this.populate(directory, false);
    }

    public int populate(String directory, bool searchSubs) {
        if(!Directory.Exists(directory)){
            throw new IOException("Directory not found");
        }
        rectoryInfo dir = new DirectoryInfo(directory);
        is.searchDir(dir, searchSubs);
        turn this.Count;
    

    private void searchDir(DirectoryInfo dir, bool searchSubs) {
        fileInfo[] files = dir.GetFiles("*.dll");
        if (files.Length == 0) {
            throw new IOException("No dll files found in " + 
                dir.FullName);
        }
        foreach (FileInfo f in files) {
            String fileName = f.FullName;
            Assembly assy = Assembly.LoadFile(fileName);
            Type[] types = assy.GetTypes();
            foreach (Type t in types) {
                if (t.GetInterface("IExtender") != null) {
                    IExtender ext = 
                (IExtender)assy.CreateInstance(t.FullName);
                    this.add(ext);
                }
                if (searchSubs) {
                    foreach(DirectoryInfo subDir in 
                        dir.GetDirectories()){
                    this.searchDir(subDir, searchSubs);
                }
            }
        }
    }
}

关于加载代码的一些说明。Reflection 用于从指定目录中的所有库加载扩展。代码遍历指定目录中所有 DLL 文件,并查询所有类型以查看它们是否实现了 `IExtender` 接口。如果是,则将其加载到我们的集合中。我选择使用目录方法(加载所有扩展)而不是专门识别扩展,这是出于易用性的考虑。我可以轻松地提供注册表项或文件来指定要加载或忽略的扩展。

回到正题——可选地,当我们调用 `populate` 方法时,我们可以提供一个 `ToolStripMenuItem`,它还将用它找到的所有扩展来填充该 `menuitem`。在这方面,我还提供了一个扩展的 `ToolStripMenuItem` 类(`ToolStripMenuItemEx`),它可以保存扩展,以便在单击时将扩展返回给应用程序。`populate` 方法还将创建内部事件处理程序,该处理程序将引发您应用程序中的外部事件处理程序并返回一个 extender。

下面是 `ToolStripMenuItemEx` 类

using System;
using System.Collections.Generic;
using System.Text;

namespace Extensibility {
    public class ToolStripMenuItemEx : System.Windows.Forms.ToolStripMenuItem {
        private IExtender _Extension = null;
        public IExtender Extension {
            get { return _Extension; }
            set { _Extension = value; }
        }
    }
}

框架就这么多了。

使用扩展来扩展应用程序功能

现在我们有了框架,我们所要做的就是创建扩展库。在一个新的类库项目(任何 .NET 语言都可以)中,添加对 `Extensibility` 项目的引用,将以下代码粘贴到一个新类中

using System.Windows.Forms;
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Text;
using Extensibility;
using System.Reflection;

namespace ExtensionsTest {
    public class SomeExtension_1 : IExtender {

        public SomeExtension_1() {
            String imagePath = System.IO.Path.GetDirectoryName
			(Assembly.GetExecutingAssembly().Location);
            Icon icn = new Icon(System.IO.Path.Combine(imagePath,"18.ico"));
            _Image = icn.ToBitmap();
        }

        private String _Name = "Some extension #1";
        private String _Description = "Description of Some Extension #1";
        private String _MenuText = "Extension 1";
        private Image _Image = null;
        private String _Provider = "My Company Name";

        #region IExtender Members

        string IExtender.Name {
            get { return _Name; }
        }

        string IExtender.Description {
            get { return _Description; }
        }

        string IExtender.MenuText {
            get { return _MenuText; }
        }

        string IExtender.DLLPath {
            get {
                return Assembly.GetExecutingAssembly().Location;
            }
        }

        Image IExtender.Image {
            get { return _Image; }
        }

        string IExtender.Provider {
            get { return _Provider; }
        }

        object IExtender.Execute() {
            return "The Execute method or operation for 
			SomeExtension_1 is not implemented.";
        }

        #endregion
    }
}

这将创建一个名为 `SomeExtension_1` 的扩展。请注意,所有标识信息都应通过只读属性提供。DLL 路径会自动由 Reflection 返回。这是使用类而不是接口的一个好理由,这样就不必在每个扩展中都提供这些信息。图像是可选的,因为它将在创建 `menuitem` 时进行检查,并且在创建扩展时设置。

整合

这是最终结果。从这一点开始,在应用程序中创建和执行扩展非常简单。创建一个新的 Windows 应用程序,同样,添加对 `Extensibility` 项目的引用,添加一个新窗体和一个按钮(`button1`),以及 `menustrip`(`menuStrip1`)和一个工具 `menuitem`,在工具 `menuitem` 下,有一个 `extensions menuitem`。请注意,我们不必引用任何 `Extensions` 库(DLL)文件;我们不希望指定任何 DLL,因为我们希望选择过程是动态的。然后粘贴以下代码

        void extensions_ExtensionClicked(object sender, EventArgs e) {
            IExtender extender = ((ToolStripMenuItemEx)sender).Extension;
            MessageBox.Show((String)extender.Execute());
        }

        private Extensions extensions = null;
        private void button1_Click(object sender, EventArgs e) {
            extensions = new Extensions();
            extensions.ExtensionClicked += new 
    Extensions.extensionClickedEventHandler(extensions_ExtensionClicked);
            try {
                extensions.populate(Application.StartupPath, false, 
            this.extensionsToolStripMenuItem);
            } catch (Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

在单击 `button1` 之前运行应用程序,您将看到 `extensions` 项是空的。单击按钮 1 将执行以下操作

  • 创建一个新的 `extensions` 集合
  • 设置事件处理程序
  • 使用 `Application.Startup` 路径目录中的所有扩展填充 `extensions` 集合

如果查看 `extensions` 菜单项,您现在会看到 `extensions`。单击它将显示来自 `extension` 的 `execute` 方法的文本。好了!您已经使用在创建应用程序时不存在的功能扩展了您的应用程序。现在,任何创建带有 `extensions` 的 DLL 的人都可以将其放入应用程序文件夹中,它将自动包含在您的应用程序中。

一些开发说明

因为我们希望动态加载扩展而不在主应用程序中引用它们(这将破坏我们的目的),所以我们的 `execute` 方法的返回类型是 `object` 类型。这将允许我们返回并使用您的应用程序知道的类型。返回对象的合适候选者是 `XmlDocument` 对象。这将允许您表示任何您想要的数据,只要您的主应用程序可以解释它。您可以通过提供一个扩展需要返回的特定类型的对象模型来绕过此限制。这取决于您。

祝您扩展愉快!

历史

  • 2007 年 7 月 30 日:初始发布
© . All rights reserved.