架构深渊

慢慢走进程序的深渊……关注领域驱动设计、测试驱动开发、设计模式、企业应用架构模式……积累技术细节,以设计架构为宗。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

基于ASP.NET Web Application的插件实现,附DEMO

Posted on 2008-11-20 21:13  chen eric  阅读(356)  评论(1编辑  收藏  举报

 

 

最近的项目中涉及到Asp.Net扩展性,考虑了两种扩展方法,一种是使用Web Services实现,另一种是动态生成代码然后利用反射来实现插件功能。现在网上博客什么的都是用插件的形式来扩展功能的,所以我也打算用插件来扩展asp.net网站的功能。

    使用插件机制来扩展B/S程序,主要需要实现两个功能,首先是动态编译插件中的C#代码,然后利用反射来执行插件已经编译的C#代码。

     (原文地址:木子博客  http://blog.moozi.net/read-228.html

    一、动态编译

    插件一般是以xml文件的形式实现其配置,必须要先读取插件中的C#代码才能进行动态编译。

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")) 
    

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
调用其方法

以下代码片段包含了完整的编译和执行过程:

CodeSnippetCompileUnit unit = new CodeSnippetCompileUnit(code); 
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中的代码了。

    二、利用反射实现插件功能

    反射的原理这里就不再说了,三层架构中用的太多了,呵呵。

public string GetPluginOutPut(string strPluginName) 

    
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> 
/// 插件接口 
/// </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发上来吧,讲的不好,大家还是看源码吧,里面都有注释,呵呵。

下载演示DEMO