I wanted to document the solution I pursued to this for anyone else who came along.
First and foremost this is a brittle kludge; by that, I mean that any changes to registration done through the proper channels in DNN are likely to break this. Any style changes have the potential to break it. It's a brittle kludge...what are you gonna do. On the other hand, it was a very small amount of effort and the end user has no idea that it's a kludge.
In the version of DNN I was working with, the user registration stuff is stored in a user control under admin/users/user.ascx. The actual code is emitted from a nested user control, . I couldn't get hooked into this without digging into the source for DNN (which of course, is another option), but the bottom line was that all I wanted to do was put another textbox right after it and make sure that the email addresses matched.
So that's exactly what I did:
<dnn:propertyeditorcontrol id="UserEditor" runat="Server"
enableClientValidation = "true"
sortmode="SortOrderAttribute"
labelstyle-cssclass="SubHead"
helpstyle-cssclass="Help"
editcontrolstyle-cssclass="NormalTextBox"
labelwidth="200px"
editcontrolwidth="200px"
width="400px"
editmode="Edit"
errorstyle-cssclass="NormalRed"/>
<!-- new code starts here -->
<span id="spanEmailConfirm" style="display: inline-block; width: 400px;">
<table cellpadding="0" style="margin-left:3px" cellspacing="0" border="0">
<tr>
<td>
<div style="width: 400px;">
<div style="border:0px solid black; float: left; width: 200px;">
<img onclick="toggleDisplay('divHelpConfirmEmailAddress');" style="border-width: 0px;" alt="Enter a valid confirmation Email address" src="/images/help.gif" title="Enter a valid confirmation Email address" tabindex="-1" id="imgConfirmEmail"/>
<asp:Label runat="server" ID="lblConfirmEmail" class="SubHead">
Confirm Email Address:
</asp:Label>
</div>
<div style="float: right; width: 200px;">
<asp:TextBox class="NormalTextBox" onblur="validateEmail();" runat="server" id="txtConfirmEmail" /><img style="border-width: 0px;" alt="Email confirmation is required" src="/images/required.gif" title="Email confirmation is required"/>
</div>
</div>
</td>
</tr>
<tr>
<td>
<asp:RequiredFieldValidator runat="server" ID="reqEmailConfirm" CssClass="NormalRed" ControlToValidate="txtConfirmEmail" ErrorMessage="You must enter a valid confirmation email address." Display="Dynamic"></asp:RequiredFieldValidator>
<asp:CustomValidator runat="server" ID="reqEmailsMatch" CssClass="NormalRed" ControlToValidate="txtConfirmEmail" ValidateEmptyText="false" ClientValidationFunction="validateEmail" Display="Dynamic" ErrorMessage="Email confirmation value must match the email value."></asp:CustomValidator>
</td>
</tr>
<tr>
<td>
<div style="width:200px; display:none" class="Help" id="divHelpConfirmEmailAddress">
<span id="spanConfirmEmail">Confirm your email address.</span>
</div>
</td>
</tr>
</table>
</span>
<!-- new code ends here -->
This invokes the following javascript, which I embedded elsewhere in the ascx:
<script type="text/javascript">
function validateEmail(sender, args) {
var emailControlName;
emailControlName = "dnn$ctr459$ManageUsers$User$UserEditor$ctl04$Email";
var emailControl = document.getElementsByName(emailControlName)[0];
args.IsValid = (emailControl.value == args.Value);
}
function toggleDisplay(controlId) {
var control = document.getElementById(controlId);
if (control.style["display"] == "block") {
control.style["display"] = "none";
} else {
control.style["display"] = "block";
}
}
</script>
Obviously, the ugliest part here is the email control name, but every piece of research I was able to find on control id's indicates that this is deterministic, i.e., it will be the same unless something changes on the input side.
So, this is the kind of solution that makes the developer want to take a shower after writing (or buy Code Offsets), but it works, and it's quick and easy.
One final caution: this code does have the odd behavior that if NOTHING is filled out on the form, the confirmation validation will fire, and not the others. ValidationGroup isn't set correctly, right? Wrong, as far as I could tell. Ultimately, it wasn't worth tracking down, as the rest of the validation fired once other fields were filled in, so there you. If anybody knows what this issue is, please update this question.