简体   繁体   English

使用嵌套的可选控件创建自定义服务器控件

[英]Create a custom server control with nested optional controls

I would like to create an custom server control, VersionedContentControl , which will allow me to specify different variations of the final markup. 我想创建一个自定义服务器控件VersionedContentControl ,它将允许我指定最终标记的不同变体。

Example usage: 用法示例:

<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>

Using the markup above, I would expect the only LinkButton control to be utilized on the page. 使用上面的标记,我希望页面上将使用唯一的LinkButton控件。

Long Story 很长的故事

I am having great difficulty trying to define this custom control. 我很难定义这个自定义控件。 I haven't even been able to find a good example on the MSDN of using nested controls like this. 我什至无法在MSDN上找到使用这样的嵌套控件的好例子。 Instead, I have had to resort to following these blog posts as examples: 相反,我不得不以这些博客文章为例:

Unfortunately, everything I have tried has failed miserably. 不幸的是,我尝试过的一切都失败了。 Here is what I have currently: 这是我目前拥有的:

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> {}
}

Even though I haven't implemented InstantiateIn , all 3 versions of my content appear on the page; 即使我还没有实现InstantiateIn ,我所有内容的所有3个版本都显示在页面上。 it shows 3 links. 它显示3个链接。

Also, I can't actually use the control unless I specify different ID property values for each nested control; 另外,除非为每个嵌套控件指定不同的ID属性值,否则我将无法使用该控件。 I can't use "HomeLink" for all of them. 我不能全部使用"HomeLink" I would like to be able to re-use the ID so that I can access the control from the code behind. 我希望能够重复使用ID以便可以从后面的代码访问控件。

I realize that, normally, it is forbidden to specify duplicate ID values for multiple controls on a page. 我意识到,通常情况下,禁止在页面上为多个控件指定重复的ID值。 However, in the MSDN documentation for System.Web.UI.MobileControls.DeviceSpecific , the examples use duplicate ID values for nested controls. 但是,在有关System.Web.UI.MobileControls.DeviceSpecificMSDN文档中 ,示例对嵌套控件使用重复的ID值。 Infact, the example is very close to what I want to do; 实际上,该示例与我要执行的操作非常接近。 it differs content based on a mobile device compatibility filter. 它基于移动设备兼容性过滤器而有所不同。

<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>

It would be nice to look at the source of those controls to see how they accomplish this but, unfortunately, it is not open-source. 最好查看这些控件的源代码,以了解它们如何完成此操作,但是不幸的是,它不是开源的。

My Question 我的问题

How can I create a custom server control which contains a list of nested controls and only renders one of the nested controls based on a property? 如何创建一个自定义服务器控件,其中包含嵌套控件列表,并且仅基于属性呈现一个嵌套控件? Ideally re-using IDs among separate nested controls. 理想情况下,在单独的嵌套控件之间重用ID。

tl;dr TL;博士

I ended up using the MultiView control. 我最终使用了MultiView控件。 It doesn't allow duplicate IDs but it satisfies all my other requirements and it allows me avoid having to maintain any custom code. 它不允许重复的ID,但可以满足我的所有其他要求,并且可以避免维护任何自定义代码。

What I tried 我尝试了什么

I was finally able to get something working with this code: 我终于能够得到的东西与此代码的工作

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 { }
}

This allows me to use the control like so: 这使我可以像这样使用控件:

<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>

Unfortunately, this solution had several drawbacks... 不幸的是,该解决方案有几个缺点。

  • Even though I was using Template instances, I wasn't allowed to use duplicate IDs in separate Content sections 即使我使用Template实例,也不允许在单独的“ Content部分中使用重复的ID
  • ViewState was not being handled properly ViewState处理不正确
    • PostBack doesn't work properly PostBack无法正常工作
    • Validation doesn't work at all 验证根本不起作用

After looking at the decompiled source code of the MultiView control (which is similar), I realized I would have had to make the code a lot more complicated to make it work as desired. 在查看了MultiView控件的反编译源代码(类似)之后,我意识到我必须使代码复杂得多才能使其按需工作。 The only thing I would gain over just using the MultiView control was possibly being able to use duplicate IDs, if I could even get that working. 我将获得只使用了唯一MultiView控制是可能能够使用重复的ID, 如果我甚至可以拿到工作。 I decided it would be best to just settle for using the build-in MultiView control. 我认为最好只是使用内置的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>");

        }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM