September 2007 - Posts

This question comes up from time to time, to time. If you understand how redirects work, then you also know it is "not possible" to redirect into a new window, because a redirect on the server causes a special HTTP response to be sent to the users browser, the client. The browsers native implementation interprets the special response code and sends the user off to the destination. There's no built-in mechanism or standard for specifying a new window.

The only way to open a new window is for it to be initiated on the client side, whether it be through script or clicking on a link.

So the solution always proposed to this problem is to instead write out some script that opens the window, rather than using Response.Redirect:

<script type="text/javascript">
    window.open("foo.aspx");
</script>

Ok... so first you lecture me about how it is "not possible", and then you give me the code that makes it possible. Why can't I just redirect to a new window -- I don't care how HTTP works or client this or server that. There's obviously a solution, so why do I have to worry about it?

(The make-believe developers in my head are always quite temperamental)

It's easy enough to write a little helper that abstracts the details away from us... while we're at it, we might as well add 'target' and 'windowFeatures' parameters. If we're going to open the new window with script, why not let you use all of the window.open parameters? For example, with 'windowFeatures' you can specify whether the new window should have a menu bar, and what its width and height are.

public static class ResponseHelper {
    public static void Redirect(string url, string target, string windowFeatures) {
        HttpContext context = HttpContext.Current;
 
        if ((String.IsNullOrEmpty(target) ||
            target.Equals("_self", StringComparison.OrdinalIgnoreCase)) &&
            String.IsNullOrEmpty(windowFeatures)) {
 
            context.Response.Redirect(url);
        }
        else {
            Page page = (Page)context.Handler;
            if (page == null) {
                throw new InvalidOperationException(
                    "Cannot redirect to new window outside Page context.");
            }
            url = page.ResolveClientUrl(url);
 
            string script;
            if (!String.IsNullOrEmpty(windowFeatures)) {
                script = @"window.open(""{0}"", ""{1}"", ""{2}"");";
            }
            else {
                script = @"window.open(""{0}"", ""{1}"");";
            }
 
            script = String.Format(script, url, target, windowFeatures);
            ScriptManager.RegisterStartupScript(page,
                typeof(Page),
                "Redirect",
                script,
                true);
        }
    }
}

Now you just call ResponseHelper.Redirect, and it figures out how to honor your wishes. If you don't specify a target or you specify the target to be "_self", then you must mean to redirect within the current window, so a regular Response.Redirect occurs. If you specify a different target, like "_blank", or if you specify window features, then you want to redirect to a new window, and we write out the appropriate script.

One nice side effect of this "do you really need a new window?" detection is that it's dynamic. Say the destination you redirect to is configurable by some administrator. Now they can decide whether it opens in a new window or not. If they don't want it to they can specify blank or _self as the target.

Disclaimers:

Note: If you use it outside the context of a Page request, you can't redirect to a new window. The reason is the need to call the ResolveClientUrl method on Page, which I can't do if there is no Page. I could have just built my own version of that method, but it's more involved than you might think to do it right. So if you need to use this from an HttpHandler other than a Page, you are on your own.

Note: Beware of popup blockers.

Note: Obviously when you are redirecting to a new window, the current window will still be hanging around. Normally redirects abort the current request -- no further processing occurs. But for these redirects, processing continues, since we still have to serve the response for the current window (which also happens to contain the script to open the new window, so it is important that it completes).

Extension Methods

Recently, Eilon and Bertrand blogged about a novel use of some C# 3.0 features. Eilon posed the question, "Have you come up with a novel way to use a new language feature that you'd like to share?". Well here you go.

Extension Methods are a new feature in C# 3.0 (you'll need it for the rest of the article). They allow you to add methods to existing types, imported via a 'using' statement. I've seen a lot of debate over their use -- whether they are bad or good. Well -- I don't know, I don't really want to be involved in that debate. But I do know that in some scenarios they seem to fit perfectly. Like all language tools, you should use it sparingly and only when appropriate. I believe even the dreaded GOTO statement, which yes, exists in C#, has its place (I wasn't a believer originally, but some old coworkers of mine convinced me (Bob!)).

In this case, an extension method seems to work well. In general, whenever you find yourself writing a static Helper class whose only purpose in life is to help use the APIs of another type, it's probably a great candidate for extension methods. Especially if the first parameter to all those methods is the type you're trying to help with -- or if the methods always grabs the instance through some static API (like HttpContext.Current) or instantiates a new one.

By rewriting our ResponseHelper to use extension methods...

public static class ResponseHelper {
    public static void Redirect(this HttpResponse response,
        string url,
        string target,
        string windowFeatures) {
 
        if ((String.IsNullOrEmpty(target) ||
            target.Equals("_self", StringComparison.OrdinalIgnoreCase)) &&
            String.IsNullOrEmpty(windowFeatures)) {
 
            response.Redirect(url);
        }
        else {
            Page page = (Page)HttpContext.Current.Handler;
            if (page == null) {
                throw new InvalidOperationException(
                    "Cannot redirect to new window outside Page context.");
            }
            url = page.ResolveClientUrl(url);
 
            string script;
            if (!String.IsNullOrEmpty(windowFeatures)) {
                script = @"window.open(""{0}"", ""{1}"", ""{2}"");";
            }
            else {
                script = @"window.open(""{0}"", ""{1}"");";
            }
 
            script = String.Format(script, url, target, windowFeatures);
            ScriptManager.RegisterStartupScript(page,
                typeof(Page),
                "Redirect",
                script,
                true);
        }
    }
}

Note the 'this' keyword in the first parameter. Now whenever we include the namespace this class is defined within, we get a nice override on the actual Response object.

ResponseRedirect

Simply including a 'using' to a namespace is what gets extensions methods to show up. So it's probably a good idea to keep extension methods isolated to their own namespaces, lest someone get more than they bargained for when they use your namespace.

Also worth noting is that this is still a static API, so you can use it the traditional way, too. You just have to pass in the Response object as the first parameter.

And to see it in action...

Response.Redirect("popup.aspx", "_blank", "menubar=0,width=100,height=100");

Redirected into a new Window...

Two posts back I discussed a technique you can use to render controls in an order other than how they are physically arranged in the control tree.

A reader, Winston Fassett, posted a comment asking for some advice on how to get a control to render into the header of a GridView. Specifically, he wanted a DropDownList to appear in the header of each column, where the value you select from the list filters the data to only that value. If you've used SharePoint, same idea.

Putting DropDownLists in the HeaderTemplate of a TemplateField is pretty straight forward and would work just fine. But Winston also wanted to have ViewState disabled on the GridView altogether, and that means binding it every request. But how do you bind it if you don't know what the filter is? And how do you know what the filter is if you haven't databound the gridview yet? The header template isn't instantiated until you databind the GridView, so you've got a chicken-and-the-egg problem.

There's a simple solution involving storing the current filter in the page's ViewState. But in the comments Winston had a rough idea for a control that would just allow him to move the rendering of a DropDownList to inside the GridView, but without having to physically put it into the GridView. He called his idea a RenderPipe or RenderCache control.

I got me thinking about just how easy it would be to implement and the interesting things you could do with it. So here you are... thanks to Winston, the Renderer control.

Terrible name, I know. Kind of hard to say. But it's descriptive at least! It's called the Renderer control because that's what it does -- you put it somewhere, point it at another Renderer control, and it renders the other control in its place.

With it, getting that DropDownList that is outside the GridView to inside the header template is easy.

<i88:Renderer id="RenderSource" runat="Server">
    <asp:DropDownList ID="ddl" runat="server">
        <asp:ListItem Text="Item 1" />
        <asp:ListItem Text="Item 2" />
        <asp:ListItem Text="Item 3" />
    </asp:DropDownList>
</i88:Renderer>
 
<asp:GridView ID="gv1" runat="server">
    <Columns>
        <asp:TemplateField>
            <HeaderTemplate>
                <i88:Renderer SourceID="RenderSource" runat="server" />
            </HeaderTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

This is going to be a terribly worded paragraph. There are two Renderers here. One wraps the content we want, the DropDownList, the other points at the first. The one doing the wrapping simply no-ops its Render method. The target Renderer, when it comes time to Render itself, finds the other Renderer and renders it instead of itself. Even if you put the Source Renderer after the target Renderer in the markup, it will work just fine, because they can communicate.

RendererGridView
The DropDownList appears inside the GridView instead of where it actually is.

How many Renderers could a Renderer Render if a Renderer could Render Renderers?

How about, render a control a dynamic number of times?

<i88:Renderer id="RenderSource" runat="Server">
    Infinities Loop
</i88:Renderer>
 
<asp:Repeater ID="rpt1" runat="server">
    <ItemTemplate>
        <i88:Renderer SourceID="RenderSource" runat="server" />
    </ItemTemplate>
</asp:Repeater>

RendererInfinitiesLoop 

This is a useless example because, well, you could just put the control actually inside the repeater for the same effect. But with this, there's only one instance of the control which is repeated.

How about this -- what happens if you point two Renderers at each other? They swap positions! All without actually moving them within the control tree.

<table width="300px" border="1" cellpadding="1" cellspacing="1">
    <tr>
        <td align="center">
            <i88:Renderer ID="r1" runat="server" SourceID="r2">
                <h2>A</h2>
            </i88:Renderer>
        </td>
        <td align="center">
                <h2>B</h2>
        </td>
        <td align="center">
            <i88:Renderer ID="r2" runat="server" SourceID="r1">
                <h2>C</h2>
            </i88:Renderer>
        </td>
    </tr>
</table>
<asp:Button runat="server" OnClick="Reverse" Text="Swap!" />
private void Reverse(object sender, EventArgs args) {
    string r1Source = r1.SourceID;
    r1.SourceID = r2.SourceID;
    r2.SourceID = r1Source;
}

RendererSwap

Terrible freehanding aside, this shows how you can not only dynamically determine the order controls are rendered, but you can actually change the structure of them, too! All without calling Controls.Remove.

Keep in mind that all this control does is call Render on the source. If you had two Renderers pointed at the same source, that control would be rendered twice on the same page. That might result in multiple elements with the same ID, among other problems.

UPDATE: Just realized another great general use this control has. Use it to put a control with ViewState enabled inside a control with ViewState disabled. Normally disabling ViewState on a control disables it for all its children as well, and there's no way to re-enable it for a control within. Now you can still have it appear to be within that control even though it isn't.

Oh yeah... here's the control.

 

public class Renderer : Control {
    private bool _renderingSource = false;
 
    public virtual string SourceID {
        get {
            return ((string)ViewState["SourceID"]) ?? String.Empty;
        }
        set {
            ViewState["SourceID"] = value;
        }
    }
 
    private Renderer FindSource() {
        Control nc = NamingContainer;
        Control c = null;
        while (nc != null && c == null) {
            c = nc.FindControl(SourceID);
            nc = nc.NamingContainer;
        }
        if (c == null) {
            throw new InvalidOperationException("Cannot find control with ID '" +
                SourceID +
                "'.");
        }
        Renderer source = c as Renderer;
        if (source == null) {
            throw new InvalidOperationException("Control with ID '" +
                SourceID +
                "'is not a Render control.");
        }
        return source;
    }
 
    protected override void Render(HtmlTextWriter writer) {
        if (_renderingSource) {
            base.Render(writer);
        }
        else if (!String.IsNullOrEmpty(SourceID)) {
            RenderSourceControl(writer);
        }
    }
 
    private void RenderControlAsSource(HtmlTextWriter writer) {
        _renderingSource = true;
        try {
            RenderControl(writer);
        }
        finally {
            _renderingSource = false;
        }
    }
 
    private void RenderSourceControl(HtmlTextWriter writer) {
        Renderer source = FindSource();
        source.RenderControlAsSource(writer);
    }
}

When you wrap content with an UpdatePanel, it pretty much takes care of everything for you. But it can't do absolutely everything...

Take for example some inline script:

<p>Some html before the script</p>
<script type="text/javascript">
    alert('hi');
</script>
<p>Some html after the script</p>

Inline meaning it appears inline with the rest of your HTML.

First of all -- I'd say it's best not to use inline script if you don't have to. If you can move that script block to the bottom or the top of the page, or to a separate javascript reference, without consequence, then why not keep the script and UI nice and separate? But there are times when inline script is either necessary or just preferred. For example -- check out how you instantiate an instance of Silverlight on your page. That's inline script. It makes sense to keep the script where it is, since that's where you want your Silverlight app to be.

Then, along came UpdatePanel...

The over simplified way of explaining how UpdatePanel does its work on the client is through the innerHTML DOM property. When a delta is retrieved from the server, it finds itself in the existing DOM, disposes of the contents, and then assigns the new content via innerHTML. Thanks to a basically consistent behavior across the major browsers, the new content appears just as if it were there to begin with.

But inline script doesn't work this way. Setting the innerHTML of a DOM element to HTML which contains a script block does not cause that script to execute. The only way to execute arbitrary script dynamically is to eval() it directly, or dynamically create a script element with document.createElement("script") and inject it into the DOM.

So if we had the above HTML+SCRIPT inside an UpdatePanel, whenever it updated, the inline script would simply be ignored.

UpdatePanel realizes that there may be script to be executed. But it only knows about such scripts if they are registered through the ScriptManager's Register script APIs. If you use that API correctly, UpdatePanel once again picks up the slack and takes care of the rest for you automatically.

ScriptManager.RegisterStartupScript(this, typeof(Page), UniqueID, "alert('hi')", true);

UpdatePanel intercepts registrations like these during asynchronous postbacks and sends the content down to the client separately from the HTML. And than the client side PageRequestManager dynamically injects a script element for you. So one solution to this inline script problem is simply not to use inline script. If you use this Register API all the time, it will work whether there is an update panel involved or not.

But I want my Inline Script back!

Alright already. So here is a novel little control that gives you the benefits of inline script without having the draw back of not working in an UpdatePanel.

public class InlineScript : Control {
    protected override void Render(HtmlTextWriter writer) {
        ScriptManager sm = ScriptManager.GetCurrent(Page);
        if (sm.IsInAsyncPostBack) {
            StringBuilder sb = new StringBuilder();
            base.Render(new HtmlTextWriter(new StringWriter(sb)));
            string script = sb.ToString();
            ScriptManager.RegisterStartupScript(this, typeof(InlineScript), UniqueID, script, false);
        }
        else {
            base.Render(writer);
        }
    }
}

If the request is normal, it just renders whatever the contents are as usual. If you happen to be in the middle of an asynchronous postback, it uses the RegisterStartupScript API.

<asp:UpdatePanel ID="up1" runat="server">
    <ContentTemplate>
        <i88:InlineScript runat="server">
            <script type="text/javascript">
                alert('hi');
            </script>
        </i88:InlineScript>
        <asp:Button ID="cmd" runat="server" Text="Update" />        
    </ContentTemplate>
</asp:UpdatePanel>

You get the beloved alert dialog when the update panel updates! Thankfully, because you still put the script element itself in the html, you still get javascript intellisense and all that jazz, too.

Simple, not terribly useful, probably not the best thing to do performance, but could be pretty handy in the right situation.

UPDATE: If you actually use this, you may want to add a check for sm == null as well as IsInAsyncPostBack, so that the control works even if there's no ScriptManager on the page.

I receive a lot of comments and emails stemming from my series of articles on understanding the nuances of dealing with dynamic controls. It's interesting that so many of these requests revolve around very similar problems.

One such theme is where you would like to shuffle the controls on the page around a little bit. In a portal application like dotnetnuke, for example, the order modules are rendered on the page depends on custom configuration.

You can run into the nuances of dynamic controls if you plan on physically moving controls from one part of the control tree to another. For one, moving a control can cause it's unique ID to change. Normally that's ok, but sometimes something has already referenced that ID, and it's going to broken. Depending on when you move the control, you might be preventing postback data from loading correctly.

Not that there are no valid scenarios where you would want to move a control from one place to another. If you must, do so as early as possible in page lifecycle.

Or best of all -- avoid doing it in the first place.

In all of the comments/emails I received about this particular issue, the developer was making an assumption that doesn't have to be true. The assumption was that controls always render in the order they are in the tree, therefore, to change their order you must change the control tree.

Well here's a really simple control that shows that this is not true -- the Ordered Control:

namespace i88.Controls {
    using System;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Collections;
 
    public enum RenderOrder {
        Normal,
        Reverse,
        Random
    }
 
    public class Ordered : Control {
 
        public RenderOrder Order {
            get {
                object o = ViewState["Order"];
                if (o == null) {
                    return RenderOrder.Normal;
                }
                return (RenderOrder)o;
            }
            set {
                ViewState["Order"] = value;
            }
        }
 
        protected override void RenderChildren(HtmlTextWriter writer) {
            switch (Order) {
                case RenderOrder.Normal:
                    base.RenderChildren(writer);
                    break;
                case RenderOrder.Reverse:
                    RenderReverse(writer);
                    break;
                case RenderOrder.Random:
                    RenderRandom(writer);
                    break;
            }
        }
 
        private void RenderRandom(HtmlTextWriter writer) {
            ArrayList controls = new ArrayList(Controls);
            Random r = new Random();
 
            while (controls.Count > 0) {
                int nextIndex = r.Next(0, controls.Count);
                Control nextControl = (Control)controls[nextIndex];
                nextControl.RenderControl(writer);
                controls.Remove(nextControl);
            }
        }
 
        private void RenderReverse(HtmlTextWriter writer) {
            for (int i = Controls.Count - 1; i >= 0; i--) {
                Controls[i].RenderControl(writer);
            }
        }
    }
}

Ok, "Ordered" is a terrible name. I'm not good at names. Maybe it should be called the Unordered control, but whatever.

The point of the control is to allow you to change how it's children are ordered. Here's an example:

<i88:Ordered runat="server" Order="Reverse">
    <asp:TextBox ID="TextBox1" runat="server" Text="TextBox 1" />
    <asp:TextBox ID="TextBox2" runat="server" Text="TextBox 2" />
    <asp:TextBox ID="TextBox3" runat="server" Text="TextBox 3" />
    <asp:TextBox ID="TextBox4" runat="server" Text="TextBox 4" />
    <asp:TextBox ID="TextBox5" runat="server" Text="TextBox 5" />
    <asp:TextBox ID="TextBox6" runat="server" Text="TextBox 6" />
</i88:Ordered>

The controls are declared 1, 2, 3, but I've put the control in 'Reverse' mode, so they actually appear on the page in the reverse order. It also supports "Random".

But one of the key features of this control that helps drive home a point is that you can change the ordering mode on a postback, dynamically. The controls will happily rearrange themselves. No postback issues. No ViewState issues. No problems at all.

I haven't taken a look at dotnetnuke for a long, long time. But a long time ago when I did look at it, I noticed that it orders modules it contains by physically adding them to the page in the right order. When you change the ordering of modules on a page, it causes a postback. During the postback, the configuration file is changed, and then a Response.Redirect causes the page to reload from scratch.

Using this technique, that redirect wouldn't be necessary. Just render them in the new order.

Put this thing inside an ASP.NET AJAX Update Panel and put it in "Random" mode. Kind of fun to play with.

Enjoy.

PS: Published using the latest Live Writer Beta 3. Sweet.

Part 1: Dynamic vs. Static
Part 2: Creating Dynamic Controls
Part 3: Adding Dynamic Controls to the Control Tree
Part 4: Because you don't know what to render at design time

UPDATE: Click here to download the Sample Code referenced in this article.

I started part 1 of this series so long ago. My original plan was to have all 4 parts done over four weeks. It's been a lot longer than that! Sorry folks!

The new plan is that four parts is not enough! I realized while writing this part that there's just too much to cover!

There are three main reasons I can think of why you would want to create controls dynamically instead of declaratively.

  1. Because the types and/or number of controls to be rendered is not known at design time. The form may be driven by a database or through configuration, or it is dynamically determined based on other data the user has provided.
  2. You know what controls may be rendered but there are a large number of possibilities and/or the controls are expensive to load, so you don't want to load them all.
  3. Because you are creating a custom server control and you don't have a choice.

In this part, we will cover #1 and #2. Part 5, hopefully the last part, will cover #3.

Example: You don't know "what" or "how many" controls should be rendered at design time

First and foremost -- ask yourself whether the problem is that you don't know "what" controls should be rendered, or if it's just "how many" controls should be rendered. If it's a question of how many, you can still solve the problem declaratively thanks to Templating.

This is a real scenario that someone emailed me about. They were attempting to solve the problem with dynamic controls. They were having various problems with their implementation and asked for my help. And of course, it turned out to be much simpler with templating.

The scenario is as follows:

You want users to be able to upload files to your site. They do this from a file manager page, where they can see a listing of the files that already exist. Next to each file is a button to delete the file. At the bottom of the list is a file upload control they can use to upload a new file. Any time a file is deleted or uploaded, the listing is updated.

To display the files, we will add rows dynamically to a table that is declared on the form. When a file is deleted we'll give some feedback via a label.

<asp:Label ID="lblStatus" runat="server" EnableViewState="false" />
<table id="tblFiles" runat="server" cellpadding="4" cellspacing="4" border="1"></table>

Here is the implementation I was given. In red, because it has problems!

public partial class _Default : Page {
    protected override void OnLoad(EventArgs e) {
        string[] files = Directory.GetFiles(Server.MapPath("~/uploaded"));
 
        foreach (string path in files) {
            // strips directory from path
            string fileName = Path.GetFileName(path);
 
            HtmlTableRow row = new HtmlTableRow();
            // file name cell
            HtmlTableCell cell = new HtmlTableCell();
            cell.InnerText = fileName;
            row.Cells.Add(cell);
 
            // delete cell
            LinkButton cmdDelete = new LinkButton();
            cmdDelete.Text = "delete";
            cmdDelete.CommandArgument = fileName;
            cmdDelete.Command += new CommandEventHandler(cmdDelete_Command);
            cell = new HtmlTableCell();
            cell.Controls.Add(cmdDelete);
            row.Cells.Add(cell);
 
            // add row to table
            this.tblFiles.Rows.Add(row);
        }
        base.OnLoad(e);
    }
 
    private void cmdDelete_Command(object sender, CommandEventArgs e) {
        // command argument contains the file name to delete
        string fileName = Path.GetFileName((string)e.CommandArgument);
        string path = Path.Combine(Server.MapPath("~/uploaded"), fileName);
 
        File.Delete(path);
        this.lblStatus.Text = "Deleted " + fileName;
 
        // now remove the table row for the file
        LinkButton cmdDelete = (LinkButton)sender;
        // link button's parent = cell, parent.parent = row
        HtmlTableRow row = (HtmlTableRow)cmdDelete.Parent.Parent;
        this.tblFiles.Rows.Remove(row);
    }
}

It seems pretty straight forward. The first cell shows the file name, the second shows a link we can click on to delete it. When deleting a file, we remove it from the file system and then remove the row from the table. And of course, we let the user know the file was deleted successfully. Let's see how it runs...

File Manager

Cool -- let's delete "InfinitiesLoop.gif", who wants that thing anyway?

Deleted InfinitiesLoop.jpg

So far so good! Now, let's delete "TheFamily.jpg". I was blinking during that picture, and I don't appreciate the bunny ears over my head, sister. Bah...

Deleted TheFamily.jpg

WHAT?! I swear I clicked delete on TheFamily.jpg, but it deleted my TODO list! Nooooo.....! Oh well I guess I can just go home now. Dare I try to delete something else? Who knows what it's going to do next...

So, what went wrong?

When the page first renders, there are 4 rows with 4 link buttons. We never specified an ID for those link buttons when we created them. That means they get automatically generated IDs. The rows and cells do, too, but only the link button actually requires an ID to be rendered, because it must cause a postback via javascript (since links don't naturally do postbacks). If you view the html source, you will see the link button's href looks like "javascript:__doPostBack('ctl01', '')". In this case, ctl01 is the LinkButton ID.

So let's say the link buttons have IDs ctl01, ctl02, ctl03, and ctl04 (the actual IDs are different because of the controls between them, but you get the idea). We click on ctl02 to delete "InfinitiesLoop.jpg". When the postback is processed, first we recreate the table with all 4 rows again. The link buttons will still have the same IDs they did last time. ctl02 raises its command event, we delete the file, and then remove the row from the table. But the link buttons have already determined what their IDs are going to be -- the rendered table will now have link buttons with these IDs: ctl01, ctl03, ctl04. Hmmm. ctl03 is the link button the represents TheFamily.jpg. It will now appear 2nd in the table.

Now we click to delete TheFamily.jpg (ctl03). First the table gets recreated -- only, this time there is no InfinitiesLoop.jpg in the Upload directory. So we only create 3 link buttons this time -- ctl01, ctl02, ctl03. At this stage, ctl03 is the link button that corresponds to TODOList.txt. See the problem yet?

ASP.NET knows who raised the postback event via its ID. But we've pulled the rug out from under it -- the controls on this request have different IDs than they did on the previous request. There happens to be a control with the ID that was posted, but it isn't the one we intended it to be. ASP.NET finds ctrl03, and we delete TODOList.txt. Oops.

This problem can manifest itself in many different ways. If we had textboxes in this table, you might find that they lose their values after certain postbacks, or that their values seem to shift in position.

What to do about it

It's important that the structure of the control tree be the same after a postback as it was before the postback. We actually were not really violating that rule in this case -- we rendered out 3 rows when InfinitiesLoop.jpg was deleted, and we created 3 rows after the postback. But we cheated -- we created 4 rows and removed one, and then on the next request we just created 3 right off the bat. One way we could solve this is by giving each LinkButton a specific ID that will never change. In this case the simplest way would be to make it's ID equal to the file name. There's no way you can confuse one link button with another then. But it's far from ideal to have to stuff the file name into the control ID. The name might be really long. And if we were accessing multiple directories, we could have duplicate file names, so now we'd have to include path information, too. There may be other controls in each row, such as checkboxes that show the file's attributes like whether it is marked as ReadOnly. We'd have to apply this trick to those controls, too. Yuck. Yuck. Yuck.

Really what you want is to be able to "reset" the ID scheme of all the controls in the table. When we remove a row, all the controls after it should 'shift up' with their IDs, so that they will render with the same ID they will have on the next postback. So, what if when we deleted a row, we completely rebuilt the table from the start? Just clear out all the rows and then rebuild it like we originally did...

protected override void OnLoad(EventArgs e) {
    BuildTable();
    base.OnLoad(e);
}
 
private void cmdDelete_Command(object sender, CommandEventArgs e) {
    // command argument contains the file name to delete
    string fileName = Path.GetFileName((string)e.CommandArgument);
    string path = Path.Combine(Server.MapPath("~/uploaded"), fileName);
 
    File.Delete(path);
    this.lblStatus.Text = "Deleted " + fileName;
 
    // removed a row, throw away the table and rebuild it
    this.tblFiles.Rows.Clear();
    BuildTable();
}
 
private void BuildTable() {
    string[] files = Directory.GetFiles(Server.MapPath("~/uploaded"));
 
    foreach (string path in files) {
        // strips directory from path
        string fileName = Path.GetFileName(path);
 
        HtmlTableRow row = new HtmlTableRow();
        // file name cell
        HtmlTableCell cell = new HtmlTableCell();
        cell.InnerText = fileName;
        row.Cells.Add(cell);
 
        // delete cell
        LinkButton cmdDelete = new LinkButton();
        cmdDelete.Text = "delete";
        cmdDelete.CommandArgument = fileName;
        cmdDelete.Command += new CommandEventHandler(cmdDelete_Command);
        cell = new HtmlTableCell();
        cell.Controls.Add(cmdDelete);
        row.Cells.Add(cell);
 
        // add row to table
        this.tblFiles.Rows.Add(row);
    }
}
 

"A" for effort (affort?), but there's still a problem. Now when we click to delete TheFamily.jpg after deleting InfinitiesLoop.jpg, this is what we get.

Delete TheFamily.jpg again

Nothing happens except the postback. If you examine the control IDs this time, you will see that instead of resetting the IDs by clearing out the table and rebuilding it, the rows simply continued the ID sequence. That's because Controls.Clear() does not reset the automatic ID counter unless the control implements INamingContainer. HtmlTable does not implement INamingContainer.


Aside: What is INamingContainer?

INamingContainer is a marker interface, meaning it has no methods to implement. A control "implements" this interface to let the framework know that it plans on giving it's child controls really specific IDs. It's important to the framework, because if a two instances of the same control are on the same page, and the control gives its child controls some specific ID, there'd end up being multiple controls with the same ID on the page, which is going to cause problems. So when a Control is a naming container, the UniqueID for all controls within it will have the parent's ID as a prefix. This scopes it to that control. So while a child control might have ID "foo", its UniqueID will be "parentID$foo" (where parentID = the ID of the parent). Now even if this control exists twice on the page, everyone will still have a UniqueID.

INamingContainer also has the property that any controls within it that do not have a specific ID will have its ID automatically determined based on a counter that is scoped to it. So if there were two naming containers, foo and bar, they might have child controls with UniqueIDs foo$ctl01 and bar$ctl01. Each naming container gets its own little counter.

Note that the ID "foo$ctl01" does not necessarily imply that ctl01 is a direct child control of foo! All it means is that foo is ctl01's naming container (control.NamingContainer). It's parent might be another control which is not a naming container.


So we can solve this problem once and for all by using a custom HtmlTable control that implements INamingContainer. I won't bother showing that... because there's an even better solution.

Remember this scenario is that you don't know "how many" controls there should be at design time. We do, however, know "what" the controls are. You can do it dynamically, if you manage the control tree correctly. But when you know what the controls will be ahead of time, Templating can step in and take care of all of this for you automatically!

<asp:Label ID="lblStatus" runat="server" EnableViewState="false" />
 
<asp:Repeater ID="rptFiles" runat="server" EnableViewState="false">
    <HeaderTemplate>
        <table cellpadding="4" cellspacing="4" border="1">
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td><%# Path.GetFileName((string)Container.DataItem) %></td>
            <td>
                <asp:LinkButton runat="server" Text="delete"
                    CommandArgument="<%# Path.GetFileName((string)Container.DataItem) %>" />
            </td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>

Using a repeater, we declare what each row will look like. This is what I mean by "what" vs "how many". We know what, so we can express "what" via the repeater ItemTemplate. The "how many" will come from databinding the repeater, which will repeat the template once for each item we bind to it.

protected override void OnLoad(EventArgs e) {
    this.rptFiles.ItemCommand += new RepeaterCommandEventHandler(rptFiles_ItemCommand);
    BindRepeater();
    base.OnLoad(e);
}
 
private void rptFiles_ItemCommand(object source, RepeaterCommandEventArgs e) {
    // command argument contains the file name to delete
    string fileName = Path.GetFileName((string)e.CommandArgument);
    string path = Path.Combine(Server.MapPath("~/uploaded"), fileName);
 
    File.Delete(path);
    this.lblStatus.Text = "Deleted " + fileName;
 
    // removed a row, rebind the repeater
    BindRepeater();
}
 
private void BindRepeater() {
    string[] files = Directory.GetFiles(Server.MapPath("~/uploaded"));
    this.rptFiles.DataSource = files;
    this.rptFiles.DataBind();
}

This is what Databound controls are good at. Let them do their job. Doing things dynamically when you don't really need to only complicates things. This design is so much better in so many ways. For one, notice we really have no UI related code in the code-behind (except for the 'status' message). ASP.NET's code-behind model is meant to separate code and UI. Also, notice that there's considerably less code! Why? Because the repeater takes care of the following things for us: (1) it implements the foreach loop. We give it something to enumerate over and call DataBind, it does the rest. (2) it implements the creation of the controls for us. Controls are still being created dynamically at runtime, but we've handed that responsibility to the repeater by describing for it what an item should look like. (3) it implements INamingContainer. Calling DataBind on a databound control throws away its contents and rebuilds it from scratch, just like we had to do. All we need to worry about is maintaining the data and letting the repeater know when the data has changed.

Example: You don't know "what" controls should be rendered at design time, or you want to avoid loading controls you don't need because of performance or because there are too many possibilities.

If you don't know what controls will be rendered in the first place, you have to use dynamic controls, right? Well, even then, it depends. Maybe you don't know what control you will need, but the possibilities are limited. In that scenario, you can avoid dynamic control complexities by simply declaring every possible control with Visible=false, then switch the one you want on by making it visible. Like the repeater example above, this takes the burden of being responsible for the control tree out of your hands. It will also work well if it's possible for the control that is loaded to change during a postback due to a change in state. Since you're loading them all anyway, it doesn't matter.

What about performance?

I've suggested this solution a number of times to readers who sent me their problems. A lot of the time, they were doing it dynamically instead of using the visibility trick because they considered it better for performance. True, loading two controls is more work than loading one, especially when you can know one isn't needed early on. But you're splitting hairs, my friend! Controls, whether they be built in controls, custom server controls, or user controls, are efficient. It doesn't take many resources to instantiate one. It doesn't take many resources to add it to the control tree.

The only time I'd be worried about the performance of a control on the page that doesn't need to exist is if the code in the control is going to do something it doesn't need to do, or if it has a lot of ViewState associated with it. Most of the time, that's not the case. If all the control does is render some html and maybe process some postback data, it isn't worth the effort. If the problem is the amount of viewstate it contains... well, then turn it off, and just enable it for the one control that you do need. If the problem is the operations the control is going to do, like query a database, then that control isn't coded correctly. Control's typically shouldn't go off and do things on their own. They should be told when to do things by the page it lives on. Controls should almost never call databind on themselves, unless they are designed for a really specific purpose where you just want the control to do its own thing. Refactor the control so it has to be told when to do that expensive operation, such as by putting the logic in the DataBind method.

Ok. Let's move on. Say you can't just load them all ahead of time. Maybe the control to load depends on a setting in your database. Maybe there are hundreds of possibilities based on input. The next question to ask is whether the control to load is dependent on state information on the page.

For example, if your database holds the path to a user control which acts as the footer to your layout, that's probably pretty static. It probably doesn't depend on any state information. So all you have to do is load the control and add it to the tree every request. No issues. Just make sure you do it as soon as you can in the page life cycle. OnInit preferably.

If the control(s) to load depend on state data, then that's another story. In this example, we have two radio buttons and a place holder. The dynamic control is loaded into the placeholder, but which control we load depends on which radio button is selected. If you wish, you can also imagine that the control we load is dependent on a setting in web.config. But for this example we'll just use two hard controls.

First we'll define two user controls, UserControl1.ascx and UserControl2.ascx.

<%@ Control Language="C#" %>
<script runat="server">
    private void ButtonClick(object sender, EventArgs args) {
        this.lbl.Text = "Clicked UserControl1";
    }
</script>
UserControl1<hr />
<asp:Label ID="lbl" runat="server" /><br />
<asp:Button ID="cmd" runat="server" Text="Click" OnClick="ButtonClick" />

UserControl2 looks the same except has "UserControl2" instead of "UserControl1". Each has a label and a button. When the button in control 1 is pressed, it updates the label in control 1. When the button in control 2 is pressed, it updates the label in control 2. Keep in mind that each control has its very own button and label. There is no label on the form:

<%@ Page Language="C#" %>
<script runat="server">
    protected override void OnLoad(EventArgs e) {
 
        if (this.opt1.Checked) {
            ph.Controls.Add(LoadControl("~/UserControl1.ascx"));
        }
        else if (this.opt2.Checked) {
            ph.Controls.Add(LoadControl("~/UserControl2.ascx"));
        }
 
        base.OnLoad(e);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Pick-a-control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:RadioButton ID="opt1" runat="server" Text="Number 1"
            AutoPostBack="true" GroupName="g" Checked="true" />
        <asp:RadioButton ID="opt2" runat="server" Text="Number 2"
            AutoPostBack="true" GroupName="g" />
        <hr />
        <asp:PlaceHolder ID="ph" runat="server" />
    </div>
    </form>
</body>
</html>

The devil is in the details. Let's load it up. UserControl1 will load by default, so lets just go ahead and make sure it's working by clicking on the button:

Clicked on UserControl1

So far so good. It says "Clicked UserControl!" like we expected. It loaded, we posted back, it reloaded, and it successfully processed the click as if it were a declared control. Beautiful. Let's swap over to UserControl2 now... but we won't click on its button yet...

Switched to UserControl2

What? That's weird. According to this, I've loaded UserControl2, but it's label has the data I put into UserControl1's label! Voodoo!

That's nothing. Now let's have some fun. Change the label control in UserControl2 to a TextBox. Don't even give it the same ID.

<%@ Control Language="C#" %>
<script runat="server">
    private void ButtonClick(object sender, EventArgs args) {
        this.txt1.Text = "Clicked UserControl2";
    }
</script>
UserControl2<hr />
<asp:TextBox ID="txt1" runat="server" /><br />
<asp:Button ID="cmd" runat="server" Text="Click" OnClick="ButtonClick" />

Now what happens when we switch from control 1 to control 2?

Voodoo!!

Ohhh yah... we're hacking now. We've successfully loaded the ViewState for the Label in UserControl1 into the TextBox in UserControl2. They both have a "Text" property, which both happen to use "Text" as the ViewState key to remember the value in. *High five*

I mean this to drive home an important point. The control tree into which viewstate is loaded must basically match the control tree which was used to save that viewstate. Coming from more procedural web frameworks (like ASP), you might tend to think of the life of a control to be over once it renders itself. But really, I like to think of it as if a control's lifecycle straddles the request/response boundary. It does indeed get re-instantiated upon every request, but because ASP.NET manages state data for us, the control created on a postback is intimately connected with its "predecessor", for lack of a better word. This more "logical" lifecycle ends after the control has loaded its ViewState and its postback data from its previous life, if any. After that, but before it begins its next life in PreRender, you can make persistent changes to the control tree with no worries.

So here is the solution to the above problem. Every scenario is different, so this isn't necessarily a real general pattern you should follow to the tee, but it shows how we do things the right way for this scenario. Hopefully you can adapt the solution to your specific needs.

<%@ Page Language="C#" %>
<script runat="server">
    protected override void OnLoad(EventArgs e) {
        if (!Page.IsPostBack) {
            // no viewstate on initial request, load the default control
            ViewState["state"] = opt1.Checked ? 1 : 2;
            LoadUserControl();
        }
        base.OnLoad(e);
    }
 
    protected override void LoadViewState(object savedState) {
        base.LoadViewState(savedState);
        // viewstate loaded, now we know which control to show.
        LoadUserControl();
    }
 
    private void CheckChanged(object sender, EventArgs args) {
        // state has changed. Remove the loaded control and load the new one.
        ViewState["state"] = opt1.Checked ? 1 : 2;
        ph.Controls.Clear();
        LoadUserControl();
    }
 
    private void LoadUserControl() {
        Control c;
        int state = (int)ViewState["state"];
        if (state == 1) {
            c = LoadControl("~/UserControl1.ascx");
        }
        else {
            c = LoadControl("~/UserControl2.ascx");
        }
        // id assigned to avoid shifting IDs when control changed on a postback
        c.ID = "foo";
        ph.Controls.Add(c);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Pick-a-control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:RadioButton ID="opt1" runat="server" Text="Number 1"
            AutoPostBack="true" GroupName="g" Checked="true"
            OnCheckedChanged="CheckChanged"  />
        <asp:RadioButton ID="opt2" runat="server" Text="Number 2"
            AutoPostBack="true" GroupName="g"
            OnCheckedChanged="CheckChanged" />
        <hr />
        <asp:PlaceHolder ID="ph" runat="server" />
    </div>
    </form>
</body>
</html>

The idea is to load the control that existed on the previous request by utilizing a ViewState field to remember which control was active. We override LoadViewState, then immediately after calling base.LoadViewState, we can look for the ViewState value to tell us which control existed previously. On the initial request, there is no ViewState and therefore no call to LoadViewState, so we detect this in OnLoad and make sure the default control is loaded at first. Then, we listen for the CheckChanged event on the radio buttons to tell us when the control to be loaded has changed (note: I actually really dislike the CheckChanged event, but that's a different discussion. It works well for this simple scenario).

At the time the CheckChanged event fires, we will have already loaded a user control -- whichever one was active previously. So we have to remove it before adding the new one. That is why we call Controls.Clear() on the placeholder. And finally, we assign the control a specific ID so there's no way we can run into the ID problem mentioned earlier, which would happen when removing the old control and adding the new one in response to the CheckChanged event. There's no way we could accidentally post data from one control into another as you switch from one control to another, because we're loading the correct control prior to the loading of post data. Post data is loaded right after LoadViewState. That is why we don't do the logic from OnLoad -- the user control would miss that phase. Actually, ASP.NET loads post data in two phases, one before OnLoad / after LoadPostData, and one right after OnLoad. The purpose of the 2nd pass after OnLoad is to load postdata for any controls that may have been dynamically added (or created through databinding) during OnLoad. That would actually work just fine for our scenario, but there are consequences to this late loading that are best avoided if possible. Normally, you can rely on post data to have been completely loaded from the OnLoad phase. But for late-comers, that isn't the case. It's best to provide consistent behavior if you can, so LoadViewState is where it's at!

Actually, this very closely emulates what data bound controls do. If you were to examine the code for the Repeater, for example, you would find that from its CreateChildControls method it examines a ViewState field. If it exists, it calls its CreateControlHiearchy method, which rebuilds its control tree based on ViewState. When it is DataBound, it clears the control collection and rebuilds it again, calling the same CreateControlHiearchy method.

THAT'S ALL FOLKS. In the next part we'll cover custom server controls and some of the things to watch out for there.

UPDATE: Click here to download the Sample Code referenced in this article.

One more thing...

Many of you apparently were so anxious to read part 4 of the series, you cleverly deduced that the url to the article must be the same as Part 3, only with a "4". You URL HAXXOR, you!!!!111... You see, I've been working on a draft of this article for a long time now (which as it turns out has been completely redone), and I unknowingly had the article saved in a state that would allow you to access it if you happened to know the address to it!

In all, by the time I realized what was happening, this article received 121 hits to it even before it was published! All you hackers got to read my embarrassingly terrible draft. I thwarted you by renaming the article temporarily. I added "abc" to the end. I was waiting for someone to guess that, too. If you did, there would have been a surprise in it for you. But no winners. Oh well....

Until next time.... part 5 will come much sooner than part 4 did, I promise.

More Posts