使用嵌套的可选控件创建自定义服务器控件
本文关键字:控件 创建 自定义 服务器控件 嵌套 | 更新日期: 2023-09-27 18:25:45
我想创建一个自定义服务器控件VersionedContentControl
,它将允许我指定最终标记的不同变体。
示例用法:
<custom:VersionedContentControl runat="server" VersionToUse="2">
<ContentVersions>
<Content Version="1">
<asp:HyperLink runat="server" ID="HomeLink" NavigateUrl="~/Default.aspx">Home</asp:HyperLink>
</Content>
<Content Version="2">
<asp:LinkButton runat="server" ID="HomeLink" OnClick="GoHome">Home</asp:LinkButton>
</Content>
<Content Version="3">
<custom:HomeLink runat="server" ID="HomeLink" />
</Content>
</ContentVersions>
</custom:VersionedContentControl>
使用上面的标记,我希望在页面上使用唯一的LinkButton
控件。
长话短说
我在尝试定义这个自定义控件时遇到了很大的困难。我甚至还没能在MSDN上找到一个使用这样的嵌套控件的好例子。相反,我不得不以以下博客文章为例:
- http://blog.spontaneouspublicity.com/child-collections-in-asp-net-custom-controls
- http://www.tomot.de/en-us/article/2/asp.net/how-to-create-an-asp.net-control-that-behaves-as-a-template-container-to-nest-content-via-markup
不幸的是,我所尝试的一切都以惨痛的失败告终。这是我目前拥有的:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
namespace CustomControls
{
[ParseChildren(true)]
[PersistChildren(false)]
public class VersionedContentControl : Control, INamingContainer
{
public string VersionToUse { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
public IList<Content> ContentVersions { get; set; }
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
var controlToUse = ContentVersions.Single(x => x.Version == VersionToUse);
Controls.Clear();
controlToUse.InstantiateIn(this);
}
}
public class Content : ITemplate
{
public string Version { get; set; }
public void InstantiateIn(Control container)
{
// I don't know what this method should do
}
}
public class ContentVersionsList : List<Content> {}
}
尽管我还没有实现InstantiateIn
,但我的内容的所有3个版本都出现在页面上;它显示了3个链接。
此外,除非为每个嵌套控件指定不同的ID
属性值,否则我实际上无法使用该控件;我不能全部使用"HomeLink"
。我希望能够重用ID
,这样我就可以从代码后面访问控件。
我意识到,通常情况下,禁止为页面上的多个控件指定重复的ID
值。但是,在MSDN System.Web.UI.MobileControls.DeviceSpecific
文档中,示例对嵌套控件使用了重复的ID
值。事实上,这个例子非常接近我想要做的;它根据移动设备兼容性过滤器来区分内容。
<mobile:Form id="Form1" runat="server">
<mobile:DeviceSpecific Runat="server">
<Choice Filter="isHTML32">
<HeaderTemplate>
<mobile:Label ID="Label1" Runat="server">
Header Template - HTML32</mobile:Label>
<mobile:Command Runat="server">
Submit</mobile:Command>
</HeaderTemplate>
<FooterTemplate>
<mobile:Label ID="Label2" Runat="server">
Footer Template</mobile:Label>
</FooterTemplate>
</Choice>
<Choice>
<HeaderTemplate>
<mobile:Label ID="Label1" Runat="server">
Header Template - Default</mobile:Label>
<mobile:Command ID="Command1" Runat="server">
Submit</mobile:Command>
</HeaderTemplate>
<FooterTemplate>
<mobile:Label ID="Label2" Runat="server">
Footer Template</mobile:Label>
</FooterTemplate>
</Choice>
</mobile:DeviceSpecific>
</mobile:Form>
如果能看看这些控件的来源,看看它们是如何做到这一点的,那就太好了,但不幸的是,它不是开源的。
我的问题
如何创建一个自定义服务器控件,该控件包含嵌套控件列表,并且仅基于属性呈现一个嵌套控件?理想情况下,在单独的嵌套控件之间重新使用ID。
tl;dr
我最终使用了MultiView
控件。它不允许重复的ID,但它满足了我的所有其他要求,并使我避免了维护任何自定义代码。
我尝试了什么
我终于能够用这个代码得到一些工作的东西:
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
namespace CustomControls
{
[ParseChildren(true)]
[PersistChildren(false)]
public class VersionedContent : Control
{
public string VersionToUse { get; set; }
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(ContentContainer))]
[TemplateInstance(TemplateInstance.Multiple)]
public List<Content> ContentVersions { get; set; }
public override ControlCollection Controls
{
get
{
EnsureChildControls();
return base.Controls;
}
}
public ContentContainer ContentContainer
{
get
{
EnsureChildControls();
return _contentContainer;
}
} private ContentContainer _contentContainer;
protected override void CreateChildControls()
{
var controlToUse = ContentVersions.Single(x => x.Version == VersionToUse);
Controls.Clear();
_contentContainer = new ContentContainer();
controlToUse.InstantiateIn(_contentContainer);
Controls.Add(_contentContainer);
}
}
public class Content : Control, ITemplate
{
public string Version { get; set; }
public void InstantiateIn(Control container)
{
container.Controls.Add(this);
}
}
public class ContentContainer : Control { }
}
这让我可以像这样使用控制:
<custom:VersionedContent ID="VersionedContentControl" runat="server" VersionToUse="1">
<ContentVersions>
<custom:Content Version="1">
<custom:MyControlV1 runat="server" />
</custom:Content>
<custom:Content Version="2">
<custom:MyControlV2 runat="server" CustomProperty="Foo" />
</custom:Content>
</ContentVersions>
</custom:VersionedContent>
不幸的是,这个解决方案有几个缺点。。。
- 尽管我使用的是
Template
实例,但不允许在单独的Content
节中使用重复的ID - ViewState未得到正确处理
- PostBack无法正常工作
- 验证根本不起作用
在查看了MultiView
控件的反编译源代码(类似)后,我意识到我必须使代码变得更加复杂,才能使其按要求工作。仅仅使用MultiView
控件,我唯一能得到的就是可能使用重复的ID,如果我甚至能做到这一点的话。我决定最好只接受使用内置MultiView
控件。
protected override void RenderContents(HtmlTextWriter output)
{
output.Write("<div><div class='"UserSectionHead'">");
Label l = new Label() { Text = Label };
TextBox t = new TextBox() { Text = Text };
l.AssociatedControlID = t.ID;
l.RenderControl(output);
output.Write("</div><div class='"UserSectionBody'"><div class='"UserControlGroup'"><nobr>");
t.RenderControl(output);
output.Write("</nobr></div></div><div style='"width:100%'" class='"UserDottedLine'"></div>");
}