在阅读了Javier Lozano的文章《ExtendingASP.NETWeb Controls With Custom HTML Attributes》后,立即看到了这项技术的应用潜力,并开始尝试编写代码。这种方式的运作方式让感到满意,它解决了遇到的一些问题。但有一件事让感到困扰,那就是扩展性。因此,自然而然地开始将这个概念转化为一个插件框架。本文就是这项努力的结果。
这段代码的工作原理如下:
创建了一个基页面,应用程序中的页面将从这个基页面继承。这允许通过覆盖适当的事件来钩住页面生命周期。这个基页面本身必须从System.Web.UI.Page继承。
public abstract class BasePage : System.Web.UI.Page
然后覆盖了System.Web.UI.Page.Render(HtmlTextWriter writer)方法。这意味着可以在将控件渲染到浏览器之前的最后一刻从页面中获取控件。这很重要,否则将错过任何动态添加的Web用户控件上的自定义属性。下面的代码简单地遍历了页面上的服务器端控件(包括HTML和Web控件):
protected override void Render(HtmlTextWriter writer)
{
foreach (Control c in Controls)
{
if (c is HtmlForm)
{
ProcessControls((c as HtmlForm).Controls);
}
}
base.Render(writer);
}
这些控件被传递给ProcessControls(ControlCollection c)方法,该方法递归扫描所有控件。
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);
}
}
}
AttributeParser类有一个重载的ProcessControl方法,它接受一个Web或HTML控件作为参数。然后,该方法检查自定义属性列表,如果当前属性在该列表中,则将其传递给相关插件的Apply方法。由于这个方法是重载的,这两个实现都将它们的抽象数据传递给ProcessProperty(string attributeValue, PropertyInfo pi, Control c)方法,在这里实际处理属性。
PluginFramework.dll包含以下类:
public sealed class AttributeFactory
这个类拥有一个静态构造函数,它扫描bin目录以查找PluginFramework.IAttribute类型的插件。这个扫描只在Application_Start时发生一次。
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 than 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);
}
}
}
}
插件然后存储在一个Hashtable中,以及它们的属性名称。
private static Hashtable asmLocations = new Hashtable();
GetAttribute(string type)方法然后负责为属性提供正确的插件。
一个关键的要点是插件通过以下接口进行通信:
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);
}
}
public string GetIdentifier()
{
return "Translate";
}