Getting rid of the Naming Container in asp.net 2.0
Lately I’ve been playing around with asp.net 2.0.
One of the new “features” of asp.net 2 is the introduction of Naming containers.
While using naming containers can be really useful when using databound controls and standard asp.net coding, they can be a real pain to work with when making extensive use of javascript.
This is because a control declared as
<asp:TextBox ID="TextBox1" runat="server" />
will be rendered as
<input name="ctl00$ContentPlaceHolder1$TextBox1" type="text" id="ctl00_ContentPlaceHolder1_TextBox1" />
on the client side, thus breaking any script referencing TextBox1 with, for example, getElementById(‘TextBox1’).
This behavior is built inside asp.net and is triggered not only when using databoud controls (where it would be necessary to prevent having multiple controls with the same name, for example inside a datagrid), but it is also triggered when using Master Pages, where having multiple controls with the same name is almost impossible.
So, how do we code around it?
The client-side hack
Luke Foust, in his article Using JQuery to Make Asp.Net Play Nice with Asp.Net, explains a couple of client-side methods to let your javascripts select the right element in this situation. I recommend you to read it, because it contains some nice ideas and it also shows off some of the power of JQuery.
While these methods will work for most projects, they still present a couple of issues:
- Generally quite hard to mantain, especially the first hack
- Code bloat. While bandwidth is costantly becoming less of an issue, the generated page will have elements with horribly long declarations
- It breaks existing scripts. You’ll have to re-check and re-code most of the scripts that should already work
- Extra workload on the client. Todays PCs are fast, so this is less of an issue, however it is something you should consider if you have a lot of asp.net controls in your page.
The Solution
Let’s face it: the problem is really on the server, not on the client. Asp.net should not generate those horribly long IDs in the first place, unless we want to. So the best solution is a server-side hack.
How does asp.net know whether or not it should generate a unique id for our controls?
Every WebControl in asp.net exposes a property called (I’m sure you guessed it) NamingContainer, which tells asp.net what’s the parent control for our textboxes, labels and so on. All we have to do in our code is create a new class that will inherit the control we want to "sanitize" and hide that property from asp.net. Better said in code than in words:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; namespace wtd { ////// A TextBox control without ugly IDs /// public class TextBox : System.Web.UI.WebControls.TextBox { public override Control NamingContainer { get { return null; } } } }
Then, in our pages, all we have to do is register our new set of controls, like this:
<%@ Register TagPrefix="wtd" Assembly="app_code" Namespace="wtd" %>
and replace every instance of the default asp controls with our own set of controls (a simple find&replace will do the trick), so
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
becomes:
<wtd:TextBox ID="TextBox1" runat="server"></wtd:TextBox>
And that’s it! Our textbox will now be rendered like this:
<input name="TextBox1" type="text" id="TextBox1" />
Not only this method won’t break your existing javascript, it also works nicely with your existing server-side code 🙂
Update
As pointed out by some of you in the comments, this method does not work in a traditional post-back scenario. See my new article for a possible solution to this problem.
Does this also override the naming for any controls nested under this control (ie, can I just make this change to the one top-level parent control, and all child controls will do the same, or do I have to create a new class for every control type on my page?
Joshua: When overriding the NamingContainer property, we are removing the parent-child relationship that is used by asp.net to determine the ID of an object. So, if we have these controls on a webform:
[contentplaceholder]
[UserControl]
[TextBox]
Normally, the name of the generated textbox will be “ctl00$ContentPlaceHolder1$UserControl1$TextBox1”.
If we override the NamingContainer peroperty of the UserControl, we’ll get “UserControl1$TextBox1”. This is because (from asp.net’s point of view) UserControl1 is no more a child of ContentPlaceHolder1, but it’s still the parent of TextBox1!
So, in brief, yes you have to override all the low-level controls you use (TextBoxes, HiddenFields, DropDownLists…).
I love this approach, although I’m not looking forward to duplicating all of the standard form controls just to circumvent the naming container “feature”.
My question though, in my simple tests I seem to lose access to the Text property and ViewState on postback when using my TextBox control in a ContentPlaceHolder on a MasterPages page, have you experienced this as well?
How can i set This for all control in one statement or code
Another post on a similar topic, but using a different strategy (by overriding the ClientId property):
http://www.west-wind.com/WebLog/ShowPost.aspx?id=4605
I also can no longer access the text value of the control after postback. Is there a fix or a diferent way to get this information?
I too can no longer access the text property, am i missing something??
It is supposed that Visual Studio 2010 will come with a solution to this, letting you choose how you want to identify your controls in the client-side.
I don’t wanna wait of course! It is kind of frustrating…
Great article, adding it to my bookmarks!
This article was helpful in making sure my client-side controls were named as expected so that my javascript didn’t break. However, as mentioned, it breaks postbacks since the resulting client-side id’s don’t match the server-side control hierarchy.
I eventually got all that working by deriving my own MasterPage and ContentPlaceHolder controls and by juggling the control hierarchy during the page PreInit. I ended up writing about it and providing the source code necessary (C#).
Hope this helps. Feel free to copy the code:
http://www.netquarry.com/index.php/2009/03/master-pages-ajax-and-javascript-10292/
It seems this textbox does not use HtmlEncode and so the code I was going to suggest seems to break your page.
I’ll convert the html tags manually… did you write this webpage yourself? You really gotta HtmlEncode your text input before processing it… you’re just asking for it to be hacked.
Anyway, you can inject code directly into the markup like so:
You can also call functions in the codebehind file something like this:
protected string MyText() { return "Yes!"; } What you say? < %= MyText() %>
Chris: This blog is running on WordPress, so I wouldn’t worry about hacking too much 🙂
Your solution is of course a valid alternative, however some of us prefer to keep HTML and javascript in separate files instead of throwing it all together.
HAPPY NEW YEAR! Brothers ;))
And good luck to all of you!
Just browsing your blog, how long have you had it? I love the design.(Charlxtz)
Hi buddy, your solution is great, simple but very great. Thanks, regards.
Very good information and facts! I have been searching for everything such as this for some time currently. Regards!
Impressive post, I actually count on updates by you.
Hi there, I discovered your website via Google at the same time as searching for a related subject, your website got here up, it looks good. I have bookmarked it in my google bookmarks.
Hi, I think your site might be having browser compatibility issues.
When I look at your blog site in Opera, it looks fine but when opening in Internet Explorer, it has some overlapping.
I just wanted to give you a quick heads up!
Other then that, excellent blog!