Dynamically Creating Applications Using System.CodeDom

Introduction

 
The System.CodeDom namespace in .net allows the users to dynamically compile and create assemblies. This concept is used by ASP.NET internally. The article provides an insight on how to create assemblies dynamically.
 
The following are the list of classes, which would be used to create the assembly:
  • CodeNamespace
  • CodeTypeDeclaration
  • CodeCompileUnit
  • CodeEntryPointMethod
  • CodeMemberField
  • CodeMemberProperty
  • CodeMemberMethod
  • CodeParameterDeclarationExpression
  • CodeCompileUnit
  • CompilerParameters
  • CodeSnippetExpression
  • CodeExpressionStatement
Step 1
 
Add the following namespace declarations,
  1. using System;  
  2. using System.CodeDom;  
  3. using System.CodeDom.Compiler; 
The System.Codedom namespace contains the classes/types necessary to create assemblies at runtime. The above-mentioned classes are contained within this namespace.
 
Step 2: Create the following class,
  1. public class CCodeGenerator {  
  2.     CodeNamespace mynamespace;  
  3.     CodeTypeDeclaration myclass;  
  4.     CodeCompileUnit myassembly;  
Any code created from Step 3 to Step 10 must reside in the CcodeGenerator class. For clarity and explanation sake, the article has fragmented the code into pieces.
 
Step 3
 
The CodeNamespace class is used to add/create namespaces. The name of the namespace can be passed through the constructor of the class.
  1. public void CreateNamespace()  
  2. {  
  3.     mynamespace = new CodeNamespace("mynamespace");  
Step 4
 
Using the mynamespace object, add the list of all namespaces that are used by the assembly.
  1. public void CreateImports() {  
  2.     mynamespace.Imports.Add(new CodeNamespaceImport("System"));  
  3.     mynamespace.Imports.Add(new CodeNamespaceImport("System.Drawing"));  
  4.     mynamespace.Imports.Add(new CodeNamespaceImport("System.Windows.Forms"));  
Step 5
 
Create a class inside the namespace.
  1. public void CreateClass() {  
  2.     myclass = new CodeTypeDeclaration();  
  3.     //Assign a name for the class  
  4.     myclass.Name = "CMyclass";  
  5.     myclass.IsClass = true;  
  6.     //Set the Access modifier.  
  7.     myclass.Attributes = MemberAttributes.Public;  
  8.     //Add the newly created class to the namespace  
  9.     mynamespace.Types.Add(myclass);  
Step 6
 
Create a member and add it to the newly created class.
  1. public void CreateMember() {  
  2.     //Provide the type and variable name  
  3.     CodeMemberField mymemberfield = new CodeMemberField(typeof(System.String), "strMessage");  
  4.     //Add the member to the class  
  5.     myclass.Members.Add(mymemberfield);  
Step 7
 
Create a property (with get and set) and add it to the class
  1. public void CreateProperty() {  
  2.     CodeMemberProperty mymemberproperty = new CodeMemberProperty();  
  3.     //Name of the property  
  4.     mymemberproperty.Name = "Message";  
  5.     //Data type of the property  
  6.     mymemberproperty.Type = new CodeTypeReference(typeof(System.String));  
  7.     //Access modifier of the property  
  8.     mymemberproperty.Attributes = MemberAttributes.Public;  
  9.     //Add the property to the class  
  10.     myclass.Members.Add(mymemberproperty);  
  11.     //Add the code-snippets to the property.  
  12.     //If required, we can also add some custom validation code.  
  13.     //using the CodeSnippetExpression class.  
  14.     //Provide the return <propertyvalue> statement-For getter  
  15.     CodeSnippetExpression getsnippet = new CodeSnippetExpression("return strMessage");  
  16.     //Assign the new value to the property-For setter  
  17.     CodeSnippetExpression setsnippet = new CodeSnippetExpression("strMessage=value");  
  18.     //Add the code snippets into the property  
  19.     mymemberproperty.GetStatements.Add(getsnippet);  
  20.     mymemberproperty.SetStatements.Add(setsnippet);  
Step 8
 
Create a method. Please take utmost care when assigning values to the CodeSnippetExpression class, as any statement provided must not have any syntax error(s). Since the assembly would be created at runtime (dynamically), the compiler would simply throw all Compile-time error(s) and we don't have any prudent means to find and eliminate the error.
  1. public void CreateMethod() {  
  2.     //Create an object of the CodeMemberMethod  
  3.     CodeMemberMethod mymethod = new CodeMemberMethod();  
  4.     //Assign a name for the method.  
  5.     mymethod.Name = "AddNumbers";  
  6.     //create two parameters  
  7.     CodeParameterDeclarationExpression cpd1 = new CodeParameterDeclarationExpression(typeof(int), "a");  
  8.     CodeParameterDeclarationExpression cpd2 = new CodeParameterDeclarationExpression(typeof(int), "b");  
  9.     //Add the parameters to the method.  
  10.     mymethod.Parameters.AddRange(new CodeParameterDeclarationExpression[] { cpd1, cpd2 });  
  11.     //Provide the return type for the method.  
  12.     CodeTypeReference ctr = new CodeTypeReference(typeof(System.Int32));  
  13.     //Assign the return type to the method.  
  14.     mymethod.ReturnType = ctr;  
  15.     //Provide definition to the method (returns the sum of two //numbers)  
  16.     CodeSnippetExpression snippet1 = new CodeSnippetExpression("System.Console.WriteLine(\" Adding :\" + a + \" And \" + b )");  
  17.     //return the value  
  18.     CodeSnippetExpression snippet2 = new CodeSnippetExpression("return a+b");  
  19.     //Convert the snippets into Expression statements.  
  20.     CodeExpressionStatement stmt1 = new CodeExpressionStatement(snippet1);  
  21.     CodeExpressionStatement stmt2 = new CodeExpressionStatement(snippet2);  
  22.     //Add the expression statements to the method.  
  23.     mymethod.Statements.Add(stmt1);  
  24.     mymethod.Statements.Add(stmt2);  
  25.     //Provide the access modifier for the method.  
  26.     mymethod.Attributes = MemberAttributes.Public;  
  27.     //Finally add the method to the class.  
  28.     myclass.Members.Add(mymethod);  
Step 9
 
Create a main method (Entry point for the assembly);
  1. public void CreateEntryPoint() {  
  2.     //Create an object and assign the name as "Main"  
  3.     CodeEntryPointMethod mymain = new CodeEntryPointMethod();  
  4.     mymain.Name = "Main";  
  5.     //Mark the access modifier for the main method as Public and //static  
  6.     mymain.Attributes = MemberAttributes.Public | MemberAttributes.Static;  
  7.     //Provide defenition to the main method.  
  8.     //Create an object of the "Cmyclass" and invoke the method  
  9.     //by passing the required parameters.  
  10.     CodeSnippetExpression exp1 = new CodeSnippetExpression("CMyclass x = new CMyclass()");  
  11.     //Assign value to our property  
  12.     CodeSnippetExpression exp2 = new CodeSnippetExpression("x.Message=\"Hello World \"");  
  13.     //Print the value in the property  
  14.     CodeSnippetExpression exp3 = new CodeSnippetExpression("Console.WriteLine(x.Message)");  
  15.     //Invode the method  
  16.     CodeSnippetExpression exp4 = new CodeSnippetExpression("Console.WriteLine(\"Answer: {0}\",x.AddNumbers(10,20))");  
  17.     CodeSnippetExpression exp5 = new CodeSnippetExpression("Console.ReadLine()");  
  18.     //Create expression statements for the snippets  
  19.     CodeExpressionStatement ces1 = new CodeExpressionStatement(exp1);  
  20.     CodeExpressionStatement ces2 = new CodeExpressionStatement(exp2);  
  21.     CodeExpressionStatement ces3 = new CodeExpressionStatement(exp3);  
  22.     CodeExpressionStatement ces4 = new CodeExpressionStatement(exp4);  
  23.     CodeExpressionStatement ces5 = new CodeExpressionStatement(exp5);  
  24.     //Add the expression statements to the main method.  
  25.     mymain.Statements.Add(ces1);  
  26.     mymain.Statements.Add(ces2);  
  27.     mymain.Statements.Add(ces3);  
  28.     mymain.Statements.Add(ces4);  
  29.     mymain.Statements.Add(ces5);  
  30.     //Add the main method to the class  
  31.     myclass.Members.Add(mymain);  
Step 10
 
Compile the class and create the assembly.
  1. public void SaveAssembly() {  
  2.     //Create a new object of the global CodeCompileUnit class.  
  3.     myassembly = new CodeCompileUnit();  
  4.     //Add the namespace to the assembly.  
  5.     myassembly.Namespaces.Add(mynamespace);  
  6.     //Add the following compiler parameters. (The references to the //standard .net dll(s) and framework library).  
  7.     CompilerParameters comparam = new CompilerParameters(new string[] { "mscorlib.dll" });  
  8.     comparam.ReferencedAssemblies.Add("System.dll");  
  9.     comparam.ReferencedAssemblies.Add("System.Drawing.dll");  
  10.     comparam.ReferencedAssemblies.Add("System.Windows.Forms.dll");  
  11.     //Indicates Whether the compiler has to generate the output in //memory  
  12.     comparam.GenerateInMemory = false;  
  13.     //Indicates whether the output is an executable.  
  14.     comparam.GenerateExecutable = true;  
  15.     //provide the name of the class which contains the Main Entry //point method  
  16.     comparam.MainClass = "mynamespace.CMyclass";  
  17.     //provide the path where the generated assembly would be placed  
  18.     comparam.OutputAssembly = @ "c:\temp\HelloWorld.exe";  
  19.     //Create an instance of the c# compiler and pass the assembly to //compile  
  20.     Microsoft.CSharp.CSharpCodeProvider ccp = new Microsoft.CSharp.CSharpCodeProvider();  
  21.     ICodeCompiler icc = ccp.CreateCompiler();  
  22.     //The CompileAssemblyFromDom would either return the list of  
  23.     //compile time errors (if any), or would create the  
  24.     //assembly in the respective path in case of successful //compilation  
  25.     CompilerResults compres = icc.CompileAssemblyFromDom(comparam, myassembly);  
  26.     if (compres == null || compres.Errors.Count > 0) {  
  27.         for (int i = 0; i < compres.Errors.Count; i++) {  
  28.             Console.WriteLine(compres.Errors[i]);  
  29.         }  
  30.     }  
Step 11
 
Create a test component to create the assembly.
  1. using System;  
  2. namespace DynamicAssemblies {  
  3.     class testCodeDom {  
  4.         [STAThread]  
  5.         static void Main(string[] args) {  
  6.             CCodeGenerator cg = new CCodeGenerator();  
  7.             cg.CreateNamespace();  
  8.             cg.CreateImports();  
  9.             cg.CreateClass();  
  10.             cg.CreateMember();  
  11.             cg.CreateProperty();  
  12.             cg.CreateMethod();  
  13.             cg.CreateEntryPoint();  
  14.             cg.SaveAssembly();  
  15.             Console.ReadLine();  
  16.         }  
  17.     }  
The HelloWorld.exe would be created in the mentioned path. The output of the assembly would be as follows:
 
DynamicAppl.gif
 

Conclusion

 
The facility to dynamically create assemblies would be of immense use when developing real-world applications that demand a great amount of flexibility and scalability.