Thursday, May 19, 2005

If people only knew the power of the UserControl...

It's come to my attention that a lot of people don't understand the power of ASP.NET databinding and using server and user controls efficiently. So I've put together a few tips on making better use of these two demons.
1. User controls are controls. You can create properties in them, and as long as they are public, they are accessible to the parent control in the ASPX page. This gives you the ability to easily databind the control in the ASPX page. A simple example is when you want to display a product. The two easiest ways to handle this situation are to (1) pass the DataRowVersion or Product class directly to the user control, and have the user control bind the data, or (2) pass each property to the user control.
Example user control:
public class Product : UserControl
{
private
DataRowVersion _productData = null;
public Product() {}
public DataRowVersion ProductData
{
// notice, I do not use viewstate to persist this
// information since it is probably persisted
// in the parent control
get { return _product; }
}
...
void BindData() { // handle binding the data }
}
implementing this in a page with a repeater might look like this:
<%@ Register TagPrefix="uc" TagName="Product" Src="~/Product.ascx" %>
<asp:repeater runat="server">
<itemtemplate>
<uc:product runat="server" productdata="'<%# Container.DataItem %>' />
</itemtemplate>
</asp:Repeater>

An example for (2) is very similar and will provide little benefit in demonstration.
Now, what if you want to do some sort of skinning on a user control? Here's a simple method to give your user controls a heading:
This code will give you the ability to skin the heading of your user control and allow you to place content within the user control on the ASPX page
[ParseChildren(true, "WebPartContent")]
public class WebPart : UserControl
{
protected Label WebPartTitle;
protected PlaceHolder WebPartContentPlaceholder;
[Browsable(false)]
public PlaceHolder WebPartContent
{
get { return WebPartContentPlaceholder; }
set { WebPartContentPlaceholder = value; }
}
[Browsable(true)]
public string Title
{
get { return WebPartTitle.Text; }
set { WebPartTitle.Text = value; }
}
}

The ASCX control can look like this:
<table cellpadding="3" cellspacing="0">
<tr>
<!-- Heading -->
<td><asp:Label runat="server" id="WebPartTitle" /></td>
<td width="60" align="right"><asp:Button runat="server" id="CloseButton" /></td>
</tr>
<tr>
<td colspan="2">
<asp:Placeholder runat="server" id="WebPartContentPlaceholder" />
</td>
</tr>
</table>

And the ASPX page can look like this:
<body>
<uc:WebPart runat="server" Title="Web part title">
This is where my user control content will go...
</uc:WebPart>
</body>

Well, I hope you enjoy these. I'll try to demonstrate some other cool techniques in future blogs.

2 comments:

Anonymous said...

I don't understand how this technique would work.

In these lines we can see that property type is PlaceHolder:

public PlaceHolder WebPartContent
{
get { return WebPartContentPlaceholder; }
set { WebPartContentPlaceholder = value; }
}


As I understanding, in these lines property setter will be called:
(uc:WebPart runat="server" Title="Web part title")
This is where my user control content will go...
(/uc:WebPart)

So, you assigning any content to PlaceHolder? PlaceHolder = my user control content? I don't think so.

PS Not a good blog engine for code commenting.

PPS Sorry for my english.

Suman Chakrabarti said...

The way it works is I'm setting the control as a template for it's child xml. The PlaceHolder control becomes the inner content of my user control.

MyPage.aspx
------------
<uc:WebPart runat="server">
(Pretend there is a <asp:Placeholder runat=server id=WebPartContent> tag containing this)
The idea is that I can create a customizable user control.
<Pretend end tag </asp:Placeholder>)
</uc:WebPart>

MyUserControl.ascx
--------------------
<div>
<div class='image'><asp:PlaceHolder runat=server id=WebPartContentPlaceholder /></div>
</div>

The user control is the text-based styler for the layout, but it has no knowledge of what's inside. If you're familiar with XML schemas, it's like an <xs:any>. Normally, you would create a web control in code to handle templating the look & feel. This way allows you to customize the layout without recompiling.

The important facet that I think you're missing is the [ParseChildren...] attribute. This attribute is telling the control framework to read the inner XML of the control (that's the <uc:WebPart> control) and parse it as the content for what would normally be sub-xml that says <WebPartContent>. This content is added to the WebPartContent PlaceHolder's Controls collection prior to PreRender, thus we have such a nice look & feel.

This was a great post when I wrote it 2 years ago, but it was based on the .NET 1.x framework. There have since been some great opportunities for me to revise this post. It is still 100% valid today, but there are some other features I could employ to make it better.

Now, if you're having trouble understanding what's going on here, I'd suggest you try it for yourself. It really works! :)

BTW, I don't really use this blog anymore and this is Google's Blogger, not my own design. If you'd like, I can move this post to my MSDN blog http://blogs.msdn.com/sumanc.