.NET 插件系统框架设计
(一)框架整体构想
在应用程序开发过程中,存在很多模块重复开发的烦恼,于是收集资料开始动手,设计开发这插件系统框架,框架用于整合各个子插件。
设计阶段一:将接口定义成单独程序集,让插件去实现这个接口,框架采用反射来完成子插件功能的读取。
问题:
1. 要定义若干接口。
2. 对于现在已存的模块修改太大。
设计阶段二:着力于对第一阶段第二个问题解决,在协议接口和插件间添加一个适配器,有了这样的结构,就能将现有已存在模块方便地与主机框架整合
设计阶段三:在阶段二的启发下,解决在阶段一中的要事先定义若定义接口的问题。改进,在接口协议与主机框架间加适器,让接口协议义与主机开发断开
问题:插件与协议,主机-协议藕合较大。适配器操作麻烦。
阶段四:框架完善:在阶段三的基本上,插件及主机本身实现也预定义接口(视图),通过这一框架设计,主机系统及插件可以互不干扰地开发(为方便后期整合,插件及主机开发均定义成接口),开发完成后只需要添加一个接口协议及两人适配器就完成了主机与插件整合。
插件获取主程序数据
解决方案分组 | 项目名称 | 说明 |
Contract | Codemarks.Framework.Plugin.Contract | 程序集间协议 |
AddIn Side | Codemarks.Framework.Plugin.View.User | 插件功能接口定义 |
AddIn Side | Codemarks.Framework.Plugin.Adapter.User | 插件类适配器 |
AddIn Side | Codemarks.Framework.Plugin.AddIn.User | 用户插件类具体实现 |
AddIn Side | Codemarks.Framework.Plugin.Addin.Model | 主机操作实现类 |
Host Side | Codemarks.Framework.Plugin.Host.View.User | 主机功能接口定义 |
Host Side | Codemarks.Framework.Plugin.Host.Adapter.User | 主机协议适配器 |
Host Side | Codemarks.Framework.Plugin.Host.User | 用户插件类具体实现 |
Host Side | Codemarks.Framework.Plugin.Host.Model | 用户插件实体() |
在这个插件框架中,主要解决的问题为主机与插件相互通信,比如在日志插件中会验证用户是否有发表权限,日志插件并不会直去访问用户中心插件,而是向主机请求(如图),而主机在接受到请求后会调用用户中心插件,完成日志插件所请求的功能。在插件与主机的数据通信中主要有两和上操作:
1. 主机获取插件数据:如日志中请求用户请求用户权限数据。
2. 插件获取主机数据,如日志插件请求用户数据,主机将会通过反射创建用户对象返回供日志插件用。
主机插件关系图
日志插件请求用户数据
项目分析:
Contract项目:
事先定义插件与主机交互协议,定义接口IUserContract IUserInfo(实体类) 两个接口,
IUserContract 继承于IContract 预留接口(负责插件程序生命周期及权限相关接口)
IUsercontract 接口定义方法添加用户方法
Bool AddUser(IUserInfo userInfo) IUserInfo
GetUserInfoByName(string userName);
AddIn Sider结构
插件程序只需实现协议中所定义的方法,这样就能适现主机与插件的通信,为了方便后期整合,插件系统本身的功能我们先定义接口,1 2 3 项目并没有跟协义没有关系。
1 | AddIn Side | Codemarks.Framework.Plugin.View.User | 用户插件功能接口定义 |
2 | AddIn Side | Codemarks.Framework.Plugin.AddIn.User | 用户插件类具体实现 |
3 | AddIn Side | Codemarks.Framework.Plugin.Addin.Model | 主机操作实现类 |
4 | AddIn Side | Codemarks.Framework.Plugin.Adapter.User | 插件类适配器 |
//插件中的自定义实体类
//插件中UserView插件中功能接口
//用户件中UserView接口具体实现
以上代码为独立代码与接口协议没有关系
//适配器实现接口协议IUserContract 中的AddUser(IUserInfo) 与GetUserInfoByName();
在主机请求添加用户时会传入一个IUserInfo 接口,而在用户插件需求参数为CustomUserInfo 参数类型不同,在GetUserInfoByName(string userName) 返回值为ICustomUserInfo ,我们在适配器类中实现CustomUserInfo 到UserInfo 转换 及UserInfo到CustomUserInfo 转换
public interface UserView
{
CustomUserInfo GetUserInfoByName(string userName);
int AddUser(CustomUserInfo userInfo);
}
//插件实现类转化为协议类
定义CustomUserInfoToContractAdapter实现接口协议IUserInfo接口
定义 private CustomUserInfo addIn;通过构程函数进行赋值,在Name get 属性时返回addIn.Name;
Set 方法同理
/// <summary>
/// 从自定义实体转为协议
/// </summary>
internal class CustomUserInfoToContractAdapter : IUserInfo
{
private CustomUserInfo addIn;
public CustomUserInfoToContractAdapter(CustomUserInfo customUserInfo)
{
this.addIn = customUserInfo;
}
#region IUserInfo 成员
public string Name
{ get{return addIn.Name;}
Set{}
}
…其它属性
#endregion
}
//插件实现类转化为协议类
定义ContractTCustomUserInfoAdapter继承于插件实体类CustomUserInfo,定义 private IUserInfo contract;通过构造函数进行contract 赋值,在Name get 属性时return contract.Name;
internal class ContractToCuntomUserInfoAdapter : CustomUserInfo
{
private IUserInfo contract;
public ContractToCuntomUserInfoAdapter(IUserInfo iuserInfo)
{
this.contract = iuserInfo;
}
#region CumtomUserInfo Member
public override string Name
{
get { return contract.Name; }
set { base.Name = value; }
}
}
//UserInfoAdapter实体类适配器
/// <summary>
/// 协议与插件实体类互转适配器集合
/// </summary>
public class UserInfoAdapters
{
public static IUserInfo ContractToAddIn(CustomUserInfo customUserInfo)
{
return new CustomUserInfoToContractAdapter(customUserInfo); //调用转换类
}
public static CustomUserInfo AddInToContract(IUserInfo iuserInfo)
{
return new ContractToCuntomUserInfoAdapter(iuserInfo);
}
}
//UserAdapter类
public class UserAdapter : IUserContract
{
private cn.CodeMarks.Framework.Plugin.View.UserView view = null;
public UserAdapter()
{
this.view = new UserAddIn();
}
public UserAdapter(UserView userView)
{
this.view = userView;//创建插件具体实现
}
#region IUserContract 成员
/// <summary>
/// 根据用户名返回用户对象
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public cn.CodeMarks.Framework.Plugin.Contract.Entity.IUserInfo GetUserInfoByName(string userName)
{
通过用户名查用户对象CustomUserInfo 转换为IUserInfo 接口
return UserInfoAdapters.ContractToAddIn(view.GetUserInfoByName(userName));
}
public int AddUser(IUserInfo userInfo)
{
return view.AddUser(UserInfoAdapters.AddInToContract(userInfo));
}
#endregion
}
主机请求插件数据时序图
(二) 使用对象序列化实现自定义配置文件管理
一、 为什么要这样做
问题:在程序开发中,经常会涉及对xml的操作,在c#中常用的方法有两种
1. xpath解析
2. XmlDocument解析
在解析时什么很麻烦,c#提供了xml序列化的方法,非常方便进实现xml和以象间的转换,在插件系统框架程序中,实现插件的管理配置,使用序列化确实方便了不少,扩展性非常方便,有些朋友会提到性能的问题,在这个系统中,结合缓存问题不是问题。
二、 怎么样来做概况
首先来看类结构图:
类名 | 说明 | 备注 |
FrameworkConfiguraconfigManager | 配置文件管理类 | 实现config管理,load、save |
FrameworkConfiguraconfig | 配置文件类 | |
PluginSettings | 插件配置文件集合适配器 | 利用索引实现集合访问 |
PluginSetting | 插件配置文件 | 例如UserCenterPlugin |
AssemblyFile | 插件对应配置文件 | |
AppSettings | 应用程序配置集合适配器 | 得 key value |
AppSetting | 应用程序配置 |
本文主要是实现对象读取与保存,类结构就不做过多分析,主要看FrameworkConfiguration 、FrameworkConfigurationManager类
FrameworkConfigurationManager实现FrameworkConfiguration 保存与读取,FrameworkConfiguration包含了AppSettings对象及PluginSettiongs对象,后续还可以扩展。
关键代码
//名称空间引入
using System.IO;
using System.Xml.Serialization;
//指定xml序列化属性 根结点名间
[Serializable]
[XmlRootAttribute("configuration", Namespace = "http://www.codemarks.net ")]
public class FrameworkConfiguration
//序列化保存
[XmlElementAttribute(ElementName = "pluginSetting", IsNullable = false)]
public List<PluginSetting> Items
{
get { return _items; }
set { _items = value; }
}
public void Save()
{
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
XmlSerializer ser = new XmlSerializer(typeof(FrameworkConfiguration), new Type[] {
typeof(PluginSettings),
typeof(PluginSetting),
typeof(AssemblyFile),
});
ser.Serialize(fs, this.config);
fs.Close();
}
//序列化读取
public void Load()
{
//不存配置文件
if (!File.Exists(fileName))
{
this.config = new FrameworkConfiguration();
return;
}
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
XmlSerializer ser = new XmlSerializer(typeof(FrameworkConfiguration), new Type[] {
typeof(PluginSetting),
typeof(PluginSettings),
typeof(AssemblyFile)
});
this.config = (FrameworkConfiguration)ser.Deserialize(fs);
fs.Close();
}
在Test类中读到插件对象
FrameworkConfigurationManager.Instance.Config.PluginSettings["UserCenter"]; // UserCenter为插件名称
结果
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<pluginSettings>
<plugin name="UserPlugin" author="codemarks" descript="" version="1.0" webroot="/plugin/user/web/">
<assemblys>
<assembly name="CodeMarks.Framework.Plugin.Adapter.User" descript="">cn.CodeMarks.Framework.Plugin.Adapter.UserAdapter</assembly>
<assembly name="CodeMarks.Framework.Plugin.AddIn.User" descript=""/>
<assembly name="CodeMarks.Framework.Plugin.Model.User" descript=""/>
<assembly name="CodeMarks.Framework.Plugin.View.User" descript=""/>
</assemblys>
</plugin>
</pluginSettings>
<appSettings>
<add key="pluginRoot" value="/Plugin/"></add>
</appSettings>
</configuration>
三、 问题及其它四、 完整代码
//FrameworkConfiguraconfigManager完成代码
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
namespace cn.CodeMarks.Framework.Plugin
{
public class FrameworkConfigurationManager
{
private static FrameworkConfigurationManager instance = null;
public static FrameworkConfigurationManager Instance
{
get
{
if (FrameworkConfigurationManager.instance == null)
{
FrameworkConfigurationManager.instance = new FrameworkConfigurationManager();
}
return FrameworkConfigurationManager.instance;
}
}
private FrameworkConfiguration config = new FrameworkConfiguration();
public FrameworkConfiguration Config
{
get { return config; }
set { config = value; }
}
public void Save()
{
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
XmlSerializer ser = new XmlSerializer(typeof(FrameworkConfiguration), new Type[] {
typeof(PluginSettings),
typeof(PluginSetting),
typeof(AssemblyFile),
});
ser.Serialize(fs, this.config);
fs.Close();
}
private string fileName = "FrameworkConfig.xml";
public void Load()
{
//不存配置文件
if (!File.Exists(fileName))
{
this.config = new FrameworkConfiguration();
return;
}
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
XmlSerializer ser = new XmlSerializer(typeof(FrameworkConfiguration), new Type[] {
typeof(PluginSetting),
typeof(PluginSettings),
typeof(AssemblyFile)
});
this.config = (FrameworkConfiguration)ser.Deserialize(fs);
fs.Close();
}
public void TestData()
{
PluginSetting ps1 = new PluginSetting();
ps1.PluginName = "UserPlugin";
ps1.Author = "codemarks";
ps1.Descript = "";
ps1.Version = "1.0";
ps1.WebRoot = "/plugin/user/webroot/";
AssemblyFile af1 = new AssemblyFile();
af1.Name = "CodeMarks.Framework.Plugin.Adapter.User";
af1.AdapterClassName = "CodeMarks.Framework.Plugin.Adapter.User.UserAdapter";
ps1.Items.Add(af1);
this.config.PluginSettings.AddPlugin(ps1);
PluginSetting ps = new PluginSetting();
ps.PluginName = "UserPlugin2";
ps.Author = "codemarks2";
ps.Descript = "";
ps.Version = "1.0";
ps.WebRoot = "/plugin/user/webroot/";
AssemblyFile af = new AssemblyFile();
af.Name = "CodeMarks.Framework.Plugin.Adapter.User2";
af.AdapterClassName = "CodeMarks.Framework.Plugin.Adapter.User.UserAdapter";
ps.Items.Add(af);
this.config.PluginSettings.AddPlugin(ps);
this.config.AppSettings.Items.Add(new AppSetting("conn", ".\\nbaqn"));
this.config.AppSettings.Items.Add(new AppSetting("config", "frame.config"));
this.config.AppSettings.Items.Add(new AppSetting("catch", "sql"));
}
}
}
//FrameworkConfiguraconfig完整代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace cn.CodeMarks.Framework.Plugin
{
[Serializable]
[XmlRootAttribute("configuration", Namespace = "http://www.codemarks.net ", IsNullable = false)]
public class FrameworkConfiguration
{
public FrameworkConfiguration()
{
_PluginSettings = new PluginSettings();
_appSettings = new AppSettings();
}
private PluginSettings _PluginSettings;
/// <summary>
/// 插件配置
/// </summary>
public PluginSettings PluginSettings
{
get { return _PluginSettings; }
set { _PluginSettings = value; }
}
private AppSettings _appSettings;
public AppSettings AppSettings
{
get { return _appSettings; }
set { _appSettings = value; }
}
}
}
//PluginSettings完整代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
namespace cn.CodeMarks.Framework.Plugin
{
[Serializable]
public class PluginSettings
{
public PluginSettings()
{
_items = new List<PluginSetting>();
}
private List<PluginSetting> _items;
[XmlElementAttribute(ElementName = "pluginSetting", IsNullable = false)]
public List<PluginSetting> Items
{
get { return _items; }
set { _items = value; }
}
public PluginSetting this[string pluginName]
{
get
{
return GetPlugin(pluginName);
}
}
/// <summary>
/// 根据插件名称获取插件
/// </summary>
/// <param name="plugName"></param>
/// <returns></returns>
public PluginSetting GetPlugin(string plugName)
{
PluginSetting ret = null;
foreach (PluginSetting plug in Items)
{
if (plugName == plug.PluginName)
{
ret = plug;
break;
}
}
return ret;
}
/// <summary>
/// 添加插件成功返回true
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
public bool AddPlugin(PluginSetting plugin)
{
bool ret = false;
foreach (PluginSetting plug in Items)
{
if (plugin.PluginName == plug.PluginName)
{
ret = true;
break;
}
}
if (!ret)
{
Items.Add(plugin);
}
return ret;
}
/// <summary>
/// 移除插件,成功返回true
/// </summary>
/// <param name="pluginName"></param>
/// <returns></returns>
public bool ReMovePlugin(string pluginName)
{
bool ret = false;
foreach (PluginSetting plug in Items)
{
if (pluginName == plug.PluginName)
{
Items.Remove(plug);
ret = true;
break;
}
}
return ret;
}
}
//PluginSetting完整代码
[Serializable]
public class PluginSetting
{
public PluginSetting()
{
_items = new List<AssemblyFile>();
}
private string _pluginName;
/// <summary>
/// 插件名称
/// </summary>
[XmlAttribute("name")]
public string PluginName
{
get { return _pluginName; }
set { _pluginName = value; }
}
private string _descript;
/// <summary>
/// 插件描述
/// </summary>
[XmlAttribute("descript")]
public string Descript
{
get { return _descript; }
set { _descript = value; }
}
private string _version;
/// <summary>
/// 插件版本
/// </summary>
[XmlAttribute("version")]
public string Version
{
get { return _version; }
set { _version = value; }
}
private string _author;
/// <summary>
/// 插件作者
/// </summary>
[XmlAttribute("author")]
public string Author
{
get { return _author; }
set { _author = value; }
}
private string webRoot;
/// <summary>
/// 页面路径
/// </summary>
[XmlAttribute("webroot")]
public string WebRoot
{
get { return webRoot; }
set { webRoot = value; }
}
private List<AssemblyFile> _items;
[XmlArrayAttribute("assemblyFiles")]
public List<AssemblyFile> Items
{
get { return _items; }
set { _items = value; }
}
}
}
//AssemblyFile完整代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
namespace cn.CodeMarks.Framework.Plugin
{
[Serializable]
public class AssemblyFile
{
string name;
[XmlAttribute("name")]
public string Name
{
get { return name; }
set { name = value; }
}
string descript;
[XmlAttribute("descript")]
public string Descript
{
get { return descript; }
set { descript = value; }
}
string adapterClassName;
[XmlAttribute("adapter")]
public string AdapterClassName
{
get { return adapterClassName; }
set { adapterClassName = value; }
}
}
}
//AppSettings完整代码
//AppSetting完整代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace cn.CodeMarks.Framework.Plugin
{
public class AppSettings
{
public AppSettings()
{
_items = new List<AppSetting>();
}
private List<AppSetting> _items;
[XmlElementAttribute(ElementName = "add", IsNullable = false)]
public List<AppSetting> Items
{
get { return _items; }
set { _items = value; }
}
/// <summary>
/// 根据key获取配置对象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public AppSetting this[string key]
{
get
{
AppSetting ret = null;
foreach (AppSetting app in Items)
{
if (app.Key == key)
{
ret = app;
break;
}
}
return ret;
}
}
public AppSetting this[AppSetting appSetting]
{
set
{
bool isAlready = false;
foreach (AppSetting app in Items)
{
if (app.Key == appSetting.Key)
{
isAlready = true;
break;
}
}
if (!isAlready)
{
Items.Add(appSetting);
}
}
}
}
/// <summary>
/// 应用程序设置
/// </summary>
public class AppSetting
{
public AppSetting()
{
}
public AppSetting(string _key, string _value)
{
this.Key = _key;
this.Value = _value;
}
private string _key;
[XmlAttribute("key")]
public string Key
{
get { return _key; }
set { _key = value; }
}
private string value;
[XmlAttribute("value")]
public string Value
{
get { return this.value; }
set { this.value = value; }
}
}
}
//生成的xml文件
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<pluginSettings>
<plugin name="UserPlugin" author="codemarks" descript="" version="1.0" webroot="/plugin/user/web/">
<assemblys>
<assembly name="CodeMarks.Framework.Plugin.Adapter.User" descript="">cn.CodeMarks.Framework.Plugin.Adapter.UserAdapter</assembly>
<assembly name="CodeMarks.Framework.Plugin.AddIn.User" descript=""/>
<assembly name="CodeMarks.Framework.Plugin.Model.User" descript=""/>
<assembly name="CodeMarks.Framework.Plugin.View.User" descript=""/>
</assemblys>
</plugin>
</pluginSettings>
<appSettings>
<add key="pluginRoot" value="/Plugin/"></add>
</appSettings>
</configuration>