创建 HTML 属性插件框架
本文介绍了如何使用插件框架创建您自己的 HTML 属性。
引言
本文基于 Javier Lozano 的文章。 部分代码来自他的文章,并经其明确许可转载。
当我第一次阅读文章 “使用自定义 HTML 属性扩展 ASP.NET Web 控件”时,我立即看到了这项技术的潜在应用,并开始摆弄代码。 我喜欢它的工作方式,它为我解决了很多问题。 但有一件事让我感到困扰:可扩展性。
因此,我自然而然地开始将这个概念转化为插件框架。 本文是该努力的结果。
概要
工作如何完成
此代码的工作方式如下
- 我们创建一个基页,应用程序中的页面将从该基页继承。 这允许我们通过重写适当的事件来挂接到页面生命周期中。 此基页本身必须继承自
System.Web.UI.Page
。/// <summary> /// This is our BasePage, for our plugins to be applied, /// the page containing them need to inherit from this one. /// This page extends System.Web.UI.Page /// </summary> public abstract class BasePage : System.Web.UI.Page
- 然后,我们在基页中重写方法
System.Web.UI.Page.Render(HtmlTextWriter writer)
。 这意味着我们可以在将控件呈现到浏览器之前的最后一刻从页面中获取控件。 这很重要,因为否则我们将错过任何动态添加的 Web 用户控件上控件的自定义属性。 下面的代码只是遍历页面上的服务器端控件(HTML 和 Web 控件)。 - 这些控件被传递给
ProcessControls(ControlCollection c)
方法,该方法递归地扫描所有控件。 AttributeParser
类具有重载的ProcessControl
方法,该方法将 Web 控件或 HTML 控件作为参数。 然后,此方法检查我们的自定义属性列表,如果当前属性在该列表中,则将其传递给相关插件的Apply
方法。 由于此方法已重载,因此它的两个实现将其抽象数据传递给我们的ProcessProperty(string attributeValue, PropertyInfo pi, Control c)
方法,在该方法中执行属性的实际处理。
/// <summary>
/// The Render method is overrides our System.Web.UI.Page.Render(HtmlTextWriter writer)
/// method, and is where we intercept the controls on our page.
/// </summary>
/// <param name="writer">Stream that writes to output page.</param>
protected override void Render(HtmlTextWriter writer)
{
//********************************************************
// The original version of this code overrode
// System.Web.UI.Page.CreateChildControls().
// I found that using this method occured
// before custom web user controls and their
// control collections were added to the page,
// thus I opted to override
// System.Web.UI.Page.Render(HtmlTextWriter writer)
// instead, as it get called after
// our custom web user controls are added to the page.
//********************************************************
foreach(Control c in Controls)
{
if (c is HtmlForm)
{
ProcessControls((c as HtmlForm).Controls);
}
}
base.Render (writer);
}
/// <summary>
/// This method checks if we are deealing with an HtmlControl
/// or a WebControl and passes the control to the relevant method
/// for processing.
/// </summary>
/// <param name="c">Our server sidee forms contrtol collection.</param>
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);
}
}
}
如何加载插件
- 我们的 *PluginFramework.dll* 包含以下类
- 此类具有一个静态构造函数,该构造函数扫描我们的 *bin* 文件夹以查找类型为
PluginFramework.IAttribute
的插件。 - 然后,我们的插件与它们的属性名称一起存储在
Hashtable
中。 - 我们的
GetAttribute(string type)
方法负责为我们的属性提供正确的插件。
/// <summary>
/// This class scans our bin directory
/// for plugins of type PluginFramework.IAttribute.
/// This scan only happens once on Application_Start.
/// </summary>
public sealed class AttributeFactory
/// <summary>
/// Scan for and record plugins and their attribute names.
/// This scan only happens once on Application_Start.
/// </summary>
static AttributeFactory()
{
//***********************************************************
// This is not the most processor friendly way to do this!!
// This section could load plugins from a file or db.
//***********************************************************
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);
}
}
}
//***********************************************************
}
/// <summary>
/// This hashtablecontains all of our plugins and their attribute name.
/// </summary>
private static Hashtable asmLocations = new Hashtable();
关注点
要说明的关键点是,我们的插件通过以下接口进行通信
using System.Reflection;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
namespace PluginFramework
{
/// <summary>
/// This is the interface that our plugins must adhere
/// to in ordedr to be able to make use of our framework.
/// </summary>
public interface IAttribute
{
/// <summary>
/// This method gets the plugin identifier.
/// This identifier becomes the recognised attribute name,
/// and as such should remain unique.
/// </summary>
/// <returns>String - The plugin identifier</returns>
string GetIdentifier();
/// <summary>
/// This method gets the property info for our control and
/// passes it of to ProcessProperty(string attributeValue, PropertyInfo pi, Control c)
/// for processing.
/// </summary>
/// <param name="attributeValue">The value of our attribute.</param>
/// <param name="wc">Our WebControl</param>
void Apply(string attributeValue, WebControl wc);
/// <summary>
/// This method gets the property info for our control and
/// passes it of to ProcessProperty(string attributeValue, PropertyInfo pi, Control c)
/// for processing.
/// </summary>
/// <param name="attributeValue">The value of our attribute.</param>
/// <param name="wc">Our HtmlControl.</param>
void Apply(string attributeValue, HtmlControl wc);
/// <summary>
/// This method performs the logic necessary to do the actuall work
/// that is required by our plugin.
/// </summary>
/// <param name="attributeValue">The value of our attribute.</param>
/// <param name="pi">Our PropertyInfo.</param>
/// <param name="c">Our Control.</param>
void ProcessProperty(string attributeValue, PropertyInfo pi, Control c);
}
}
GetIdentifier()
方法负责为插件返回唯一的标识符/名称。 该标识符将用作属性名称。 即
/// <summary>
/// This method gets the plugin identifier.
/// This identifier becomes the recognised attribute name,
/// and as such should remain unique.
/// </summary>
/// <returns>String - The plugin identifier</returns>
public string GetIdentifier()
{
return "Translate";
}
历史
- 2007/01/26 - 文章创建。