在 ASP.NET 中,扩展程序提供程序组件: IExtenderProvider实现

分享于 

20分钟阅读

Web开发

  繁體 雙語

内容

简介

为所有或者某些类型的控件添加额外属性,Visual Studio 提供了一种优雅的方法,这种方法类似于多重继承。 传统上,必须将所有需要额外属性的控件子类放入类库中,并将子类控件添加到工具箱。 另一种方法是创建一个实现 IExtenderProvider 接口的组件。 将它的放入组件栏,Visual Studio 将显示它的属性,其中包含窗体上所有合适的控件。 WinForms工具提示提供程序是提供程序组件的一个示例。

本文假设你对提供程序组件工作方式有基本的理解。 如果以前没有遇到过 IExtenderProvider,请先阅读以下介绍文章之一:

这个 IExtenderProvider 机制通常被描述为在nntp中使用,因这里有很好的理由。 在MSDN库文章的底部,写着:

注意,Windows 窗体控件的扩展程序提供程序的实现与
ASP.NET 服务器控件。

对,它对 ASP.NET 服务器控件无效,因为 Visual Studio 2003中所需的设计时间支持已经断开。 好消息是有一个解决办法。

本文提供了一个可以用于为 ASP.NET 服务器控件开发扩展程序提供程序的类。 首先我们展示 Visual Studio 中出现的错误,以及如何修复它。 如果只对组件感兴趣,跳过该部分,并只使用组件代码读取

我们只讨论作为组件实现的扩展。 iPhone 7 还没出来,我们已经在iPhone上获取细节 8,或者不管是想到下一步。 Andy是基于web控件的扩展程序提供者的一个示例。

Visual Studio 有什么问题

假设我们想编写一个扩展程序提供程序 MyProvider,这些提供程序是非常有用的。 它将单个属性添加到所有网页控件中,一个名为 DoMagic的布尔值:

[ProvideProperty("DoMagic", [__b__]typeof(System.Web.UI.Control))]public class MyProvider : System.ComponentModel.Component, IExtenderProvider
{
 // IExtenderProvider implementationpublic bool CanExtend (object AExtendee)
 {
 return (AExtendee is System.Web.UI.Control)
 }
 // Retrieve the value of the DoMagic property for a controlpublic bool GetDoMagic (object AExtendee)
 {
 if (_Values.ContainsKey (AExtendee))
 {
 return _Values[AExtendee];
 }
 else
 {
 returnfalse;
 }
 }
 // Set the value of the DoMagic property for a controlpublicvoid SetDoMagic (object AExtendee, bool AYes)
 {
 _Values[AExtendee] = AYes;
 if (AYes)
 {
 (AControl as System.Web.UI.Control).PreRender += 
 new EventHandler (MagicStuff);
 }
 }
 private void MagicStuff (object sender, EventArgs e)
 {
 if (GetDoMagic (sender))
 {
 // Here the magic happens }
 }
 // The provider component stores the property valuesprivate Hashtable _Values = new Hashtable ();
}

在类库中放置这里组件,编译库,将组件添加到工具箱中,然后将它的放到新 ASP.NET 页的组件栏中。 页面中的所有网页控件都有一个额外的属性,DoMagic。 Visual Studio 自动将 InitializeComponent 方法添加到页面后面的代码中:

[__b__]private void InitializeComponent()
{
 this.MyProvider1 = new MyProvider();
}

将标签和文本框添加到页中,并将 DoMagic的值设置为 true。 你将期望 InitializeComponent 方法现在读取:

[__b__]private void InitializeComponent()
{
 this.MyProvider1 = new MyProvider();
 this.MyProvider1.SetDoMagic (this.Label1, true);
 this.MyProvider1.SetDoMagic (this.TextBox1, true);
}

但是它写着:

[__b__]private void InitializeComponent()
{
 this.MyProvider1 = new MyProvider();
 this.MyProvider1.SetDoMagic (this, false);
}

Visual Studio 不知道如何存储所提供属性的值 ! 对于具有非默认属性值的每个控件,应该添加 SetDoMagic 调用,但它将向页对象添加单个调用 SetDoMagic,通常传递默认属性值。 如果扩展程序可以扩展页面对象,它将为与页对象匹配的类型的每个属性添加 SetXXX 调用,这一点更为深入。

解决方法失败

property storage存储问题是中影响与扩展程序提供者协同工作的唯一 Bug。所有其他设计时间支持都可以很好地解决这个问题。 但它是一个关键的Bug: 如果不正确地存储和初始化属性值,则不能使用扩展程序提供程序。

解决方法的核心是使用( e ) 序列化来存储所有属性值。 InitializeComponent 方法的外观如下:

[__b__]private void InitializeComponent()
{
 this.MyProvider1 = new MyProvider();
 this.MyProvider1.PropertyData = @"AAEAA -- many more characters -- Cw==";
 this.MyProvider1.VSDesigned = this;
}

PropertyData 字符串包含所提供属性( 示例中的_Values 哈希表的例如 )的序列化值。 由于 PropertyData 是提供程序组件的public 属性,Visual Studio 知道如何在设计时处理它。

第二个属性 VSDesigned 是反序列化 PropertyData 字符串所必需的。 字符串不能包含对象引用( 像 this.Label1 ),因此我们将序列化控件的NAME。 在反序列化过程中,我们需要知道顶级( 页或者 UserControl ) 对象,以便将名称转换为控件引用。 由于组件在运行时没有直接访问它的所有者的权限,因此我们需要一个额外的属性来实现。

SetDoMagic 调用将遵循属性的分配。 由于赋值不正确,我们不希望它们出现在 InitializeComponent 方法中。 简单:告诉 Visual Studio Page 或者 UserControl 对象不能被扩展,并且 SetDoMagic 调用消失了。 因此,在设计时不可能使用IDE为页对象设置提供的属性。 如果需要这样做,手动添加 SetDoMagic 调用。

使用组件组件的

源代码文件 ExtenderProvider.cs 声明了基组件 ExtenderProviderComponent 和支持类 ExtenderProviderEngine。 如果扩展程序提供程序是组件,则从 ExtenderProviderComponent 派生组件并添加 ProvideProperty 属性:

[ProvideProperty ("DoMagic", [__b__]typeof(System.Web.UI.Control))]public class MyProvider : GoodHeavens.ComponentModel.ExtenderProviderComponent
{

ProvideProperty 属性声明的属性必须是可以序列化的。 所有本机. NET 类型和枚举都是可以序列化的;更复杂的类型必须实现ISerializable接口。 注意,不能为具有不同类型的同一属性添加两个 ProvideProperty 属性- Visual Studio 只能看到最上面的属性- 这也是没有文档的"功能"。

基类 ExtenderProviderComponent 已经实现了 IExtenderProvider 接口,只允许扩展web控件。 如果要进一步限制,请重写 CanExtendControl 方法:

[__b__]protectedoverride bool CanExtendControl (System.Web.UI.Control AExtendee)
{
 // Your logic goes here; return true to allow extension}

基类为属性提供存储。 每个属性都有一个( 唯一) NAME。 在提供的属性的get/set functions中使用:

[__b__]public bool GetDoMagic (System.Web.UI.Control AExtendee)
{
 // GetControlPropertyValue arguments: control to extend,// property name, default value return (bool )Engine.GetControlPropertyValue (AExtendee, "DoMagic", false);
}publicvoid SetDoMagic (System.Web.UI.Control AExtendee, bool AYes)
{
 // SetControlPropertyValue arguments: control to// extend, property name, value, isdefault// isdefault is a boolean that indicates// whether the value is the default value. Engine.SetControlPropertyValue (AControl, "DoMagic", AYes,!AYes);
}

在运行时,你需要钩子到页面生成过程的事件,重写 InitialiseHandlers 方法:

protectedoverridevoid <A name=InitialiseHandlers>InitialiseHandlers</A> ()
{
 foreach (System.Web.UI.Control c in Engine.ControlsWithValue ("DoMagic")
 {
 c.PreRender += new EventHandler (MagicStuff);
 }
}

最后,你必须添加事件处理程序来实现你的组件所著名的所有神奇的东西:

private void MagicStuff (object sender, EventArgs e)
 {
 // Here the magic happens }
}

向类库添加组件,将组件添加到工具箱中,你就可以。 可以通过 Visual Studio IDE输入所提供属性的值,除了你设计(" this"在后面的代码中)的页面或者 UserControl 之外的所有web控件。 如果需要,手动将一行代码添加到 Page_Load 方法中:

[__b__]private void Page_Load (object sender, System.EventArgs e)
{
 MyProvider1.SetDoMagic (this, true);
}

对引擎代码的演练。

引擎 ExtenderProviderEngine 由四个部分组成,我们将按照以下顺序讨论它们: 存储属性值, hacks 使 Visual Studio 工作,对象 生命周期管理插件和支持自定义扩展程序提供程序的支持。 最好的文档是源代码本身,但是在本节中,我们要强调一些关键的设计决策。

属性存储

提供的属性由字符串代码标识。 代码可能与属性 NAME 相同,但它不一定要。 代码用作哈希表的键,哈希表具有第二个哈希表作为值。 第二个表具有扩展控件作为键,它的值为属性值。 仅存储非默认属性值。

这里的主要问题是序列化。 由于我们发现 Hashtable 类在设计时没有正确序列化,所以我们使用自定义类来表示哈希表并实现 ISeriazable 接口。 自定义类将键写入字符串,并使用. NET 序列化机制序列化值。 这就是属性值必须是可以序列化的类型的原因。 序列化数据是base64编码的,并存储在字符串中:

[__b__]public string PropertyData
{
 get
 {
 if (HasPropertyData)
 {
 // Serialize the data MemoryStream ms = new MemoryStream ();
 BinaryFormatter bf = new BinaryFormatter ();
 bf.Serialize (ms, _PropertyList);
 return Convert.ToBase64String (ms.ToArray());
 }
 else
 {
 return null;
 }
 }
 set
 {
 // (Code omitted; see source file)// Deserialize the data MemoryStream ms = new MemoryStream (Convert.FromBase64String (value));
 BinaryFormatter bf = new BinaryFormatter ();
 _PropertyList = (NamedKeyCollection )bf.Deserialize (ms);
 }
}

通常,控件对象是哈希表的键。 我们无法序列化对象引用,因此我们编写控件的UniqueID。 但是,如果设计 UserControlUniqueID 还包括控件的NAME,并且在设计时和运行时,NAME 是不同的。 我们将控件的NAME 相对于 UserControl ( 或者 Page 对象) 存储,因这里需要对反序列化进程中的UserControl 进行引用。 这说明了 VSDesigned 属性: 反序列化后,控件仍然由 NAME 引用,但一旦设置了 VSDesigned 属性,我们可以查找控件:

[__b__]public System.Web.UI.Control VSDesigned
{
 get
 {
 return _VSDesigned;
 }
 set
 {
 if (value!= null)
 {
 _VSDesigned = value;
 ReplaceKeys ();
 }
 }
}

这个序列化过程导致我们只能扩展web控件,因为它们是唯一具有 easy-to-use NAME的。 可以使用 Site.Name 在设计时和反射上使用,但是在设计时不容易从组件中检索 Page 或者 UserControl 对象。

Visual Studio 黑客

两个 public 属性 PropertyDataVSDesigned 负责发布序列化属性值,但还有另外两个问题: 如何查找 VSDesigned的值,以及如何获取 Visual Studio 以保存属性。

我们无法在设计时控制扩展程序提供程序的创建。 首次创建时,将使用组件的默认构造函数。 由于组件没有检索它属于的对象的方法,所以我们必须找到另一种方法。 我们知道创建 IExtenderProvider.CanExtend 之后,所提供的属性的集/for方法被称为。 如果为web控件调用其中之一,我们可以遍历控件层次结构以查找 Page 或者 UserControl 对象。 在层次结构中找到了仍具有 非空 Site.Name 属性( 即使是在UserControl的顶部,层次也是一个 Page 对象)的控件:

[__b__]privatevoid CheckVSDesigned (System.Web.UI.Control AExtendee)
{
 // (Code omitted; see source file) for (System.Web.UI.Control cComponent = AExtendee;
 cComponent!= null; cComponent = cComponent.Parent)
 {
 if (cComponent.Site!= null && cComponent.Site.Name.Length >0)
 {
 _VSDesigned = cComponent;
 }
 }
 if (_VSDesigned!= null)
 {
 NotifyDesignerOfChange ();
 }
 // (Code omitted; see source file)}

因此,VSDesigned 属性的值发生更改,而页上的另一个控件进行检查。 Visual Studio 不够聪明,无法识别。 我们通过调用 NotifyDesignerOfChange 方法来实现这一点,该方法主要由 保罗。::

[__b__]protectedvoid NotifyDesignerOfChange ()
{
 // (Code omitted; see source file)// Get the designer objects from the Site IDesignerHost dhDesigner = _Site.GetService
 (typeof (IDesignerHost)) as IDesignerHost;
 if (dhDesigner!= null)
 {
 IComponentChangeService ccsChanger = dhDesigner.GetService
 (typeof (IComponentChangeService)) as IComponentChangeService;
 // Raise the OnComponentChanged to tell the// designer that our component has changed.//"_Component" in this case is the component that has changed.// You need to call OnComponentChanging as well! ccsChanger.OnComponentChanging (_Component, null);
 ccsChanger.OnComponentChanged (_Component, null, null, null);
 }
 // (Code omitted; see source file)}

如果任何属性值可能已经更改,也会调用这里方法。 如果更改了扩展程序提供程序的属性,Visual Studio 将检查扩展程序提供程序的属性并更新页方法的InitializeComponents 中的分配。

生命周期管理解决方案

组件的行为在设计时和运行时是不同的。 我们跟踪(。LifeCycleStage 枚举) 中的阶段:

Design-timeRun-time
创建扩展程序提供程序设计设计阶段初始化
PropertyData 赋值之后PropertyDataLoadedPropertyDataLoaded
VSDesigned 赋值之后设计设计阶段运行时

第一次 Visual Studio 使用默认构造函数初始化组件。 设置 PropertyDataVSDesigned 属性之后,Visual Studio 创建另一个实例并使用 InitializeComponents 方法中提到的值设置两个属性。 在运行时执行 InitializeComponents 方法,调用 InitialiseHandlers 方法,以便扩展程序提供程序可以钩子到页生成进程。

你可能记得,如果允许扩展 Page 或者 UserControl 对象,Visual Studio 将在属性分配之后添加不正确的SetXXX 调用( 因为属性值不正确)。 我们已经找到了一种检测和忽略这些调用的方法。 需要的是一种方法来获取 Visual Studio 调用 SetXXX 调用之后的组件之一。 在运行时可能: 只需订阅页面事件的OnInit,该事件在调用 InitializeComponents 之后引发。 问题在于,在设计时设置组件的属性时不会引发这里事件。 因这里,不可以能设置可以靠的方法来编辑使用IDE的Page 或者 UserControl 对象的提供属性。

对扩展程序提供程序的支持

除了访问属性值的GetControlPropertyValueSetControlPropertyValue 方法之外,扩展程序提供程序还可以使用其他方法。

PropertyHasValues 方法指示是否有特定属性的非默认值的控件。 HasPropertyData 属性告诉你是否有任何具有非默认值的控件。

ControlsWithValue 方法返回具有非默认值( 为特定属性选择)的所有控件的集合。 集合可以在 foreach 语句中使用。

历史记录

  • July 26,2004 - 写入。
  • October:: 源代码已经更新
便笺

这篇文章的副本可以在我 网站。


COM  IMP  ext  Implementation  asp-net  Extend