插件式开发基本原理
动态加载符合条件的Dll,动态调用Dll的方法以及参数。
基本式例
1.首先新建一个控制台程序命名为PlugCSDN
2.建立一个类库程序存放接口命名为Iplug
3.定义基本接口
public interface IPlugin
{
string Show();
}
4.再新建一个插件类库命名为PlugModel,引用之前生成的Iplug.dll 并实现相关接口
public class Class1 : IPlugin
{
public string Show()
{
return "这是一个插件";
}
}
5.修改Iplug以及PlugModel的生成路径,使他们和PlugCSDN在同一个路径下
6.生成时设置项目依赖项,右击重新生成。在生成目录下可以看到我们新建的类库程序。
7.主程序PlugCSDN动态加载插件
首先获取程序的生成目录
var path = Environment.CurrentDirectory;
介绍几种常用的目录获取方法:
1、取得控制台应用程序的根目录方法
方法1、Environment.CurrentDirectory 取得或设置当前工作目录的完整限定路径
方法2、AppDomain.CurrentDomain.BaseDirectory 获取基目录,它由程序集冲突解决程序用来探测程序集2、取得Web应用程序的根目录方法
方法1、HttpRuntime.AppDomainAppPath.ToString();//获取承载在当前应用程序域中的应用程序的应用程序目录的物理驱动器路径。用于App_Data中获取
方法2、Server.MapPath(“”) 或者Server.MapPath(“~/”);//返回与Web服务器上的指定的虚拟路径相对的物理文件路径
方法3、Request.ApplicationPath;//获取服务器上ASP.NET应用程序的虚拟应用程序根目录3、取得WinForm应用程序的根目录方法
1、Environment.CurrentDirectory.ToString();//获取或设置当前工作目录的完全限定路径
2、Application.StartupPath.ToString();//获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称
3、Directory.GetCurrentDirectory();//获取应用程序的当前工作目录
4、AppDomain.CurrentDomain.BaseDirectory;//获取基目录,它由程序集冲突解决程序用来探测程序集
5、AppDomain.CurrentDomain.SetupInformation.ApplicationBase;//获取或设置包含该应用程序的目录的名称其中:以下两个方法可以获取执行文件名称
1、Process.GetCurrentProcess().MainModule.FileName;//可获得当前执行的exe的文件名。
2、Application.ExecutablePath;//获取启动了应用程序的可执行文件的路径,包括可执行文件的名称获取.net的根目录的方法
方法1:System.Web.HttpContext.Current.Request.PhysicalApplicationPath
方法2:System.Web.HttpContext.Current.Server.MapPath(“./”)总注:Server.MapPath获得的路径都是服务器上的物理路径,也就是常说的绝对路径 1、Server.MapPath(“/”)
注:获得应用程序根目录所在的位置,如 C:\Inetpub\wwwroot\。 2、Server.MapPath(“./”)
注:获得所在页面的当前目录,等价于Server.MapPath(“”)。 3、Server.MapPath(“…/”)
注:获得所在页面的上级目录。 4、Server.MapPath(“~/”)
注:获得当前应用级程序的目录,如果是根目录,就是根目录,如果是虚拟目录,就是虚拟目录所在的位置,如C:\Inetpub\wwwroot\Example\。// 获取程序的基目录。 System.AppDomain.CurrentDomain.BaseDirectory
// 获取模块的完整路径。
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
//可获得当前执行的exe的文件名。// 获取和设置当前目录(该进程从中启动的目录)的完全限定目录。 System.Environment.CurrentDirectory
//备注
按照定义,如果该进程在本地或网络驱动器的根目录中启动,则此属性的值为驱动器名称后跟一个尾部反斜杠(如“C:\”)。如果该进程在子目录中启动,则此属性的值为不带尾部反斜杠的驱动器和子目录路径(如“C:\mySubDirectory”)。// 获取应用程序的当前工作目录。 System.IO.Directory.GetCurrentDirectory()
// 获取和设置包括该应用程序的目录的名称。
System.AppDomain.CurrentDomain.SetupInformation.ApplicationBaseAppDomain.CurrentDomain.BaseDirectory;//获取基目录,它由程序集冲突解决程序用来探测程序集。
// 获取启动了应用程序的可执行文件的路径。 System.Windows.Forms.Application.StartupPath
//获取启动了应用程序的可执行文件的路径,不包括可执行文件的名称。// 获取启动了应用程序的可执行文件的路径及文件名
System.Windows.Forms.Application.ExecutablePath
//获取启动了应用程序的可执行文件的路径,包括可执行文件的名称。C#
WinForm中AppDomain.CurrentDomain.BaseDirectory与Application.StartupPath的区别示例如下:private void Frm_Server_Load(object sender, EventArgs e) {
MessageBox.Show(AppDomain.CurrentDomain.BaseDirectory);
MessageBox.Show(Application.StartupPath ); }说明:
AppDomain.CurrentDomain.BaseDirectory 返回结果为: D:\mycode\
Application.StartupPath 返回结果为: D:\mycode
Application.StartupPath 只能用于WinForm窗体中,而AppDomain.CurrentDomain.BaseDirectory既可以用于WinForm窗体中,也可以用于类库DLL文件中.
其次遍历Dll文件找到符合Iplug接口的程序,并且调用方法。
var path = Environment.CurrentDirectory;
foreach (var file in new DirectoryInfo(path).GetFiles())
{
if (!file.FullName.EndsWith(".dll"))
continue;
Assembly ab = Assembly.LoadFrom(file.FullName);
Type[] types = ab.GetTypes();
foreach (Type t in types)
{
//如果某些类实现了预定义的插件接口,则认为该类适配与主程序(是主程序的插件)
if (t.GetInterface("IPlugin") != null)
{
object o = ab.CreateInstance(t.FullName);//创建该类实例
MethodInfo method = t.GetMethod("Show");//获得该类某方法
object returnValue = method.Invoke(o, null);//调用该方法
Console.WriteLine(returnValue?.ToString());
}
}
}
8.显示结果,成功加载插件。
进阶样例
上一段落展示了基本的样例,其中存在的问题是,接口定义较为固定,当接口定义完成,其他插件引用接口后,接口如果发生修改会导致所有程序都需要修改。所以我们采用动态的方法加载插件的操作,接口只定义基本规则,不定义操作方法。具体内容如下:
1.修改Iplug接口,其中Show[] 表示插件支持的操作,Operate表示实现传入的操作名称并实现。
public interface IPlugin
{
string[] Show();
bool Operate(string name);
}
2.在PlugModel中添加新的类
internal class Oper
{
public static Dictionary<string, Func<bool>> dics = new Dictionary<string, Func<bool>>
{
{"操作1",Op1 },
{"操作2",Op2 }
};
public static bool Operate(string name)
{
if (!dics.ContainsKey(name))
return false;
return dics[name].Invoke();
}
public static bool Op1()
{
Console.WriteLine("操作1实现");
return true;
}
public static bool Op2()
{
Console.WriteLine("操作2实现");
return true;
}
}
其中 public static Dictionary<string, Func> dics 是用来存放操作名称以及方法的键值对。
3.在PlugModel中实现接口
public class Class1 : IPlugin
{
public bool Operate(string name)
{
return Oper.Operate(name);
}
public string[] Show()
{
return Oper.dics.Keys.ToArray();
}
}
其中Show[] 是用来显示操作名称,Operate是用来实现操作的。
4.动态加载插件
修改PlugCSDN代码
public static void AddPlug()
{
//加载组件DLL
var path = Environment.CurrentDirectory;
foreach (var file in new DirectoryInfo(path).GetFiles())
{
if (!file.FullName.EndsWith(".dll"))
continue;
Assembly ab = Assembly.LoadFrom(file.FullName);
Type[] types = ab.GetTypes();
foreach (Type t in types)
{
//如果某些类实现了预定义的插件接口,则认为该类适配与主程序(是主程序的插件)
if (t.GetInterface("IPlugin") != null)
{
object o = ab.CreateInstance(t.FullName);//创建该类实例
MethodInfo method = t.GetMethod("Show");//获得该类某方法
object returnValue = method.Invoke(o, null);//调用该方法
foreach (string o2 in (string[])returnValue)
Console.WriteLine(o2?.ToString());
MethodInfo method1 = t.GetMethod("Operate");//获得该类某方法
object returnValue1 = method1.Invoke(o, new object[] { "操作1" });//调用该方法
object returnValue2 = method1.Invoke(o, new object[] { "操作2" });//调用该方法
}
}
}
}
5.运行结果
以上就是插件是开发的核心内容