Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create ASP.NET user control with inner properties used with markup similar to UpdatePanel?

I am creating a user control that I wish to have inner properties as well as normal attributes. Although the actual function of the control has nothing to do with, say, UpdatePanel, I am trying to create something with similar ASPX markup. The developer designing the page should be able to use my control like:

<ns:MyControl ID="someID" runat="server" SomeOtherAttribute="true">
  <ContentTemplate>
    <asp:Label ID="someLabel" runat="server" Text="Normal page markup and controls should go here" /><br />
    <p>This should be OK too.</p>
  </ContentTemplate>
  <ControlEvent ControlName="idOfOtherControl" Event="Click" />
  <ControlEvent ControlName="idOfSomeOtherControl" Event="MouseOver" />
</ns:MyControl>

Also acceptable would be wrapping the ControlEvent tags in some other tag, more like what happens when using UpdatePanel:

<ns:MyControl ID="someID" runat="server" SomeOtherAttribute="true">
  <ContentTemplate>
    <asp:Label ID="someLabel" runat="server" Text="Normal page markup and controls should go here" /><br />
    <p>This should be OK too.</p>
  </ContentTemplate>
  <ControlEvents>
    <ns:ControlEvent ControlName="idOfOtherControl" Event="Click" />
    <ns:ControlEvent ControlName="idOfSomeOtherControl" Event="MouseOver" />
  </ControlEvents>
</ns:MyControl>

But, the ControlEvents tag should only allow this one particular tag, not normal server markup or anything.

I understand how to get the content inside ContentTemplate to render (in my Page_Init, I use the ITemplate.InstantiateIn(placeholderControl) method). That part works fine. Where I'm having trouble is getting the ControlEvent part to work. I can create the inner property in the code-behind like:

[TemplateInstance(TemplateInstance.Multiple)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[TemplateContainer(typeof(ControlEvent))]
public List<ControlEvent> ControlEvents { get; set; }

In so doing, ASP.NET does seem to understand that I want a ControlEvents tag. But, I can't get properties, either inner properties or attributes, to be recognized. Instead, if I try the first type of markup, I get a runtime Parser Error: "Property 'ControlEvents' does not have a property named 'ControlName'." If I try the second type of markup, I get a NullReferenceException as a parser error at runtime, with the error being generated at the <ns:ControlEvent> line.

I've checked online, but I haven't found a good example or explanation of how to accomplish what I am trying to do with ControlEvent or ControlEvents. What am I missing? Could someone point me to a good example here?

Thanks!

Edit:

It turned out that some of the problem was due to how I had the control registered to the page using it. If I was using:

<%@ Register Src="~/Controls/MyControl.ascx" TagPrefix="ns" TagName="MyControl" %>

It appeared that the control itself was recognized in the markup, but ns:ControlEvent was not. If I changed the Register to:

<%@ Register Namespace="Namespace.Controls" TagPrefix="ns" Assembly="MyAssembly" %>

it worked as far as understanding the control itself (provided that runat="server" was included in the ns:ControlEvent tags). However, the child controls of MyControl were not being instantiated or rendered anymore, and events of the control such as Page_Init and Page_Load were not firing.

After much messing around, I figured out that I seem to need both Register directives. This seems like a messy "solution," and it doesn't really even solve everything, as (for not) at least some of the markup in my control is not rendering, though the template control (ContentTemplate) seems to be working again.

What kinds of things could I be missing here that would make this control less messy to use and that would make this control actually render everything correctly?

like image 593
Andrew Avatar asked Nov 05 '10 15:11

Andrew


1 Answers

The properties must be implmented as a Collection and not a List; other than that, the most important thing to remember here is that the parent control must have [ParseChildren(true)] attribute set for this to work, otherwise the collection will be null.

The following (server-control) sample works as expected:

   public class ControlEvent
    {
        public string ControlName { get; set; }
        public string Event { get; set; }
    }

    [ParseChildren(true)]
    public class SampleControl : System.Web.UI.Control, INamingContainer
    {
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(SampleControl))]
        public ITemplate ContentTemplate { get; set; }

        [MergableProperty(false), PersistenceMode(PersistenceMode.InnerProperty), DefaultValue((string)null)]
        public Collection<ControlEvent> ControlEvents { get; set; }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            if (ContentTemplate != null)
                ContentTemplate.InstantiateIn(this);

            if (null != ControlEvents)
                foreach (ControlEvent e in ControlEvents)
                    Controls.Add(new Label() { Text = string.Format("ControlName:{0}, Event:{1}", e.ControlName, e.Event) });
        }
    }

HTH.

like image 149
Nariman Avatar answered Sep 28 '22 02:09

Nariman