Bummer. I've been mucking around with some more custom databinding that integrates validation into the databinding process. One of the things the control does is automatically add 'notification' icons or error text to the page. The idea is this:
- Control(s) unbind
- If there are binding errors an Icon or Message is injected into the Control Collection
Basically the code looks something like this:
public bool AddBindingError(string ErrorMessage, wwDataBindingItem BindingItem)
{
// *** Associated control found - add icon and link id
if (BindingItem.ControlInstance != null)
this.BindingErrors.Add(new BindingError(ErrorMessage, BindingItem.ControlInstance.ClientID));
else
{
// *** Just set the error message
this.BindingErrors.Add(new BindingError(ErrorMessage) );
return false;
}
BindingItem.BindingErrorMessage = ErrorMessage;
// *** Insert the error text/icon as a literal
if (this.ShowBindingErrorsOnControls && BindingItem.ControlInstance != null)
{
LiteralControl Literal = new LiteralControl(this.GetBindingErrorMessageHtml(BindingItem));
int CtlIdx = BindingItem.ControlInstance.Parent.Controls.IndexOf(BindingItem.ControlInstance);
try
{
// *** Can't add controls to the Control collection if <%= %> tags are on the page
BindingItem.ControlInstance.Parent.Controls.AddAt(CtlIdx + 1, Literal);
}
catch {;}
}
return true;
}
And it works beautifully in most situations. The literal control creates markup that loads the appropriate image and adds the error text (if configured that way).
This all works great until you happen to have some script markup on the page using. For example if the page contains something as simple as this:
<%= DateTime.Now %>
The Controls.AddAt() call will fail with:
The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).
Now I feel like a complete schmuck to never have run into this before. That's one hell of an assumption to miss about ASP.NET it seems. I have tons of pages where there's <%= %> markup on it. Especially in script code to get the appropariate ClientID:
<%= this.txtCompany.ClientID %>
Luckily there's a workaround for code like this by using DataBinding expressions instead:
<%# this.txtCompany.ClientID %>
This works fine for simple expressions. The difference here is that <%= %> expressions are embedded into the ASP.NET output as part of the generated Parse Tree class, whereas the <%# %> expressions are embedded at runtime.
Another workaround is to force any script code into the content of a server control and remove it from the Page class or the Content container that you're adding controls to.
<div runat="server">
<script type="text/javascript">
function ShowCreditCard()
{
var IsPayPal = false;
for(x=0; x<4; x++ )
{
var ctl = $("<%= this.txtCCType.ClientID %>_" + x.toString());
if (ctl == null)
break;
if (ctl.value == "PP" && ctl.checked)
{
IsPayPal = true;
break;
}
}
var loCC = $("<%= this.trCreditCard.ClientID %>");
if (loCC == null)
return;
var loCC2 = $("<%= this.trCreditCardExpiration.ClientID %>");
if (IsPayPal)
{
loCC.style.display = "none";
loCC2.style.display = "none";
}
else
{
loCC.style.display = "";
loCC2.style.display = "";
}
}
</script>
</div>
This works as well, although this is also pretty ugly. In my case this is probably the easier solution though, because most of my markup expressions are doing exactly what's happening above: Embedding ClientScript Ids into JavaScript.
I still don't see why the control collection can't be modified if there are <% %> blocks on the page. Those blocks are just turned into Response.Write() commands, or raw code blocks. I don't see how this affects the Controls collection that would require this sort of error.
In my situation here I was able to get by just switching to <%# %> or wrapping sections with a server tag. Even if that's not an option the above code captures the error and goes on. This means the warning icons don't show up, but the rest of the error handling showing the summary and error control linking etc. all still works.
Can anybody think of another more reliable way to inject markup into the page dynamically from outside of the control rendering? In a previous rev of my databinding tools I had custom controls and I simply took over post rendering which was reliable. But this is not so easily done externally… I can think of possibly hooking up the Render method and calling back into my custom control, but man does that seem ugly.