HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

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 below.

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.

Figure 1

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.

About The Author
Doug Holland, a professional .NET consultant based in Roseville, CA, has over eight years of experience in designing and developing software. Doug currently works with the .NET Framework and specializes in consulting for companies using Rational XDE for .NET development. doug.holland@precisionobjects.com

	



Listing 1: HelloWorld implemented directly using MSIL

.assembly HelloWorld { }
.assembly extern mscorlib { }
.method public static void main() {
    .entrypoint
    ldstr "Hello .NET World"
    call void
        [mscorlib]System.Console::WriteLine
        (Class System.String)
    ret
}


Listing 2:  Adding controls directly to a System.Web.UI.Page controls
collection causes a System.Web.HttpException to be thrown


/// <summary>
/// System.Web.HttpException thrown when attempt
/// ing to add controls
/// dynamically to the controls collection of a
/// System.Web.UI.Page
/// directly. Therefore we must add them to a
/// Panel instead.
/// </summary>
private void Page_Load(object sender, EventArgs e)
{
    try
    {
System.Web.UI.WebControls.TextBox _textbox
           = new System.Web.UI.WebControls.TextBox();

           textbox.Text = "Dynamically Inserted TextBox";

this.Controls.Add(_textbox); // will not render textbox!!!!
    }
    catch(System.Web.HttpException ex)
    {
     // System.Web.HttpException will occur if you
     // add controlsdirectly to the
     // System.Web.UI.Page controls collection.
    }
    catch(Exception ex)
    {
    }
}

Listing 3: Using a System.Web.UI.WebControls.Panel to add controls dynamically

protected System.Web.UI.WebControls SearchPanel
                = new System.Web.UI.WebControls();

/// <summary>
/// Creates a TextBox control dynamically and
/// successfully adds it
/// to the SearchPanel controls collection.
/// </summary>
private void Page_Load(object sender, EventArgs e)
{
    try
    {
System.Web.UI.WebControls.TextBox _textbox
           = new System.Web.UI.WebControls.TextBox();

            _textbox.Text = "Dynamically Inserted TextBox";

SearchPanel.Controls.Add(_textbox);
    }
    catch(System.Web.HttpException ex)
    {
     // What no worries ;)
    }
    catch(Exception ex)
    {
     // What no worries ;)
    }
}


Listing 4:  SearchNavigation.ascx Page_Load Event Handler

       private System.Type m_type = null;

private void Page_Load(object sender, EventArgs e)
{
    try
    {
      m_type = typeof(SearchPage);
      FieldInfo[] fields =
      m_type.GetFields(BindingsFlags.NonPublic |
            BindingsFlags.Instance);

               TableRow _row = new TableRow();

  foreach(FieldInfo field in fields)
  {
      if(field.FieldType.FullName
          == "System.Web.UI.WebControls.Panel")
           {
      LinkButton _linkButton = new LinkButton();
     linkButton.Text = field.Name.Replace("Panel",
     string.Empty);
     _linkButton.ForeColor = Color.FromArgb(99,
     99, 99);
     _linkButton.Click
     += new EventHandler(this.LinkButton_Clicked);

     TableCell _cell = new TableCell();
               _cell.Controls.Add(_linkButton);
               _row.Controls.Add(_cell);
                    }
  }
        }
    catch(Exception ex)
    {
        // What no worries ;)
    }
}


Listing 5:  SearchNavigation.ascx LinkButton_Clicked Event Handler

private void LinkButton_Clicked(object sender, EventArgs e)
{
            try
           {
               foreach(System.Web.UI.Control
               _control in this.Parent.Controls)
               {
                   if(_control.Type ==
typeof(System.Web.UI.WebControls.Panel))
                   {
                       if(_control.ID ==
                       ((LinkButton)sender).Text
                       + "Panel")
                          _control.Visible =
                          true;
                       else
                          _control.Visible =
                          false;
                   }
               }
           }
           catch(Exception ex)
           {
               // What no worries ;)
           }
       }


Listing 6

foreach(FieldInfo field in fields)
{
    if(field.FieldType.FullName ==
    "System.Web.UI.WebControls.Panel")
    {
               LinkButton _linkButton = new
               LinkButton();
               _linkButton.Text = field.Name.Re
               _place("Panel", string.Empty);
               _linkButton.ForeColor = Color.From
               Argb(99, 99, 99);
               _linkButton.Click
                           += new EventHandler(
                           this.LinkButton_
                           Clicked);

              TableCell _cell = new
               TableCell();
               _cell.Controls.Add(_linkButton);
               _row.Controls.Add(_cell);

    }
}


Listing 7:  SearchAttribute.cs

using System;

namespace PrecisionObjects.CustomAttributes
{
    /// <summary>
    /// class SearchableAttribute
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class SearchableAttribute : Attribute
    {
        private string m_validation;

        public SearchableAttribute(string validation)
        {
            m_validation = validation;
        }
    }
}


Listing 8: Publisher.cs

using System;
using PrecisionObjects.CustomAttributes;
using PrecisionObjects.Interfaces;

namespace PrecisionObjects.Organizations
{
    public class Publisher : ISearchable
    {
        private string m_name;
        private string m_city;
        private string m_state;
        private string m_country;
        private string m_contact;

        /// <summary>
        /// public string Name ~ gets / sets the
        /// publisher name
        /// </summary>
        [Searchable("Insert validation
        expression")]
        public string Name
        {
            get
            {
                return m_name;
            }
            set
            {
                m_name = value;
            }
        }

        /// <summary>
        /// public string City ~ gets / sets the
        /// publisher city
        /// </summary>
        [Searchable("Insert validation
        expression")]
        public string City
        {
            get
            {
                return m_city;
            }
            set
            {
                m_city = value;
            }
        }

        /// <summary>
        /// public string State ~ gets / sets the
        /// publisher state
        /// </summary>
        [Searchable("Insert validation
        expression")]
        public string State
        {
            get
            {
                return m_state;
            }
            set
            {
                m_state = value;
            }
        }

        /// <summary>
        /// public string Country ~ gets / sets
        /// the publisher country
        /// </summary>
        [Searchable("Insert validation
        expression")]
        public string Country
        {
            get
            {
                return m_country;
            }
            set
            {
                m_country = value;
            }
        }

        /// <summary>
        /// public string Contact ~ gets /
        /// sets the publisher contact
        /// </summary>
        public string Contact
        {
            get
            {
                return m_contact;
            }
            set
            {
                m_contact = value;
            }
        }

    }
}


Listing 9

=m_type = typeof(Publisher);

    // establish if m_type implements ISearchable
    if(m_type.IsClass && !m_type.IsAbstract)
    {
        m_interfaces = m_type.GetInterfaces();

        foreach(System.Type type in m_interfaces)
        {
                  if(type.Name == "ISearchable"
                  &&
                            type.Namespace ==
                            "PrecisionObjects.
                            Interfaces")
                  {

Listing 10

    PropertyInfo[] props = m_type.GetProperties();

    foreach(PropertyInfo p in props)
    {
        string name = p.Name;
           object[] supported
              = p.GetCustomAttributes(typeof
              (SearchableAttribute), false);

        if(supported.Length != 0) // Property Is
        Searchable
        {
            // Dynamically add associated controls
            // to ASP.NET table
           }
       }

Additional Code: ~zip file 7.61 KB

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com