This article is based on that of Javier Lozano. A portion of this code is from his article, and is reproduced with his express permission.
When I first read the article "Extending ASP.NET Web Controls With Custom HTML Attributes", I immediately saw the possible applications of this technology and started to play around with the code. I liked the way that it worked, and it solved a number of problems for me. One thing that bothered me though was this: Extensibility.
So naturally, I set out to turn this concept into a plug-in framework. This article is the result of that effort.
How the work gets done
This code works as follows:
- We create a base page from which the pages in our application will inherit. This allows us to hook into the page lifecycle by overriding the appropriate events. This base page itself must inherit from
public abstract class BasePage : System.Web.UI.Page
- We then override the method
System.Web.UI.Page.Render(HtmlTextWriter writer) in our base page. This means that we can grab our controls out of the page at the last minute before they are rendered to the browser. This is important because otherwise we will miss the custom attributes of controls on any dynamically added web user controls. The code below simply iterates through the server side controls on our page (both HTML and Web controls):
protected override void Render(HtmlTextWriter writer)
foreach(Control c in Controls)
if (c is HtmlForm)
ProcessControls((c as HtmlForm).Controls);
- These controls are handed off to the
ProcessControls(ControlCollection c) method that recursively scans all controls.
private void ProcessControls(ControlCollection c)
foreach (Control cc in c)
if (cc is WebControl)
AttributeParser.ProcessControl(cc as WebControl);
if(cc is HtmlControl)
AttributeParser.ProcessControl(cc as HtmlControl);
AttributeParser class has an overloaded
ProcessControl method that takes either a Web or an HTML control as an argument. This method then checks our list of custom attributes and if the current attribute is in that list, passes it off to the relevant plug-in's
Apply method. Since this method is overloaded, the two implementations of it pass their abstracted data to our
ProcessProperty(string attributeValue, PropertyInfo pi, Control c) method where the actual processing of the attribute takes place.
How the plug-ins get loaded
- Our PluginFramework.dll contains the following class:
public sealed class AttributeFactory
- This class possesses a static constructor that scans our bin folder for plug-ins of type
string files = Directory.GetFiles(Path.GetDirectoryName(
foreach(string s in files)
if(Path.GetExtension(s).ToLower() == ".dll")
Assembly asm = Assembly.LoadFile(s);
Type interfaceType = null;
int interfaceCount = 0;
foreach(Type t in asm.GetTypes())
Type iType = t.GetInterface("IAttribute");
if(iType != null)
interfaceType = t;
if(interfaceType == null)
Debug.Write("Interface not found in plugin - " + asm.FullName);
else if(interfaceCount > 1)
Debug.Write("More that one interface found - " + asm.FullName);
IAttribute myAttrib =
MethodInfo InvokeMethodInfo =
ParameterInfo InvokeParamInfo =
string identifier =
- Our plug-ins are then stored in a
Hashtable along with their attribute names.
private static Hashtable asmLocations = new Hashtable();
GetAttribute(string type) method is then responsible for providing the correct plug-in for our attribute.
Points of interest
A crucial point to make is that our plug-ins communicate through the following interface:
public interface IAttribute
void Apply(string attributeValue, WebControl wc);
void Apply(string attributeValue, HtmlControl wc);
void ProcessProperty(string attributeValue, PropertyInfo pi, Control c);
GetIdentifier() method is responsible for returning a unique identifier/name for the plug-in. This identifier is what will be used as the attribute name. I.e.:
public string GetIdentifier()
- 2007/01/26 - Article created.