ASP.NET: Creating a UserControl with Child Content

square-peg-round-hole-21 by Yoel Ben-AvrahamI love ASP.NET User Controls, aka “ascx” files.  These little guys are great for reusable content and dividing up the components of a website.  The little brother of the more powerful Custom Server Control, they have some limitations, but I’ve found they are often sold short.  Once falsehood is that a User Control cannot contain content between the opening and closing tags, like such:

<MyControls:ContentBlock runat="server" Title="The Control has Content!">
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
    Donec ut nisi sed elit aliquam vulputate eu et lacus</p>
    <asp:HyperLink runat="server" NavigateUrl="~/">Home</asp:HyperLink>
</MyControls:ContentBlock>

If you attempt something like this with a stock User Control you’ll get “Type 'ASP.mycontent_contentblock_ascx' does not have a public property named 'p'.”  If we just want to have a simple control that formats the content, then we only need a few tweaks and we can keep the User Control (if you have more advanced needs, then it’s time to step up to a Custom Server Control).

The first tweak is to wrap the ascx template in a single control:

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="ContentBlock.ascx.cs" Inherits="MyControl_ContentBlock" %>
<asp:PlaceHolder ID="phContent" runat="server">
    <h1>
        <%= Title %>
    </h1>
    <div class="body">
        <%= Text %>
    </div>
</asp:PlaceHolder>

I choose a PlaceHolder control because it won’t emit any HTML.  Then in code-behind we need to make a few tweaks:

[ParseChildren(false)]
public partial class MyContent_ContentBlock : System.Web.UI.UserControl
{
    public String Title { get; set; }
    protected String Text { get; set; }

    protected override void AddParsedSubObject(object obj) {
        if (obj is LiteralControl) 
            this.Text += ((LiteralControl)obj).Text;

        else if (obj is PlaceHolder && !String.IsNullOrEmpty(((PlaceHolder)obj).ID) 
            && ((PlaceHolder)obj).ID.Equals("phContent")) 
            base.AddParsedSubObject(obj);

        else {
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb)) {
                using (HtmlTextWriter w = new HtmlTextWriter(sw)) {
                    ((Control)obj).RenderControl(w);
                    this.Text += sb.ToString();
                }
            }
        }
    }
}

The class gets a new attribute, ParseChildren, set to false.  I’m stealing this attribute from the Custom Server Control which normally would use this attribute to map the child content to a property.  Setting it to false however on a User Control will stop the framework from throwing an error on the User Control.  If you stop here, the output of the control would look something like:

<h1>
    The Control has Content!
</h1>
<div class="body">
</div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
Donec ut nisi sed elit aliquam vulputate eu et lacus</p>
<a href="/Default.aspx">Home</a>

The content was appended to the User Control template.  This can be fixed by overriding the AddParsedSubObject method.  This method is called for every control in the User Control – including the PlaceHolder that acts as our template.  Any HTML content is converted into a LiteralControl.  The logic checks the control being passed in, and if it’s a LiteralControl the text is added to the control’s Text property.  If the control is the PlaceHolder it is passed to the base method to let normal processing takeover.  The remaining controls are rendered, and the text appended to the control’s Text property.  Technically I don’t need the special case for LiteralControls, since the last case will handle it, but it’s the common case and worth saving the additional CPU cycles required to use the RenderControl method.

We now have the proper output:

<h1>
    The Control has Content!
</h1>
<div class="body">
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
    Donec ut nisi sed elit aliquam vulputate eu et lacus</p>
    <a href="/Default.aspx">Home</a>
</div>

There is a gotcha with this approach (beyond advanced data binding and postback issues you may encounter) – the following code will botch the output:

<MyControls:ContentBlock runat="server">
    <%= DateTime.Now %>
</MyControls:ContentBlock>

Simply put, Code Blocks change the way controls get processed.  Wrapping Code Blocks in another server control will work around this problem:

<MyControls:ContentBlock runat="server">
    <asp:PlaceHolder runat="server"><%= DateTime.Now %></asp:PlaceHolder>
</MyControls:ContentBlock>

Now that you have this new power, promise me you’ll only use it where proper and create a Custom Server Control when needed.

Promise?

Posted By Mike On Tuesday, December 08, 2009
Filed under asp.net usercontrol | Comments (1)

Submit this story to DotNetKicks   

online slots - Friday, August 20, 2010 8:26:21 AM

ASP.NET provides several exciting security controls, but these need to be understood properly and used wisely.ASP.NET application only performs database lookups and does not update any data, you only need to grant read access to the tables.

Leave a comment



Your name:
 

Your email (not shown):
 
Will display your Gravatar image.

Your website (optional):



About Michael

Michael C. Neel, born 1976 in Houston, TX and now live in Knoxvile, TN. Software developer, currently .Net focused. Board member of ETNUG and organizes CodeStock, East Tennessee's annual developers conference. .Net speaker, a Microsoft ASP.NET MVP and ASPInsider. Co-Founder of FuncWorks, LLC and GameMarx.

Proud father of two amazing girls, Rachel and Hannah, and loving husband to Cicelie who inflates and pops his ego as necessary.

 Subscribe to ViNull.com |  Comments

Follow me on Twitter | Contact Me

Related Posts

ASP.NET: Cannot use a leading .. to exit above the top directory

This is a really quick post, mostly for myself so the next time I have this issue I can find the answer (yes, I often search my blog before google). The ... Read more

ASP.NET SEO Interview on Polymorphic Podcast

Craig Shoemaker just posted the latest episode of the Polymorphic Podcast: ASP.NET SEO - Interview with Michael Neel.  Yes, I've now appeared in a ... Read more

Google Can You Hear Me?

In June of 2006 I wrote an article titled "Google Can You Hear Me? How to design URLs that are search engine friendly" that also included a sidebar titled ... Read more

Using LINQ to generate HTML

I hate seeing code mixed with markup. Seeing a template page with <% if(show) { %> makes me want to claw my eyes out.  Seeing String htmlTitle ... Read more

The ASP.NET MVC Definition

A few days ago I posted a question to the community, looking for a definition of the ASP.NET MVC framework that didn't depend upon faults in ASP.NET WebForms ... Read more

XNA 3D Primer by Michael C. Neel

XNA 3D Primer by Michael C. Neel
Buy Now: [ Amazon ] [ Wrox ]

GameMarx

CodeStock

ASPInsiders Member

ETNUG Member