PageRenderTime 24ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Visual Studio 2008/CSCodeDOM/ScriptControl.cs

#
C# | 391 lines | 234 code | 49 blank | 108 comment | 20 complexity | 027828695a7df1795197433c41248db3 MD5 | raw file
  1. /*********************************** Module Header ***********************************\
  2. * Module Name: ScriptControl.cs
  3. * Project: CSCodeDOM
  4. * Copyright (c) Microsoft Corporation.
  5. *
  6. * The CSCodeDOM project demonstrates how to use the .NET CodeDOM mechanism to enable
  7. * dynamic souce code generation and compilation at runtime.
  8. *
  9. * This source is subject to the Microsoft Public License.
  10. * See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
  11. * All other rights reserved.
  12. *
  13. * History:
  14. * * 6/14/2009 1:00 PM Jie Wang Created
  15. * * 9/14/2009 11:40 AM Jie Wang Bug fixed in RunInternal method.
  16. \*************************************************************************************/
  17. #region Using directives
  18. using System;
  19. using System.IO;
  20. using System.Text;
  21. using System.CodeDom;
  22. using System.CodeDom.Compiler;
  23. using System.Reflection;
  24. using System.Collections.Generic;
  25. using Microsoft.CSharp;
  26. using Microsoft.VisualBasic;
  27. using Microsoft.JScript;
  28. #endregion
  29. namespace CSCodeDOM
  30. {
  31. public sealed class ScriptControl : MarshalByRefObject
  32. {
  33. /// <summary>
  34. /// Languages supported by <see cref="ScriptControl" />.
  35. /// </summary>
  36. public enum Language
  37. {
  38. CSharp, VisualBasic, JScript
  39. }
  40. private const string ScriptAppDomainFriendlyName = "ScriptDomain";
  41. private const string ContainerNamespace = "ScriptContainerNamespace";
  42. private const string ContainerClassName = "ScriptContainer";
  43. private const string ScriptMethodName = "RunScript";
  44. private const string LanguageVersion = "v3.5";
  45. #region .ctor
  46. /// <summary>
  47. /// Creates an instance of ScriptControl.
  48. /// </summary>
  49. public ScriptControl()
  50. {
  51. this.TargetLanguage = Language.VisualBasic;
  52. this.RunInSeparateDomain = true;
  53. this.assemblyReferences = new List<string>();
  54. this.namespaceImports = new List<string>();
  55. }
  56. /// <summary>
  57. /// Creates an instance of ScriptControl.
  58. /// </summary>
  59. /// <param name="script">Script text.</param>
  60. /// <param name="language">Script language.</param>
  61. public ScriptControl(string script, Language language)
  62. : this()
  63. {
  64. this.Script = script;
  65. this.TargetLanguage = language;
  66. }
  67. #endregion
  68. #region Private members
  69. /// <summary>
  70. /// List of namespaces to be imported
  71. /// </summary>
  72. private List<string> namespaceImports;
  73. /// <summary>
  74. /// List of assemblies to be references
  75. /// </summary>
  76. private List<string> assemblyReferences;
  77. #endregion
  78. #region Public interface
  79. /// <summary>
  80. /// Runs the script stored in the <see cref="Script"/> property.
  81. /// </summary>
  82. /// <returns>The object returned by the script. If the script doesn't return
  83. /// anything, an instance of System.Object is returned by default.</returns>
  84. public object Run()
  85. {
  86. object r;
  87. if (this.RunInSeparateDomain) // We will run the script in a new AppDomain...
  88. {
  89. // Create a new domain for running the script.
  90. AppDomain scriptDomain = AppDomain.CreateDomain(ScriptAppDomainFriendlyName);
  91. // Create an instance of ScriptControl inside the new AppDomain.
  92. ScriptControl sc = (ScriptControl)scriptDomain.CreateInstanceAndUnwrap(
  93. Assembly.GetExecutingAssembly().FullName, this.GetType().FullName);
  94. // Set the property values of the ScriptControl in the new AppDomain.
  95. // Making the values identical with current instance.
  96. sc.TargetLanguage = this.TargetLanguage;
  97. sc.Script = this.Script;
  98. // Add assembly references
  99. for (int i = 0; i < this.AssemblyReferences.Count; i++)
  100. {
  101. sc.AddAssemblyReference(this.AssemblyReferences[i]);
  102. }
  103. // Add namespace imports.
  104. for (int i = 0; i < this.CodeNamespaceImports.Count; i++)
  105. {
  106. sc.AddNamespaceImport(this.CodeNamespaceImports[i]);
  107. }
  108. // Except this one, other wise the call will end up as an infinite loop.
  109. sc.RunInSeparateDomain = false;
  110. // Call the Run method in the remote AppDomain and get the result.
  111. r = sc.Run();
  112. // We're done with the new AppDomain, unload it.
  113. AppDomain.Unload(scriptDomain);
  114. }
  115. else
  116. {
  117. // We will run the script in current AppDomain, call RunInternal directly.
  118. r = this.RunInternal();
  119. }
  120. return r;
  121. }
  122. /// <summary>
  123. /// Adds a namespace import.
  124. /// </summary>
  125. public void AddNamespaceImport(string ns)
  126. {
  127. if (this.CodeNamespaceImports.IndexOf(ns) < 0)
  128. {
  129. this.CodeNamespaceImports.Add(ns);
  130. }
  131. }
  132. /// <summary>
  133. /// Removes a namespace import.
  134. /// </summary>
  135. public void RemoveNamespaceImport(string ns)
  136. {
  137. this.CodeNamespaceImports.Remove(ns);
  138. }
  139. /// <summary>
  140. /// Adds an assembly reference.
  141. /// </summary>
  142. public void AddAssemblyReference(string asm)
  143. {
  144. if (this.AssemblyReferences.IndexOf(asm) < 0)
  145. {
  146. this.AssemblyReferences.Add(asm);
  147. }
  148. }
  149. /// <summary>
  150. /// Removes an assembly reference.
  151. /// </summary>
  152. public void RemoveAssemblyReference(string asm)
  153. {
  154. this.AssemblyReferences.Remove(asm);
  155. }
  156. /// <summary>
  157. /// Gets or sets the language.
  158. /// </summary>
  159. public Language TargetLanguage
  160. {
  161. get;
  162. set;
  163. }
  164. /// <summary>
  165. /// Gets or sets the script.
  166. /// </summary>
  167. public string Script
  168. {
  169. get;
  170. set;
  171. }
  172. /// <summary>
  173. /// Gets or sets whether the script should be run in a separate <see cref="AppDomain"/>.
  174. /// </summary>
  175. public bool RunInSeparateDomain
  176. {
  177. get;
  178. set;
  179. }
  180. /// <summary>
  181. /// Gets a list of namespaces to be imported.
  182. /// </summary>
  183. public List<string> CodeNamespaceImports
  184. {
  185. get { return this.namespaceImports; }
  186. }
  187. /// <summary>
  188. /// Gets a list of assemblies to be referenced.
  189. /// </summary>
  190. public List<string> AssemblyReferences
  191. {
  192. get { return this.assemblyReferences; }
  193. }
  194. #endregion
  195. #region Internal support methods
  196. /// <summary>
  197. /// The inner implementation of <see cref="Run"/>.
  198. /// </summary>
  199. private object RunInternal()
  200. {
  201. // Build the script into a class and compile it into an in-memory assembly.
  202. CompilerResults r = CompileCode(BuildClass(this.Script));
  203. Assembly asm = r.CompiledAssembly;
  204. // Now extract the method containing the script using reflection...
  205. Module[] modules = asm.GetModules(false);
  206. Type[] types = modules[0].GetTypes();
  207. for (int i = 0; i < types.Length; i++)
  208. {
  209. Type type = types[i];
  210. if (type.IsClass && type.Name == ContainerClassName) // The class we're looking for.
  211. {
  212. MethodInfo[] mis = type.GetMethods();
  213. for (int j = 0; j < mis.Length; j++)
  214. {
  215. MethodInfo mi = mis[j];
  216. if (mi.Name == ScriptMethodName) // The method we're looking for.
  217. {
  218. return mi.Invoke(null, null); // Call the method and return the result.
  219. }
  220. }
  221. }
  222. }
  223. // Should never be here.
  224. throw new ApplicationException("Script method not found.");
  225. }
  226. /// <summary>
  227. /// Build the Class source code using CodeDOM.
  228. /// </summary>
  229. /// <param name="snippet">Script text to be built into the class.</param>
  230. /// <returns>CodeDOM generated source.</returns>
  231. private string BuildClass(string snippet)
  232. {
  233. CodeNamespace ns = new CodeNamespace(ContainerNamespace);
  234. // Import namespaces.
  235. for (int i = 0; i < this.CodeNamespaceImports.Count; i++)
  236. {
  237. ns.Imports.Add(new CodeNamespaceImport(this.CodeNamespaceImports[i]));
  238. }
  239. // Create the class declaration.
  240. CodeTypeDeclaration cls = new CodeTypeDeclaration();
  241. cls.IsClass = true;
  242. cls.Name = ContainerClassName;
  243. cls.Attributes = MemberAttributes.Public;
  244. // Create the method.
  245. CodeMemberMethod method = new CodeMemberMethod();
  246. method.Name = ScriptMethodName;
  247. method.ReturnType = new CodeTypeReference(typeof(object)); // It will return an object.
  248. method.Attributes = MemberAttributes.Static | MemberAttributes.Public;
  249. method.Statements.Add(new CodeSnippetExpression(snippet)); // Add script code into the method.
  250. // Since we don't know if the script will return something or not, we will add
  251. // this return statement to the end of the script code. This will not affect the
  252. // script if the script does return something because in this case our return
  253. // statement will never be executed. However, if the script doesn't include a
  254. // return to the end of its code path, our return statement will return an
  255. // instance of System.Object by default. Otherwise, a compile error will occur:
  256. // not all code paths return a value.
  257. method.Statements.Add(new CodeMethodReturnStatement(new CodeObjectCreateExpression(
  258. typeof(object), new CodeExpression[] { })));
  259. cls.Members.Add(method); // Add method to the class.
  260. ns.Types.Add(cls); // Add class to the namespace.
  261. StringBuilder sb = new StringBuilder();
  262. StringWriter sw = null;
  263. CodeDomProvider provider = null;
  264. try
  265. {
  266. sw = new StringWriter(sb);
  267. provider = GetProvider();
  268. ICodeGenerator generator = provider.CreateGenerator(sw);
  269. // Generate the code and write to the stream.
  270. generator.GenerateCodeFromNamespace(ns, sw, new CodeGeneratorOptions());
  271. sw.Flush();
  272. sw.Close();
  273. }
  274. finally
  275. {
  276. if (provider != null) provider.Dispose();
  277. if (sw != null) sw.Dispose();
  278. }
  279. return sb.ToString(); // Return generated source code.
  280. }
  281. /// <summary>
  282. /// Returns an instance of CodeDomProvider according to the target language.
  283. /// </summary>
  284. private CodeDomProvider GetProvider()
  285. {
  286. Dictionary<string, string> cpOptions = new Dictionary<string, string>();
  287. switch (this.TargetLanguage)
  288. {
  289. case Language.CSharp:
  290. cpOptions.Add("CompilerVersion", LanguageVersion);
  291. return new CSharpCodeProvider(cpOptions);
  292. case Language.VisualBasic:
  293. cpOptions.Add("CompilerVersion", LanguageVersion);
  294. return new VBCodeProvider(cpOptions);
  295. case Language.JScript:
  296. return new JScriptCodeProvider();
  297. default:
  298. throw new NotSupportedException(string.Format(
  299. "Target language {0} is not supported.", this.TargetLanguage));
  300. }
  301. }
  302. /// <summary>
  303. /// Returns an instance of <see cref="CompilerParameters"/> containing the compiler parameters.
  304. /// </summary>
  305. private CompilerParameters CreateCompilerParameters()
  306. {
  307. CompilerParameters cp = new CompilerParameters();
  308. cp.CompilerOptions = "/target:library";
  309. cp.IncludeDebugInformation = true;
  310. cp.GenerateExecutable = false;
  311. cp.GenerateInMemory = true;
  312. // Add assembly references.
  313. for (int i = 0; i < this.AssemblyReferences.Count; i++)
  314. {
  315. cp.ReferencedAssemblies.Add(this.AssemblyReferences[i]);
  316. }
  317. return cp;
  318. }
  319. /// <summary>
  320. /// Compiles the source code into assembly.
  321. /// </summary>
  322. /// <param name="source">Full source generated by <see cref="BuildClass"/> method.</param>
  323. private CompilerResults CompileCode(string source)
  324. {
  325. CodeDomProvider provider = GetProvider();
  326. CompilerParameters cp = CreateCompilerParameters();
  327. CompilerResults cr = provider.CompileAssemblyFromSource(cp, source);
  328. if (cr.Errors.Count > 0)
  329. {
  330. StringBuilder sb = new StringBuilder();
  331. foreach (CompilerError error in cr.Errors)
  332. {
  333. sb.AppendLine("Compile Error: " + error.ErrorText);
  334. sb.AppendLine();
  335. }
  336. throw new ApplicationException(sb.ToString());
  337. }
  338. return cr;
  339. }
  340. #endregion
  341. }
  342. }