使用插件机制来扩展B/S程序,主要需要实现两个功能,首先是动态编译插件中的C#代码,然后利用反射来执行插件已经编译的C#代码。
(原文地址:木子博客 http://blog.moozi.net/read-228.html)
一、动态编译
插件一般是以xml文件的形式实现其配置,必须要先读取插件中的C#代码才能进行动态编译。
try
{
//获取插件配置文件
doc.Load(Server.MapPath(@"plugins/" + strPluginName + "/config.xml"));
}
catch (Exception e)
{
Response.Write("插件载入错误:" + e.ToString());
return;
}
XmlNode xn = doc.DocumentElement.SelectSingleNode("csharpcode");
string code = string.Empty;
if (xn != null)
{
XmlElement xe = (XmlElement)xn;
if (xe.HasAttribute("link"))
{
XmlDocument doc = new XmlDocument();
try
{
//获取插件配置文件
doc.Load(Server.MapPath(@"plugins/" + strPluginName + "/config.xml"));
}
catch (Exception e)
{
Response.Write("插件载入错误:" + e.ToString());
return;
}
XmlNode xn = doc.DocumentElement.SelectSingleNode("csharpcode");
string code = string.Empty;
if (xn != null)
{
XmlElement xe = (XmlElement)xn;
if (xe.HasAttribute("link"))
{
using (
StreamReader sr =
System.IO.File.OpenText(
Path.Combine(
Server.MapPath(@"plugins/" + strPluginName + "/"), xe.GetAttribute("link")
)
)
)
{
code = sr.ReadToEnd();
sr.Close();
}
}
else
{
code = xe.InnerText;
}
}
.Net为我们提供了很强大的支持来实现这一切我们可以去做的基础,主要应用的两个命名空间是:System.CodeDom.Compiler和Microsoft.CSharp或Microsoft.VisualBasic。另外还需要用到反射来动态执行你的代码。动态编译并执行代码的原理其实在于将提供的源代码交予CSharpCodeProvider来执行编译(其实和CSC没什么两样),如果没有任何编译错误,生成的IL代码会被编译成DLL存放于于内存并加载在某个应用程序域(默认为当前)内并通过反射的方式来调用其某个方法或者触发某个事件等。之所以说它是插件编写的一种方式也正是因为与此,我们可以通过预先定义好的借口来组织和扩展我们的程序并将其交还给主程序去触发。一个基本的动态编译并执行代码的步骤包括:
将要被编译和执行的代码读入并以字符串方式保存
声明CSharpCodeProvider对象实例
调用CSharpCodeProvider实例的CompileAssemblyFromSource方法编译
用反射生成被生成对象的实例(Assembly.CreateInstance
调用其方法
以下代码片段包含了完整的编译和执行过程:
ICodeCompiler compiler = new CSharpCodeProvider().CreateCompiler();
CompilerParameters para = new CompilerParameters();
para.ReferencedAssemblies.Add("System.dll");
para.ReferencedAssemblies.Add("System.Data.dll");
para.ReferencedAssemblies.Add("System.XML.dll");
para.ReferencedAssemblies.Add("System.Drawing.dll");
para.ReferencedAssemblies.Add("System.Web.dll");
para.ReferencedAssemblies.Add(Server.MapPath(this.Request.ApplicationPath + "/bin/PluginDemo.dll"));
para.GenerateExecutable = false;
para.OutputAssembly = dllPath;
//编译结果
CompilerResults cr = compiler.CompileAssemblyFromDom(para, unit);
if (cr.Errors.Count > 0)
{
Response.Write("插件安装失败,编译错误:<br/>");
//遍历编译错误
foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
{
Response.Write(ce.ErrorText + "(文件:" + ce.FileName + ",行号:" + ce.Line.ToString() + ",错误编号:" + ce.ErrorNumber + ")<br/>");
}
return;
}
这里引用了 System.CodeDom和System.CodeDom.Compiler。
读取插件中的C#代码并进行动态编译后,要实现插件功能,就需要执行已经编译好的插件的dll中的代码了。
二、利用反射实现插件功能
反射的原理这里就不再说了,三层架构中用的太多了,呵呵。
{
try
{
//载入插件程序集
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.Load(Server.MapPath("plugins/installedplugins.xml"));
string asmName = ((System.Xml.XmlElement)doc.DocumentElement.SelectSingleNode("plugin[@name='" + strPluginName + "']")).GetAttribute("assembly");
System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFrom(Server.MapPath(@"plugins/bin/" + asmName));
IPlugin plugin = (IPlugin)asm.CreateInstance("PluginDemo.plugins." + strPluginName);
return plugin.GetOutput(this);
}
catch (Exception ex)
{
return "插件‘" + strPluginName + "’载入失败:" + ex.ToString();
}
}
忘了说一下,要实现反射,我们需要先在网站中定义好的一个IPlugin接口
/// 插件接口
/// </summary>
public interface IPlugin
{
/// <summary>
/// 获取插件输出的内容
/// </summary>
/// <param name="page">System.Web.UI.Page</param>
/// <returns>字符串</returns>
string GetOutput(Page page);
/// <summary>
/// 安装插件时执行的代码
/// </summary>
/// <param name="page">System.Web.UI.Page</param>
void Install(Page page);
/// <summary>
/// 卸载插件时执行的代码
/// </summary>
/// <param name="page">System.Web.UI.Page</param>
void Uninstall(Page page);
/// <summary>
/// 更新插件时执行的代码
/// </summary>
/// <param name="page">System.Web.UI.Page</param>
void Update(Page page);
}
然后所有的插件的C#代码都继承自这一个接口。
还是把我做的DEMO发上来吧,讲的不好,大家还是看源码吧,里面都有注释,呵呵。