Introduction
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.
The rundown
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
System.Web.UI.Page
.
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);
}
}
base.Render (writer);
}
- 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);
}
if(cc.HasControls())
{
ProcessControls(cc.Controls);
}
}
}
- The
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
PluginFramework.IAttribute
.
static AttributeFactory()
{
string[] files = Directory.GetFiles(Path.GetDirectoryName(
Assembly.GetExecutingAssembly().CodeBase).Replace("file:\\", ""));
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;
interfaceCount++;
}
}
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);
}
else
{
IAttribute myAttrib =
(IAttribute)Activator.CreateInstance(interfaceType);
MethodInfo InvokeMethodInfo =
interfaceType.GetMethod("GetIdentifier");
ParameterInfo[] InvokeParamInfo =
InvokeMethodInfo.GetParameters();
string identifier =
InvokeMethodInfo.Invoke(myAttrib,
InvokeParamInfo).ToString().ToLower();
asmLocations.Add(identifier, myAttrib);
}
}
}
//***********************************************************
}
- Our plug-ins are then stored in a
Hashtable
along with their attribute names.
private static Hashtable asmLocations = new Hashtable();
- Our
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:
using System.Reflection;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
namespace PluginFramework
{
public interface IAttribute
{
string GetIdentifier();
void Apply(string attributeValue, WebControl wc);
void Apply(string attributeValue, HtmlControl wc);
void ProcessProperty(string attributeValue, PropertyInfo pi, Control c);
}
}
The 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()
{
return "Translate";
}
History
- 2007/01/26 - Article created.