目的:模板文件中支持程序语言,以便于输出一些数据,而非一般的模板文件替换方案,借此开发自主的MVC模板支持
思路:读取模板文件中的语句块,然后生成真实的C#代码,调用编译器编译并保存,下次直接装载该编译过的类库,支持运行
一:模板
确定模板文件中的语句块,采用正则表达式读取
参见如下模板:
- <?xml version="1.0" encoding="utf-8" ?>
- <NXDO.WebUI.Faces>
- <html index="0" des="">
- <![CDATA[
- <table width="100%" border="1">
- <@imp="DemoMvcLib.Controllers"/>
- <@server>
- List<Class1> lst = (List<Class1>)(${lst});
- int i = 0;
- foreach(Class1 cls in lst)
- {
- ${cls} = cls;
- ${ii} = ++i;
- </@server>
- <tr>
- <td><@out=${ii}/></td>
- <td><@out=${cls.Name}/></td>
- <td><@out=${cls.Sex}/></td>
- </tr>
- <@server>
- }
- </@server>
- </table>
- ]]>
- </html>
- </NXDO.WebUI.Faces>
得到<@server>与</@server>之间的代码,将 ${cls},类似与这样的变量全部替换成 env["cls"].
env是一个脚本与host之间交换数据的媒介对象,它实现了IDictionary<string, object>接口.
这一段有输出,则直接解释成 String,生成代码时,将<@out=${ii}/>替换成 env.Out("ii");
- <tr>
- <td><@out=${ii}/></td>
- <td><@out=${cls.Name}/></td>
- <td><@out=${cls.Sex}/></td>
- </tr>
Out是env对象的输出方法,env.Out("ii")返回 ii 的值.
Out主要通过传入的字符判断,得到集合中的值并采用反射完成,不带.直接调用集合中的值ToString().
带有.的,则通过反射得到成员的值,记住采用递归调用,可能开发人员输入多个.
二:生成的代码
- [Serializable]
- public class default2_FaceHtml1_0 : MarshalByRefObject, IScript
- {
- public default2_FaceHtml1_0(ScriptContext env){
- this._env = env;
- }
- public string GetHtml(){
- StringBuilder builder = new StringBuilder();
- List<Class1> list = (List<Class1>) env["lst"];
- int ii = 0;
- foreach (Class1 cls in list)
- {
- env["cls"] = cls;
- env["ii"] = ++ii;
- string str = "<tr>" +
- "<td>" + env.Out("ii") + "</td>" +
- "<td>" + env.Out("cls.Name") + "</td>" +
- "<td>" + env.Out("cls.Sex") + "</td>" +
- "</tr>";
- builder.AppendLine(str);
- }
- return builder.ToString();
- }
- public override object InitializeLifetimeService(){return null;}
- public ScriptContext env{
- get{
- return this._env;
- }
- }[NonSerialized]ScriptContext _env;
- }
说明:继承自MarshalByRefObject,便于在第二次调用时,新建一个AppDomain调用,实现IScript,便于反射得到实例后调用 GetHtml方法
新建一个AppDomain的原因,主要考虑如果模板发生更改,则不装载而是重新编译,替换上一次编译的结果.在新的AppDomain中运行.可以不锁定该DLL加载default2_FaceHtml1_0类,编译以后就可以覆盖.
判断是否需要重新编译脚本的条件为:比较生成的DLL与模板文件的最后一次更新时间
三:编译以上生成的原代码
贴出代码段:
- string DyOutName = "Script." + this.className + ".dll";
- string DyDllName = Path.Combine(outDllsDir, DyOutName);//outDllsDir编译后保存DLL的路径
- CodeDomProvider prov = new CSharpCodeProvider();
- CompilerParameters parms = new CompilerParameters();
- parms.ReferencedAssemblies.Add("System.dll");
- //...其它需要的引用
- parms.GenerateInMemory = true;
- parms.OutputAssembly = DyDllName;
- CompilerResults cr = prov.CompileAssemblyFromSource(parms, sCode); //sCode为上面产生的代码
- Assembly asm = cr.CompiledAssembly;
- Type type = asm.GetType("NXDO.WebUI.Script." + this.className);
- IScript iscript = type.GetConstructor(new Type[] { scriptCnxt.GetType() }).Invoke(new object[] { scriptCnxt }) as IScript; //scriptCnxt就是代码段中的 env
- string sReturnHtml = iscript.GetHtml(); //执行
四:如果DLL文件存在,则建立新的AppDomain运行
贴出代码段:
- if (needCompiler())//比较生成的DLL与模板文件的最后一次更新时间
- {
- //调用第三部 {编译以上生成的原代码}
- ...
- return;
- }
- //直接装载并运行
- AppDomainSetup setup = new AppDomainSetup();
- setup.ApplicationBase = binPath;
- setup...其它属性设置
- AppDomain appDomain = AppDomain.CreateDomain("Script Domain", null, setup);
- string webuiDll = Path.Combine(binPath, "NXDO.WebUI.dll"); //ScriptAppDomain此类,存在NXDO.WebUI.dll中
- ScriptAppDomain scriptDomain = appDomain.CreateInstanceFromAndUnwrap(webuiDll, "NXDO.WebUI.CScriptEngine.ScriptAppDomain") as ScriptAppDomain;
- string sReturnHtml = scriptDomain.IScriptExecute(this.scriptDllName, this.className, ctxt) //ctxt为ScriptContext实例
- AppDomain.Unload(appDomain); //卸载appDomain ,主要为实现HotSwap
- IScriptExecute这个方法体的主要代码
- class ScriptAppDomain : MarshalByRefObject
- {
- public string IScriptExecute(string asmFile, string className, MarshalByRefObject scriptCnxtRefObj)
- {
- AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
- Assembly asm= AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(asmFile));
- ScriptContext scriptCnxt = (ScriptContext)scriptCnxtRefObj;
- Type type = asm.GetType("NXDO.WebUI.Script." + className);
- IScript iscript = type.GetConstructor(new Type[] { scriptCnxt.GetType() }).Invoke(new object[] { scriptCnxt }) as IScript;
- return iscript.GetHtml();
- }
- }
五:运行结果示图