Welcome!

.NET Authors: Liz McMillan, Eric Carter, Maureen O'Gara, Elizabeth White, Dana Gardner

Related Topics: .NET

.NET: Article

Dynamic Page Generation

Dynamic Page Generation

You have probably not escaped seeing the latest commercials for Microsoft Windows Server 2003, which urge listeners to "do more with less"; this has been an aim of software engineering since the very beginning.

When I started writing software using C and C++ on Unix systems, programmers aimed to do more with less by reusing others' header files and precompiled libraries.

Today I find myself doing more with less through many technologies such as COM+ components and commercial extensions to the .NET Framework.

Recently I discovered I could also do more with less through .NET Reflection and dynamically assigning controls to ASP.NET pages and panels.

This article will present an introduction to the technologies and techniques that I've discovered over the past 18 months ­ and that ultimately resulted in the epiphany that has empowered me to do more with less. This article will present an introduction to the technologies and techniques that I've discovered over the past 18 months - and that ultimately resulted in the epiphany that has empowered me to do more with less. The source code for this article can be downloaded from http://www.sys-con.com/sourcec.cfm.

Introduction to Reflection
Unlike unmanaged environments, in which the source code you write is ultimately compiled directly into native machine code for the specific microprocessor upon which it is intended to run, Microsoft .NET code is compiled into Microsoft Intermediate Language (also known as MSIL, or simply IL).

Any Common Language Specification (CLS)­compliant language can create IL, which the Common Language Runtime (CLR) can then run through a process known as just-in-time compilation to convert the IL (such as that shown in Listing 1) to machine code native to the processor on which the CLR is running. The source code for this article can be downloaded from www.sys-con.com/dotnet/sourcec.cfm

In addition to the IL generated by a managed compiler, .NET assemblies also contain metadata (data about data). Within an assembly metadata is a system of descriptors that describe the structural items of an application, e.g., the members and attributes of a given class.

Using the classes within the System.Reflection namespace you can access the information held within an assembly's metadata dynamically at runtime.

private static System.Type m_hashtable = null;
private static System.Type[] m_interfaces = null;

In the preceding C# code I have defined two variables: m_hashtable of type System.Type, and an array m_interfaces, also of type System.Type.

m_hashtable = System.Type.
GetType("System.Collections.Hashtable");

System.Type.GetType is a static method that returns a System.Type based on the supplied case-sensitive, fully qualified class name. Another way of doing this would be to use the C# type of operator as shown below:

m_hashtable = typeof(System.Collections.Hashtable);

System.Type includes many methods that can be used to discover information about the current System.Type object. One such method, GetInterfaces(), returns an array of System.Type objects representing all the interfaces implemented or inherited by the current type. Using GetInterfaces(), we now assign the array of System.Type objects representing the interfaces implemented or inherited for the Hashtable class.

m_interfaces = m_hashtable.GetInterfaces();

Compared to its predecessors, the C and C++ languages, one of the best additions to the C# language is the foreach loop, which we use now to print out the names of all the interfaces discovered by calling GetInterfaces():

foreach(System.Type t in m_interfaces)
{
Console.WriteLine("{0} Realizes {1}",
m_hashtable.Name, t.Name);
}

If you run this code you will see the following output:

Hashtable Realizes IDictionary
Hashtable Realizes ICollection
Hashtable Realizes IEnumerable
Hashtable Realizes ISerializable
Hashtable Realizes IDeserializationCallback
Hashtable Realizes ICloneable

Reflection enables you to produce highly adaptable dynamic applications. However, the very dynamic nature of using Reflection effectively requires you to program defensively.

Imagine the following scenario: you have defined a new interface, ISearchable, and wish to have your code act upon a type differently if the type supports the ISearchable interface. You then deploy your application, which functions without error for many months until a user installs a newer version of the .NET Framework.

It appears that Microsoft has been kind enough to implement their own ISearchable interface (highly possible), which causes your code to throw exceptions of type System.MissingMethodException when the code runs.

Therefore, if you ever write code such as this:

if(type.Name == "ISearchable")
{
}

you might want to consider changing it to behave a little more defensively against changes outside of your control:

if(type.Name == "ISearchable" && type.Namespace ==
"PrecisionObjects.Interfaces")
{
}

It is highly unlikely that Microsoft will implement an interface ISearchable in the namespace PrecisionObjects.Interfaces.

Dynamically Adding Controls to ASP.NET Web Forms
ASP.NET allows you to dynamically add controls to Web Forms using the Controls property of container controls such as the System.Web.UI.WebControls.Panel control.

Panel has this ability because it directly inherits from the ASP.NET base control class System.Web.UI.Control, which provides the Controls property.

ASP.NET System.Web.UI.Page also inherits from System.Web.UI.Control and therefore also contains a Controls collection property. However, any controls added directly to the Controls collection of the page will not be rendered, and an exception of type System.Web.HttpException will be thrown when you attempt to run your code. This is demonstrated in Listing 2. Listing 3 successfully adds the new control to a System.Web.UI.WebControls.Panel control.

ASP.NET allows the developer to add controls dynamically to many other container controls. You will probably find it useful to dynamically add controls to an ASP.NET table control.

You can add controls to both the System.Web.UI.WebControls.Table Cell and the System.Web.UI.WebControls.TableRow controls. I'll show you how in the SearchPage example, which dynamically adds controls using information discovered through Reflection about another class.

Using Reflection to Empower Dynamic Control Creation
I have now introduced two techniques that are very powerful in their own right but become even more powerful when combined to build dynamic ASP.NET Web Forms or user controls.

If you have spent any time writing ASP.NET Web applications you will have undoubtedly discovered the usefulness of ASP.NET user controls (*.ascx controls). If you haven't yet, then you might be convinced of their usefulness after seeing the following example.

Imagine your enterprise application has a Search page that contains three System.Web.UI.WebControls.Panel controls: Search-Panel, ResultsPanel, and DetailsPanel.

SearchNavigation.ascx (see Listings 4 and 5) allows you to dynamically, through Reflection, establish a hyperlink menu for each of the panels on a Web Form and then allow the user to set the appropriate Panel.Visible property to true and all others to false.

If any further panels are subsequently added to the Web Form, the code within SearchNavigation.ascx doesn't have to be updated because it will dynamically discover the new panel at runtime.

Before we can discover the panels that exist within the System .Web.UI.Page control collection we need to retrieve the type of the page:

m_type = typeof(SearchPage);

Just as we used GetInterfaces() before to retrieve the interfaces implemented or inherited for the System.Collections.Hashtable class, we can now use GetFields() to retrieve the fields of the current type, in this case the SearchPage.

FieldInfo[] fields =
m_type.GetFields(BindingsFlags.NonPublic
| BindingsFlags.Instance);

Now we can use the C# foreach loop again to iterate through the returned fields to search for System.Web.UI.WebControls.Panel controls.

foreach(FieldInfo field in fields)
{
if(field.FieldType.FullName ==
"System.Web.UI.WebControls.Panel")
{
}
}

We can create an instance of the System.Web.UI.WebControls.LinkBut ton class for each panel found on the Web Form, while also dynamically adding an event handler for the link button's Click event (see Listing 6).

Finally, we must add the newly created TableRow object to the ASP.NET table control:

CommandTable.Rows.Add(_row);

We have now created a user control that can be used to build a menu of ASP.NET panels on any Web Form. To use the control, all you would need to change is the initial type assigned to m_type within the code; even this could be automated to produce a truly disconnected user control.

As you can see in Figure 1, the link buttons were successfully created for the three panels on the Search page, allowing the user to click between the panels. The event handler will activate the appropriate panel when the user selects a menu item.

The controls on the Search panel are also dynamically constructed at runtime using Reflection and custom attributes.

Custom Attributes
Custom attributes are another tool available to developers using the .NET Framework; they allow you to insert additional metadata into your assembly that can then be accessed through Reflection.

An example of a custom attribute is the System.Web.Services.WebMethodAttribute class, which you are probably more used to seeing within ASP.NET Web services as [WebMethod].

SearchPage.aspx uses another custom attribute class, SearchableAttribute, to distinguish between members of the Publisher class that are searchable and those that are not. The code for SearchableAttribute is shown in Listing 7.

You will notice the use of another attribute in the above code;
[AttributeUsage] is used to define where a custom attribute can be used. In the case of [Searchable] we want it to be applied only to properties defined in classes written in a CLS-compliant language. Therefore, we use the AttributeTargets enumeration to specify this.

SearchPage.aspx
While working on a recent ASP.NET project that involved constructing several different search pages for various entities within the system I began thinking about a way to construct a single search page that would dynamically construct the Search, Results, and Details panels at runtime, enabling me to write a single dynamic search page.

Using the custom attribute [Searchable] it is possible to define in metadata which properties of a given class will be searchable through the dynamic search page. Listing 8 shows the [Searchable] attribute used in the class Publisher.

SearchPage.aspx uses the metadata injected into the Publisher assembly through the [Searchable] attribute to determine which properties of the Publisher class should be displayed within the Search-Panel.

As within the SearchNavigation.ascx user control, we start by assigning the type of the target class (in this case Publisher) to a variable of type System.Type and then get the associated interfaces to ensure that the class realizes the ISearchable interface (see Listing 9).

Assuming that we discover that Publisher does indeed realize the ISearchable interface and that ISearchable does reside within the namespace PrecisionObjects.Interfaces, we can then check which properties of Publisher we should allow our user to search upon.

We start by getting an array of type PropertyInfo, which contains all of the properties for a given type. Again, we can use the C# foreach loop to iterate through the properties and dynamically add the associated label and textbox controls to the SearchPanel.

Listing 10 shows how you can then establish whether or not a given property is adorned with the [Searchable] attribute. I have removed the code to add the controls to the SearchPanel, as it is nearly identical to the code presented previously in constructing the SearchNavigation.ascx user control.

Conclusion
Reflection continues to provide me with an excellent mechanism to produce highly dynamic and reusable code ­ and does enable me to do more with less.

Some of you reading this have probably been thinking that there must be a relative degradation in performance when using Reflection and, unfortunately, all good things do come at a price. However, the performance impact of using Reflection is relatively minor; in fact many aspects of the .NET Framework and Visual Studio .NET wouldn't be possible without it.

More Stories By Doug Holland

A "blue-badge" .net architect and developer at Intel Corporation since March 2007, Doug Holland is part of the Intel Mobility group and is presently working within an advanced tools and development team with an emphasis on graphics performance. He holds a Master's Degree in Software Engineering from Oxford University and has been awarded both the Microsoft MVP and Intel Black Belt Developer awards. Outside of work, Holland enjoys spending time with his wife and four children; and is also an officer in the Civil Air Patrol / U.S. Air Force Auxiliary.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.