代码改变世界

.Net插件框架的实现及分析(一)

2010-09-19 11:15  w i n s o n  阅读(1918)  评论(4编辑  收藏  举报

 

 

 在开始自己系统的同时,总想将系统做得更灵活,可让其他人非常方便地参与进来,这就需要一个完善的插件系统,今天我准备写一个插件框架系列的文章,主要是想记录下我学习 ScrewTurn Wiki 系统的成果,也方便需要的人查阅   


我个人感觉 ScrewTurn Wiki 系统的插件框架做得相当好,可让后期制作插件的人方便地将系统进行整合,所以这段时间都在研究。现大概说一下一个插件框架的架构和需求吧。 我认为一个插件系统,应该可以独立于现有系统的类库,让第三方开发者只引用一个插件项目的DLL即可完成所有相关的开发,而且对系统框架要有一定的、统一的约定,不能让第三方开发者想怎样设计就怎样设计,因此一般的插件框架都是接口来实现会比较好,以下将说说大致的调用流程,呵,我就不画流程图啦(主要是我也懒得安装 ),只用文字说明一下吧:


 用户访问网站 --> 调用核心功能函数 --> 调用实现此核心功能的接口


因此通过实现不同的接口,就可以方便地实现不同的功能函数,也达到了实现不同插件的效果,这个道理很简单,只要有接触过OO的人都会知道,但具体要如何实现呢,以下将慢慢道来。 此插件框架主要使用了接口+Provider模式实现,因此需先设计好要被实现的接口,以下仅以一个格式化的功能为例吧,此格式化功能插件主要是在文章发布后对内容进行格式化处理,如可为此创建代码高亮插件、查看次数插件等,都是在文章输出时调用不同的格式化插件进行处理即可。 OK,第一步我们先定义好一个全局总的 IProvider 接口,好让之后所有插件都基于这些接口进行:


IProvider.cs 文件:

代码
namespace CoderBlog.PluginFramework
{
    
public interface IProvider
    {
        
/// <summary>
        
/// 初始化插件
        
/// </summary>
        
/// <param name="host">主系统对象,可让插件调用主系统里的方法,以达到解耦合</param>
        
/// <param name="config">插件配置文件</param>
        void Init(IHost host, string config);

        
/// <summary>
        
/// 当注销插件时调用
        
/// </summary>
        void Shutdown();

        
/// <summary>
        
/// 插件开发者相关信息
        
/// </summary>
        PluginInfo Information { get; }

        
/// <summary>
        
/// 插件帮助信息
        
/// </summary>
        string ConfigHelpHtml { get; }
    }
}


由于这个是最基础的接口类,所以包含的方法都是最能用的,每个类型的插件都应该会用到。这里顺便提一下 IHost 这个接口,这个是主系统函数调用的接口,可以放置一些插件系统里需要用到的一些常用方法和获取系统配置系统的函数等,以下我只提供2个接口以当演示,具体大家可根据自己需要去设计,因此接口并不是本系统的重点,代码如下:  

IHost.cs 文件:
代码
namespace CoderBlog.PluginFramework
{
    
/// <summary>
    
/// 这里只需提供常用的功能函数和配置信息即可
    
/// </summary>
    public interface IHost
    {
        
/// <summary>
        
/// 获取用户列表
        
/// </summary>
        
/// <returns></returns>
        List<UserInfo> GetUsers();

       
/// <summary>
       
/// 获取插件提供者配置文件
       
/// </summary>
       
/// <param name="providerTypeName">插件类型名称</param>
       
/// <returns></returns>
        string GetProviderConfiguration(string providerTypeName);
    }
}

 


接下来我们开始设计针对文本格式化的插件接口,此接口将继承上面的基类接口(后继开始所有类型的插件都应该继承自 IProvider)。

由于要对当前输出内容进行格式化,所以还需让插件开发者知道当前的上下文环境,以方便调用所需的信息,同时如果想再灵活些的话,可添加一个格式化对象的枚举,指明当前插件只格式化哪部分信息,因此需先创建一个 FormattingContext 枚举和 ContextInfo 类以传递所需的信息,可在一个 ContextInfo.cs 文件里包含以上2个类:

ContextInfo.cs 文件:
代码
namespace CoderBlog.PluginFramework
{
    
public class ContextInfo
    {
        
/// <summary>
        
/// 包含了所需的页面上下文信息
        
/// </summary>
        public class ContextInfo
        {
            
private FormattingContext context;
            
private HttpContext httpContext;
            
private string username;

            
/// <summary>
            
/// 初始化格式化对象所需的上下文对象
            
/// </summary>
            
/// <param name="context">格式化枚举对象</param>
            
/// <param name="httpContext">当前 http 上下文</param>
            
/// <param name="username">当前用户名</param>
            public ContextInfo(FormattingContext context,
                               HttpContext httpContext, 
string username)
            {
                
this.context = context;

                
this.httpContext = httpContext;
                
this.username = username;
            }

            
/// <summary>
            
/// 获取当前格式化枚举对象
            
/// </summary>
            public FormattingContext FormatContext
            {
                
get { return context; }
            }

            
/// <summary>
            
/// 获取当前 http 上下文
            
/// </summary>
            public HttpContext HttpContext
            {
                
get { return httpContext; }
            }

            
/// <summary>
            
/// 获取当前用户名
            
/// </summary>
            public string Username
            {
                
get { return username; }
            }
        }

        
/// <summary>
        
/// 格式化枚举,指定需要格式化哪部分的信息
        
/// </summary>
        public enum FormattingContext
        {
            
/// <summary>
            
/// 侧栏
            
/// </summary>
            Sidebar,
            
/// <summary>
            
/// 帖子标题
            
/// </summary>
            PostTitle,
            
/// <summary>
            
/// 帖子内容
            
/// </summary>
            PostContent,
            
/// <summary>
            
/// 页面头部
            
/// </summary>
            PageHeader,
            
/// <summary>
            
/// 页面底部
            
/// </summary>
            PageFooter,
            
/// <summary>
            
/// 未知
            
/// </summary>
            Unknown
        }
    }
}

 



OK,现在我们可以创建 IFormatterProvider 接口了:

IFormatterProvider.cs 文件:
代码
namespace CoderBlog.PluginFramework
{
    
public interface IFormatterProvider : IProvider
    {
        
/// <summary>
        
/// 对指定内容进行格式化
        
/// </summary>
        
/// <param name="content">需格式化的内容</param>
        
/// <param name="context">当前格式化对象上下文</param>
        
/// <returns>格式化后的内容</returns>
        string Format(string content, ContextInfo context);

        
/// <summary>
        
/// 格式化帖子标题
        
/// </summary>
        
/// <param name="title">需格式化的帖子标题</param>
        
/// <param name="context">当前格式化对象上下文</param>
        
/// <returns>格式化后的内容</returns>
        string FormatPostTitle(string title, ContextInfo context);

        
/// <summary>
        
/// 格式化页面头部内容
        
/// </summary>
        
/// <param name="head">需格式化的页面头部内容</param>
        
/// <param name="context">当前格式化对象上下文</param>
        
/// <returns>格式化后的内容</returns>
        string FormatPageHead(string head, ContextInfo context);

        
/// <summary>
        
/// 格式化页面底部
        
/// </summary>
        
/// <param name="foot">需格式化的页面底部内容</param>
        
/// <param name="context">当前格式化对象上下文</param>
        
/// <returns>格式化后的内容</returns>
        string FormatPageFoot(string foot, ContextInfo context);

       
/// <summary>
       
/// 格式化插件的执行优先级,此处很重要,因如多个格式化插件同时执行时,
       
/// 可能会覆盖其他的格式,所以要有个优先级问题
       
/// </summary>
        int ExecutionPriority { get; }
    }
}

 


此接口设计好后,以后所有与格式化相关的插件,都必须实现此接口,然后可根据插件作者需要实现不同的接口方法去格式化相应的内容。

OK,这篇就先讲到这吧,所需要的接口都定义好了,下一篇将详解如何实现相关的接口