UI on the Fly

Use the .NET Framework to Generate and Execute Custom Controls at Run Time

Morgan Skinner

This article discusses:

  • Run-time code generation
  • Generating classes using Reflection.Emit
  • Generating classes using System.CodeDom
  • Creating Windows Forms and ASP.NET controls at run time
This article uses the following technologies:
Windows Forms, ASP.NET, .NET Framework, C#

Code download available at:CodeGeneration.exe(146 KB)

Contents

Code Generation Basics
Generating Classes Using Reflection.Emit
Generating Classes Using System.CodeDom
Storing Generated Assemblies
Securing Generated Assemblies
Windows Forms Example
Using Reflection.Emit to Generate a Windows Forms Control
Emitting the Assembly
Defining the Type
Defining the Constructor
Defining the InitializeComponent Method
Using System.CodeDom to Generate a Windows.Forms Control
Defining and Compiling the Assembly
Defining the Type with CodeDOM
Defining the Constructor
Defining InitializeComponent
An ASP.NET Example
Data Binding
Simplifying the Code
Conclusion

One of the often overlooked features of the Microsoft® .NET Framework is its ability to generate, compile, and execute custom code at run time. This is done, for example, during serialization of XML data, and in the use of regular expressions, where the expression evaluation function is emitted at run time.

This article describes another area in which run-time code generation can be used: the creation of UI controls. Generating these controls once and then reusing them as necessary is much more efficient than generating the controls each time a form or page is requested.

This is applicable to any application that has user-configurable fields (for instance, where the end user can select data items to be displayed on screen). It is common to define custom forms using XML. This is then parsed at run time to construct the UI dynamically when the page is loaded. However, this parsing frequently happens each time the form is displayed, or in a server scenario, for each user, thereby creating unnecessary load on the application.

In this article, I will provide detailed examples of how to use run-time code generation to construct, load, and execute controls at run time. The examples I will describe can be applied equally well to the .NET Framework 1.x and to the .NET Framework 2.0. There are some significant additions to the Reflection namespaces in the 2.0 release of the Framework, but these changes do not negate or impair the solutions outlined here in any way.

Code Generation Basics

Many applications provide a user interface (UI) that can be customized—possibly by adding extra fields or altering the order and position of existing fields. This is frequently accomplished in one of two ways:

  • The user makes changes by editing the user interface inside Visual Studio®.
  • The app generates controls at run time based on some form of configuration data, commonly stored in an XML file.

Neither of these solutions is ideal. What's really needed is a combination of these two methods that offers the performance of the hand-crafted solution and the flexibility of the application generating the controls at run time.

There are two supported methods for constructing controls at run time: Reflection.Emit and System.CodeDom. The former, which requires intimate knowledge of Intermediate Language (IL), generates managed assemblies using explicitly specified IL instructions. The latter uses an object model of the code to emit source code that can then be compiled into IL and executed. Both of these methods are covered in this article.

There is actually another method, a variant of the CodeDOM approach, which is to construct source code manually, meaning to write the .cs or .vb file yourself, rather than using the object model to do so, and then compile it at run time. While this is possible, I recommend using one of the previously mentioned mechanisms rather than a hand-crafted solution.

Generating Classes Using Reflection.Emit

The Reflection.Emit namespace permits you to generate assemblies that are entirely transient; they can be generated in memory and executed without ever being persisted to disk. The option to write to disk is available, however, if necessary. (I discuss this a bit later in the article.)

To generate a class using Reflection.Emit, the following steps are required:

  • Define a dynamic assembly.
  • Create a module within that assembly.
  • Define a type in that module, deriving the type from the appropriate base class and interface(s):Create the methods on that type, obtain an ILGenerator for each method, emit IL opcodes as appropriate, and persist the type.
  • Optionally save the assembly for future use.

Constructing classes using Reflection.Emit is a complex approach, and for any given source code operation (such as calling a method) you will typically have to generate several lines of IL code. I'll get back to that later in the article.

Generating Classes Using System.CodeDom

The System.CodeDom namespace provides a language-agnostic way to define types. You construct an in-memory model of the code, and then use a code generator to emit the source code for that model. As long as you have an appropriate code generator, you can emit code in any language (the .NET Framework redistributable includes code generators for Visual Basic®, C#, and JScript®; if Visual Studio is installed, generators for C++ and J# will also be available).

To generate a class using the CodeDOM:

  • Create a new CodeCompileUnit.
  • Add namespaces to the CodeCompileUnit.
  • Add any import statements as appropriate.
  • Add a CodeTypeDeclaration for the class being constructed.
  • Add CodeExpressions to each method.
  • Obtain a code compiler and compile the CodeCompileUnit.

One of the benefits of this method is that you use higher-level concepts than when working with Reflection.Emit.

Once your classes have been defined, you need to construct instances of them at run time. The most common method is to use the Activator class to load the type, as shown in the following code snippet:

public Control LoadControl ( string typeName ) { return LoadControl(Type.GetType(typeName)); } public Control LoadControl ( Type controlType ) { return Activator.CreateInstance(controlType) as Control; }

Here I call the static GetType member of the Type class. GetType returns the .NET type information for the type named in its argument. The Activator class is then used to construct an instance of that type. Note that if the specified type lives in an assembly other than mscorlib or the currently executing assembly, the type name must be qualified with the assembly's name. When stored as a string, the names of managed types can be either partially qualified with the namespace of the type, or fully qualified with both the namespace of the type and with the name of the assembly in which the type is stored. Here's an example of an assembly-qualified type name:

TestControls.TestControl, TestAssembly

This type name corresponds to a type as shown in the following code, compiled into an assembly called TestAssembly:

using System.Web.UI; namespace TestControls { public class TestControl : Control { ... } }

When generating controls, it would be customary to store this type name in a persistent storage medium, such as a SQL Server database. When your form or page is rendered, you would load the type using Activator.CreateInstance and then display that object to the user.

Storing Generated Assemblies

Assemblies created using Reflection.Emit or System.CodeDom can be entirely transient or can be generated on disk for future use. Transient assemblies exist purely for the lifespan of the application domain in which they're created. Once this application domain is unloaded (which for the default application domain occurs when the application is unloaded), these assemblies are unloaded from memory and no longer exist. Since there is a cost to generating these assemblies at run time, I recommend that you consider persisting generated assemblies to the file system and looking for them there first. If an assembly can't be found on the file system at run time, it can then be regenerated. Of course, if you choose this approach, you'll need to ensure that the definition for the controls and the generated assemblies remain in sync. For a commercial application, I suggest that you provide your users with an administrative tool that can run through all the controls that need to be generated and store these in a single assembly that is held on disk or within SQL Server™.

I also recommend limiting the number of dynamically generated assemblies to the smallest practical number—preferably just one. This does mean that the entire set of controls will need to be generated each time a change is made in any control. However, as this is an administrative activity that is unlikely to occur too frequently, this should not pose a performance problem.

Securing Generated Assemblies

One other important area to understand is the security issues associated with generated assemblies. This is of paramount importance, as generated assemblies pose a unique threat to an application—namely, the application in question expects to dynamically load some types, and these types, in theory, could execute any arbitrary code.

The preferred way to secure generated code is to assign a limited set of permissions to all generated code. This can be accomplished in .NET by specifying a code group within the .NET configuration tool, mscorcfg.msc.

A custom code group can be defined with a specific membership condition and set of permissions. As an example, you might want to assign all dynamically generated code a very limited set of permissions, such as Execute (which permits the assembly to execute, but severely restricts what that code can do). A code group is applied to an assembly by means of a membe rship condition—in effect this is used to define inclusion into the group.

There are several types of membership conditions. Probably the most useful in this example would be the URL permission, which can be used to define membership based on a particular directory on disk (using, for example, "file://E:/Code Generation/bin/Debug/*" as the URL). The permission set defined within the tool will apply to any generated assemblies that are stored within the specified folder.

Generating controls once and loading them at run time has performance benefits over, for example, using an XML representation of a form and evaluating it at run time. Loading an XML representation typically involves instantiating and loading an XmlDocument, a relatively slow operation compared to instantiating a typical control, even with Activator.CreateInstance.

Windows Forms Example

This section provides an example that shows how a Windows® Forms control can be generated at run time using both Reflection.Emit and System.CodeDom. The control that is generated will present a simple user interface comprised of just a static textbox, as shown in Figure 1.

Figure 1 Control Generation Sample App

Figure 1** Control Generation Sample App **

The generated control is the portion that appears within the red outline. Although this is a simple example, it does provide all of the necessary concepts that can be extended to deliver a more complete implementation.

What we're after is a control whose source code mimics that shown in Figure 2. This code demonstrates several concepts that can be used no matter what amount of code you are emitting:

  • The class is derived from a base class.
  • The class contains a private field.
  • The class contains a public default constructor.
  • The class contains a private method.
  • The class shows how to use properties.
  • The class shows how to call methods.
  • The class shows how to construct new object instances.

This is by no means an exhaustive list of all the capabilities that could be emitted, but it's a good starting point.

Figure 2 Prototype UserControl

using System; using System.Drawing; using System.Windows.Forms; public class TestControl : UserControl { private Label _label ; public TestControl ( ) { this.InitializeComponent ( ) ; } private void InitializeComponent ( ) { _label = new Label(); this.SuspendLayout(); _label.Anchor = (AnchorStyles.Top | (AnchorStyles.Left | AnchorStyles.Right)); _label.BorderStyle = BorderStyle.Fixed3D; _label.Location = new Point(8, 8); _label.Name = "_label"; _label.Size = new Size(312, 23); _label.TabIndex = 0; _label.Text = "***User defined message goes here***"; this.Controls.Add(_label); this.Name = "MyControl"; this.Size = new Size(328, 100); this.ResumeLayout(false); } }

Using Reflection.Emit to Generate a Windows Forms Control

The Reflection.Emit namespace contains a small number of classes, as it's a very low-level API and most of the hard work has to be performed by your own code. I'll present the code in sections and describe each in detail.

Working with Reflection.Emit is hard—the only real way to ensure that the correct code is generated is to write some test code, compile it, and then examine the generated IL (using tools like ildasm or Lutz Roeder's .NET Reflector). This is the method I used in order to generate the following IL. That's not to say that you could not generate your own IL without reverse engineering some other code, but I doubt there are many developers who consider IL to be their primary language, so skills in this area are few and far between (if you are looking to become an expert in IL, a great reference is Serge Lidin's book Inside Microsoft .NET IL Assembler, Microsoft Press®, 2002). For this discussion, I've split the code into four logical sections: emitting the Assembly, defining the type, defining the constructor, and defining the InitializeComponent method.

Emitting the Assembly

The code in Figure 3 constructs a dynamic assembly, adds a module to that assembly, and creates a unique name for the assembly. First, an assembly name is constructed. In this example, I'm using a GUID to ensure a unique enough name for the assembly.

Figure 3 Dynamic Assembly Builder

AssemblyName assemName = new AssemblyName ( ) ; assemName.Name = string.Format( "MyControl{0}", Guid.NewGuid().ToString("N") ) ; // Create the assembly builder object AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( assemName, AssemblyBuilderAccess.RunAndSave ) ; // Then create the module builder ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule( "MyModule", "MyAssembly.dll" ) ; ... assemblyBuilder.Save ( "MyAssembly.dll" ) ;

It's then necessary to call the static DefineDynamicAssembly method of the AppDomain class. There are a number of overloaded versions of the DefineDynamicAssembly function, but here I opted to call the simplest override. Next, a module builder object is created by defining the module name and file name. After the code in the following sections, the assembly is finally created using the AssemblyBuilder.Save method.

Defining the Type

To create a type using Reflection.Emit, you need to call one of the DefineType methods on the ModuleBuilder class. There are various methods that permit you to define the class, its base class, and any interfaces it explicitly implements. The following segment of code shows how to define a class derived from System.Windows.Forms.UserControl:

Type baseClass = typeof ( System.Windows.Forms.UserControl ) ; TypeBuilder typeBuilder = moduleBuilder.DefineType( "MyControls.MyControl", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.BeforeFieldInit, baseClass ) ;

DefineType allows you to specify type attributes for the generated type. In this case, the new type is a class; it has public visibility, and static members can be called without forcing the runtime to initialize the class.

I then construct a private field which will hold the label control. This follows a similar pattern as the code just shown, in that the name, type, and attributes are specified in a single call. The following code snippet shows how it looks:

// Create a private field for the label FieldBuilder labelField = typeBuilder.DefineField( "_label", typeof( System.Windows.Forms.Label ) , FieldAttributes.Private ) ;

Defining the Constructor

In order to add anything to a type, you need to call one of the Define* methods of the TypeBuilder class (this object was created earlier). There are a number of these methods for defining constructors, events, fields, methods, and other constructs.

In Figure 4 I'm using the DefineConstructor method to create a public constructor with no parameters. I supply appropriate method attributes to the DefineConstructor method, and define the parameter array to be empty in this instance. The IL generated from the constructor code is shown here:

.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 3 L_0000: ldarg.0 L_0001: call instance void System.Windows.Forms.UserControl::.ctor() L_0006: ldarg.0 L_0007: call instance void MyControls.MyControl::InitializeComponent() L_000c: ret }

Note that there is a nearly 1:1 correspondence between the generated IL and the commands that emitted that IL in the C# code.

Figure 4 ConstructorBuilder

// Then create the constructor which will call InitializeComponent ConstructorBuilder constructor = typeBuilder.DefineConstructor( MethodAttributes.Public | MethodAttributes.HideBySig , CallingConventions.Standard , new System.Type[] { } ) ; // Get an IL generator so I can write some code ILGenerator constructorIL = constructor.GetILGenerator ( ) ; // Call the base class constructor and then InitializeComponent constructorIL.BeginScope ( ) ; constructorIL.Emit ( OpCodes.Ldarg_0 ) ; constructorIL.Emit ( OpCodes.Call, baseClass.GetConstructor ( new Type[] {} ) ) ; constructorIL.Emit ( OpCodes.Ldarg_0 ) ; constructorIL.Emit ( OpCodes.Call, initComponentBuilder ) ; constructorIL.Emit ( OpCodes.Ret ) ; constructorIL.EndScope ( ) ;

Once you have a ConstructorBuilder object, you can retrieve an ILGenerator from it. This is the object that permits you to emit IL into the assembly.

The BeginScope and EndScope methods are not strictly necessary here, and basically equate to { and } in C#. Within this scope I emit IL opcodes.

The CLR is a stack-based architecture, so the first opcode (Ldarg_0) is pushing an argument (in this case, the this pointer for the current object) onto the evaluation stack. The second instruction is a call to the base class constructor. This pops the this pointer off the stack and calls the constructor, so the next opcode loads the this pointer onto the stack ready for the next call.

The penultimate opcode is a call to the InitializeComponent method, and the final opcode is a return from the constructor.

Defining the InitializeComponent Method

This method is much more involved than the others, as it has to do the following:

  • Create the new label control.
  • Call SuspendLayout.
  • Set all properties of the label control.
  • Add the label control to the user control's Control collection.
  • Set the initial size of the user control.
  • Call ResumeLayout.

These tasks are outlined in Figure 5.The amount of code required to generate the control is fairly significant, and easy to get wrong. You can, however, break this into separate functions for readability.

Figure 5 Generating InitializeComponent

// Now generate the InitializeComponent method MethodBuilder initComponentBuilder = typeBuilder.DefineMethod( "InitializeComponent" , MethodAttributes.Private | MethodAttributes.HideBySig, CallingConventions.Standard, typeof ( void ) , new System.Type[] { } ) ; // Get an IL generator for the InitializeComponent method // and write the method body. ILGenerator initIL = initComponentBuilder.GetILGenerator ( ) ; initIL.BeginScope ( ) ; // Construct the label initIL.Emit ( OpCodes.Ldarg_0 ) ; initIL.Emit ( OpCodes.Newobj , typeof( System.Windows.Forms.Label).GetConstructor( new Type[] { } ) ) ; initIL.Emit ( OpCodes.Stfld , labelField ) ; // Then call Control.SuspendLayout initIL.Emit ( OpCodes.Ldarg_0 ) ; initIL.Emit ( OpCodes.Call , baseClass.GetMethod ( "SuspendLayout" ) ) ; // Anchor the control to the top left and right initIL.Emit ( OpCodes.Ldarg_0 ) ; initIL.Emit ( OpCodes.Ldfld, labelField ) ; initIL.Emit ( OpCodes.Ldc_I4_S, (int) (AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right ) ) ; initIL.Emit ( OpCodes.Callvirt , labelClass.GetProperty( "Anchor",typeof(AnchorStyles)).GetSetMethod ( ) ) ; ... // Set the other properties of the label control // Call Control.ResumeLayout initIL.Emit ( OpCodes.Ldarg_0 ) ; initIL.Emit ( OpCodes.Ldc_I4_0 ) ; initIL.Emit ( OpCodes.Call , baseClass.GetMethod ( "ResumeLayout" , new Type[] { typeof ( bool ) } ) ) ; // And emit the end of the function initIL.Emit ( OpCodes.Ret ) ; initIL.EndScope ( ) ;

Most of the sections of code in Figure 5 do something similar—they load the this pointer onto the stack with the Ldarg_0 opcode, and then load parameters onto the stack. The parameters are then used in a call to a property setter or, in some cases, to a method call. As an example, the code that sets the size of the control is outlined here:

initIL.Emit ( OpCodes.Ldarg_0 ) ; initIL.Emit ( OpCodes.Ldc_I4, 328 ) ; initIL.Emit ( OpCodes.Ldc_I4, 200 ) ; initIL.Emit ( OpCodes.Newobj , typeof ( Size ).GetConstructor( new Type[] { typeof ( int ) , typeof ( int ) } ) ) ; initIL.Emit ( OpCodes.Callvirt , controlClass.GetProperty("Size", typeof(Size)).GetSetMethod ( ) ) ;

I load the this pointer onto the stack followed by two integers, 328 and 200. I then make a call to the Size constructor, which constructs a size object using these two integers. This size object is returned at the top of the stack. I then obtain the control's Size property and retrieve the setter for this property, which I call with the arguments currently on the stack. The net effect is that I generate the following call:

this.Size = new Size ( 328, 200 ) ;

Due to the inherent complexity of this solution, I would not suggest that it be used widely, if at all. The following solution provides much of the flexibility of IL generation without as steep a learning curve for the developers writing the code. There are some esoteric things that can be done in IL that you can't achieve in other CLR-targetting languages, but these are unlikely to be necessary in many applications.

Using System.CodeDom to Generate a Windows.Forms Control

The CodeDOM namespace provides an abstracted code model that you can program against. This model is built up in memory using fairly simple expressions and can then be converted into source code using one of the available .NET code generators. Microsoft currently provides code generators for C#, Visual Basic, C++, JScript, J#, and MSIL. Generators are also available from third parties for other languages.

I've broken the code down into the same sections as used in the Reflection.Emit example so direct comparisons of each step can be made. Note that the two methods of code generation are not interchangeable—you cannot mix CodeDOM with emitted code in the same assembly.

Defining and Compiling the Assembly

The first step for working with the CodeDOM is to reference the System.CodeDom namespace. Then you need to create a CodeCompileUnit, which will form the basis of the assembly. Here, you can add namespaces, add import statements to these namespaces, and define types.

The following code shows how to create a particular assembly. I've created a CodeCompileUnit, added the MyControls namespace to it, and then defined import statements for all the classes used in the code. The namespace is then added to the code compile unit:

// Create a code compile unit and a namespace CodeCompileUnit ccu = new CodeCompileUnit ( ) ; CodeNamespace ns = new CodeNamespace ( "MyControls" ) ; // Add some imports statements to the namespace ns.Imports.Add ( new CodeNamespaceImport ( "System" ) ) ; ns.Imports.Add ( new CodeNamespaceImport ( "System.Drawing" ) ) ; ns.Imports.Add ( new CodeNamespaceImport ( "System.Windows.Forms" ) ) ; // Add the namespace to the code compile unit ccu.Namespaces.Add ( ns ) ;

Once you have a completed type definition, you can use the following code to use the C# code provider to compile the definition into an assembly. I've included the compilation step here just to complete this section. Logically, however, this would occur after creating the types in the assembly, which is described later in the article.

CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider ( ) ; ICodeCompiler compiler = provider.CreateGenerator ( ) as ICodeCompiler ; CompilerParameters cp = new CompilerParameters ( new string[] { "System.dll", "System.Windows.Forms.dll", "System.Drawing.dll" } ) ; cp.GenerateInMemory = true; cp.OutputAssembly = "AutoGenerated"; CompilerResults results = compiler.CompileAssemblyFromDom ( cp, ccu ) ;

Here, a CodeDomProvider is created for C#, and the call to CreateGenerator returns an ICodeCompiler interface. I then define the libraries that should be referenced from this assembly. After setting some parameters on this CompilerParameters class, I call the CompileAssemblyFromDom method. Based on the settings I specify in this code, an assembly is generated in memory with the name AutoGenerated.

The CompilerResults class contains an assembly property that is set on successful compilation of the assembly. You can then load types from this assembly or do whatever is appropriate.

Defining the Type with CodeDOM

Type creation using the CodeDOM is very easy. The following code shows how to create a class that derives from UserControl:

CodeTypeDeclaration ctd = new CodeTypeDeclaration ( "MyControl" ) ; ctd.BaseTypes.Add ( typeof ( System.Windows.Forms.UserControl ) ) ; ns.Types.Add ( ctd ) ;

The CodeTypeDeclaration contains a collection called BaseTypes, which contains the class that the type will derive from. It can also contain interfaces that the type should implement. Note that even though this is a collection of arbitrary types, you cannot use two or more classes here as the CLR only supports single inheritance. If you try to use more than one base class, an error will be thrown.

Defining the Constructor

When you have a CodeTypeDeclaration, all other code is added to this declaration. The following snippet uses the ctd variable created when defining the constructor:

CodeConstructor constructor = new CodeConstructor ( ) ; constructor.Statements.Add ( new CodeMethodInvokeExpression ( new CodeThisReferenceExpression ( ) , "InitializeComponent", emptyParams ) ) ; constructor.Attributes = MemberAttributes.Public ; ctd.Members.Add ( constructor ) ;

Constructors (and other types derived from CodeMemberMethod) include a CodeStatementCollection property called Statements. To this, you add one or more statements in the form of CodeStatement objects (if you add a CodeExpression, it will be automatically wrapped in a CodeExpressionStatement object). In this example, I have added a CodeMethodInvokeExpression that calls this.InitializeComponent and passes no parameters. I then specify that the constructor is publicly accessible and finally add the constructor to the type definition.

Defining InitializeComponent

The InitializeComponent method, which is shown in Figure 6, is obviously more complex than the constructor, as there are numerous statements that need to be created. Though somewhat lengthy, this code is simpler to write than the IL equivalent. Let's dissect some of this code so you can see how it works. First look at the SuspendLayout call:

this.SuspendLayout ( ) ;

This expression is generated by using CodeMethodInvokeExpression, and passing a reference to the object that the method is called on. This is followed by the method name and any parameters that need to be passed to that method. CodeMethodInvokeExpression is called as follows:

CodeMethodInvokeExpression ( targetObject, methodName, parameters )

In this instance, the targetObject is this, the methodName is SuspendLayout, and there are no parameters, so it is defined in code as follows (emptyParams is an empty array of type CodeExpression):

initializeComponent.Statements.Add( new CodeMethodInvokeExpression( new CodeThisReferenceExpression(), "SuspendLayout", emptyParams));

Figure 6 InitializeComponent with CodeDom

// Now add the InitializeComponent method CodeMemberMethod initializeComponent = new CodeMemberMethod ( ) ; initializeComponent.Name = "InitializeComponent" ; initializeComponent.ReturnType = new CodeTypeReference ( typeof(void) ); initializeComponent.Statements.Add( new CodeAssignStatement ( new CodeVariableReferenceExpression ( "_label" ), new CodeObjectCreateExpression ( typeof ( System.Windows.Forms.Label ), Type.EmptyTypes ) ) ; // Add the SuspendLayout() call initializeComponent.Statements.Add( new CodeMethodInvokeExpression ( new CodeThisReferenceExpression ( ) , "SuspendLayout", emptyParams)); // Setup the Anchor property of the label CodeBinaryOperatorExpression leftAndRight = new CodeBinaryOperatorExpression ( new CodeFieldReferenceExpression ( new CodeTypeReferenceExpression ( typeof ( System.Windows.Forms.AnchorStyles ) ), "Left" ), CodeBinaryOperatorType.BitwiseOr , new CodeFieldReferenceExpression ( new CodeTypeReferenceExpression ( typeof ( System.Windows.Forms.AnchorStyles ) ), "Right" ) ) ; CodeBinaryOperatorExpression topToo = new CodeBinaryOperatorExpression ( new CodeFieldReferenceExpression ( new CodeTypeReferenceExpression ( typeof ( System.Windows.Forms.AnchorStyles ) ), "Top" ) , CodeBinaryOperatorType.BitwiseOr, leftAndRight ) ; initializeComponent.Statements.Add ( new CodeAssignStatement ( new CodePropertyReferenceExpression ( new CodeVariableReferenceExpression ( "_label" ), "Anchor" ), topToo ) ) ; ... // Set the other properties of the label control // Add the ResumeLayout ( false ) call initializeComponent.Statements.Add ( new CodeMethodInvokeExpression ( new CodeThisReferenceExpression ( ) , "ResumeLayout", new CodeExpression [] { new CodePrimitiveExpression ( false ) } ) ) ; // And finally add initializeComponent to the members for the class ctd.Members.Add ( initializeComponent ) ;

The next example shows how to set a property to a particular value. In this instance, the code emitted is:

_label.TabIndex = 0 ;

Here you need to use CodeAssignmentStatement, which takes two parameters: a left-hand side, or lhs (the thing you are assigning to), and a right-hand side, or rhs (the object being assigned). In this instance, the lhs is a CodePropertyReferenceExpression and the rhs is a CodePrimitiveExpression:

initializeComponent.Statements.Add( new CodeAssignStatement( // This forms the left hand side and is // _label.TabIndex new CodePropertyReferenceExpression( new CodeVariableReferenceExpression ( "_label" ), "TabIndex"), // This is the right hand side new CodePrimitiveExpression ( 0 ) ) ) ;

CodePropertyReferenceExpression itself takes two parameters: the object that the property is defined upon and the property name. Here I've used CodeVariableReferenceExpression, which references the _label object, and specified that the property of interest is TabIndex.

I've surrounded this with a CodeAssignStatement call, which assigns the value 0 to this property. CodePrimitiveExpression can be used for certain built-in types, such as string and integer.

The enumerated value example demonstrates how to use an enumerated value in the code. Enumerated values are stored as fields on the enumerated type. Therefore, in order to get the value of a given field (such as the AnchorStyles.Left value in the example), it is necessary to use CodeFieldReferenceExpression.

For more complex enumerated values where the value is a combination of two or more fields, it is necessary to combine these using CodeBinaryOperatorExpression. This can be used, for example, to combine field values together, as is common when defining the AnchorStyle property. These are generally combined using a logical OR and emitted to code as field1 | field2.

Within the constructed code, there are several places where objects are created and assigned to appropriate properties. One example is shown here:

_label.Location = new System.Drawing.Point(8, 8);

The code that generates this is shown here:

initializeComponent.Statements.Add( new CodeAssignStatement( new CodePropertyReferenceExpression( // Equates to this._label new CodeVariableReferenceExpression ( "_label" ), "Location"), new CodeObjectCreateExpression( // Eguates to new Point (8,8) typeof ( System.Drawing.Point ) , new CodeExpression[]{ new CodePrimitiveExpression ( 8 ) , new CodePrimitiveExpression ( 8 ) } ) ) );

I use the customary CodePropertyReferenceExpression to generate the left-hand side of the statement. CodeObjectCreateExpression is passed the type of the object to be created, and then a collection of the parameters is passed to the constructor. Similar code can be found when setting the size of the label or the initial size of the entire control.

An ASP.NET Example

Now let's look at how to construct an ASP.NET control at run time. Since the code is fairly similar to that in the Windows Forms example, I have omitted it. If you would like to view this code, it is available in the download that accompanies this article. In this example I have created a server control (derived from WebControl) that renders a string that is entered by the user. When you run the site, you can enter some text, as shown in Figure 7, and then generate either an IL or a CodeDOM example.

Figure 7 Generating ASP.NET Controls

Figure 7** Generating ASP.NET Controls **

After clicking one of the buttons, the code generates a server control and loads it up inside a panel. The resulting display is shown in Figure 8. While this isn't the most useful control, it does illustrate the principle that you can create controls at run time that are used on ASP.NET pages.

Figure 8 Generated Control Placed in a Web Page

Figure 8** Generated Control Placed in a Web Page **

With ASP.NET, there are two types of controls: server controls and user controls. A server control resides within an assembly and is commonly created by subclassing WebControl. They typically have excellent design-time behavior for the end user.

A user control, on the other hand, consists of an .ascx file with an associated codebehind file. The .ascx defines the layout of the control and the codebehind defines the behavior. Generating a server control for this example is far easier than generating a user control, so this is the option I have chosen for this article.

The main difference between this and the Windows Forms example is how the control is rendered. With Windows Forms custom controls, each constituent control is added within the InitializeComponent method. With ASP.NET controls, you have a couple options for rendering the HTML output. The first is to generate all of the content for the container control by overriding the Render method and explicitly writing HTML to the output stream. The second is to utilize controls that are added within the CreateChildControls method.

In this example, I am using the first method—an override of the Render method. This example is simple, but provides a basis for understanding how to define controls at run time. The example could be extended to add extra properties and rendering logic, if desired.The control is loaded at runtime using the following code:

Control ctrl = Activator.CreateInstance ( t ) as Control ; t.GetProperty("Text").SetValue ( ctrl, caption, new object[] { } ) ; this.controlPlaceholder.Controls.Add ( ctrl ) ;

The type t represents the generated control, and I've used Activator.CreateInstance to instantiate the control. The next line sets the value of the Text property, showing an example of run-time binding (which I'll expand upon in a moment). The final piece adds the newly created control to a placeholder control on the page.

For more details about creating custom server controls, I suggest the book Developing Microsoft ASP.NET Server Controls and Components by Nikhil Kothari and Vandana Datye (Microsoft Press, 2002). In my opinion, this book offers excellent coverage of all aspects of control creation and is a must-have if you ever define your own server controls.

Note that I have not persisted the generated controls to disk in these examples. I chose not to do this for two reasons. The first, and most obvious, reason is to prove that you can generate controls without persisting them to disk; they can be entirely transient and thus will be deleted once the application is restarted. The second, and less obvious, is that by permitting the application to generate controls on disk, you are opening up a potential security flaw in the application.

If the application were to save the generated assemblies to disk, the ASP.NET process would have to have permission to save DLL files to disk. These files would then be loaded up again to display data to the user. If an intruder were to somehow gain access to writing files to this directory, he could potentially inject code into your application with serious consequences.

If your application is modified to permit generation of controls, I suggest you make that part of an administrative toolset that is not running within a Web site—it should, for instance, be a Windows Forms application. This would read any control definitions from your database, generate the controls, and store these to disk or within a SQL Server database. The ASP.NET site would then simply load these generated controls at run time.

Data Binding

When these generated controls are used on a page, you need some way to bind data from the back-end database objects to the controls. You could use generated code (which is the recommended approach), or you could use reflection as a means of binding data to the UI. In this section, I'll describe both methods. In these examples, I am assuming there is a 1:1 correspondence between properties on the underlying business object and controls on the user interface.

Since the main purpose of this article is to provide strategies for making a high-performance configurable solution, emitting code to perform the data binding is an obvious step to take. You could quite easily generate the data binding code while generating the control (again, assuming there is a direct 1:1 relationship between the UI control and the underlying business object). The code that should be generated is essentially looping through each property and setting the textual value of control to the value of that property, as shown in this code snippet:

control.Text = businessObject.Text;

The appropriate way to emit this code for ASP.NET controls is as an override to the standard DataBind method, defined on the Control class.

Reflection can be used to retrieve data from your business logic component and display this data in the control. When using Visual Studio, a control defines properties as available for data binding by adding the Bindable attribute to the property definition:

[Bindable(true)] public string Text { get; set; }

So, it would be possible to retrieve all the bindable properties of a control using code like that shown in Figure 9. The first line requests a collection of all properties defined on type t that are public instance properties. BindingFlags.DeclaredOnly ensures that only properties of the direct type are used—without this, all properties in the inheritance hierarchy for the type will be returned.

Figure 9 Retrieving the Bindable Properties of a Control

ArrayList bindableProperties = new ArrayList ( ); foreach ( PropertyInfo pi in t.GetProperties ( BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) ) { // Check if this has the Bindable(true) attribute... object[] attributes = pi.GetCustomAttributes ( typeof ( BindableAttribute ) , true ) ; if ( attributes.Length > 0 ) { BindableAttribute bindable = attributes[0] as BindableAttribute ; if ( bindable.Bindable ) bindableProperties.Add ( pi ) ; } }

This code then loops through these properties, looking for any that include the Bindable attribute. For each of these, it specifically checks whether the attribute has been defined as Bindable(true), as it's possible that the developer has used Bindable(false).

With this collection of bindable properties, it is possible to write code, like that shown in Figure 10, to set values of the control based on values in the business object.

Figure 10 Setting Values for Bindable Properties

private void SetProperties ( ArrayList bindableProperties, object control, object businessObject ) { Type bot = businessObject.GetType ( ) ; object[] emptyIndex = new object[] { } ; foreach ( PropertyInfo bindable in bindableProperties ) { PropertyInfo bop = bot.GetProperty ( bindable.Name, bindable.PropertyType ) ; if ( null != bop ) { object value = bop.GetValue ( businessObject, emptyIndex ) ; bindable.SetValue ( control, value , emptyIndex ) ; } } }

This function loops through all bindable properties on the control and, for each, checks to see if a property with the same name and return type exists on the business object. If so, the property on the control is set to the value of the property on the business object. Similar code could be used to read data that the user enters into the UI, then could send the data back to the business object.

Simplifying the Code

The code emitted by this sample at present is fairly detailed. To simplify matters, you can construct a base class (derived from WebControl for ASP.NET or Control for Windows Forms) and include functions on the control to do some of the trickier aspects of control generation. As an example, you can define methods that construct controls using a set of functions, such as the following:

private Label CreateLabelControl ( string ID, string caption ) { Label newLabel = new Label ( ) ; newLabel.ID = ID; newLabel.Text = caption ; this.Controls.Add ( newLabel ) ; }

You could create base class functions to create all types of controls. Then the emitted code would simply have to generate the instance variables for the controls and call these base class functions to do the control creation.

Conclusion

This article has illustrated how you can use two styles of runtime code generation—Reflection.Emit and System.CodeDom—to generate controls for Windows Forms and ASP.NET applications. I've given examples, and described some of the facilities available for these two differing approaches. Now I urge you to try employing these ideas in your own applications.

If you currently generate any of your interface controls using XML definitions, I hope the section on relative performance of these methods has given you the impetus to at least consider changing to a CodeDOM or Reflection.Emit approach. The performance enhancement you'll gain by creating your controls up front will be well worth the investment.

Morgan Skinner works for Microsoft in the UK, where he is an Application Development Consultant specializing in C#, Windows Forms, and ASP.NET. He's been working with .NET since its inception. Check out the Premier Support for Developers (PSfD) team blog.