Using Email to Login with ASP.NET Membership

by Jim Jul 06, 2009 9:16 AM

One requirement of my latest job is to login with email, instead of a username. I’ve read some debate about whether this is a good design idea, but here’s how I did it. This post doesn’t cover the setup and use of the membership and controls – just the changes necessary to use the email instead of the username.

First we have to realize that the username must exist – it is the identifier for the user session, among other things. So we have to “fake” the username by creating one when the account is created.

If you haven’t already done this, convert the CreateUserWizard control to a template so that it can be modified. This is an option in the design view, as described here.

The UserName field must exist on the page, or the control will throw an exception. So we’ll move it from the table and hide it with CSS, while deleting the corresponding validation control. The email field is shifted to the top.

<div style="display: none;">
    <asp:TextBox ID="UserName" runat="server"></asp:TextBox>
</div>
<table border="0" cellpadding="4">
    <tr>
        <td >
            <asp:Label ID="EmailLabel" runat="server" AssociatedControlID="Email">
                E-mail:</asp:Label></td>
        <td>
            <asp:TextBox ID="Email" runat="server"></asp:TextBox>
            <asp:RequiredFieldValidator ID="EmailRequired" runat="server" 
                ControlToValidate="Email"
                ErrorMessage="E-mail is required." 
                ToolTip="E-mail is required." 
                ValidationGroup="CreateUserWizard1">
                *</asp:RequiredFieldValidator>
        </td>
    </tr>
    <tr>
        <td></td>
        <td>You will log in with your email address.</td>
    </tr>

Now in the code, we handle the CreatingUser event, which fires before the user is created, and before the username is validated. We just make the username a Guid, to ensure uniqueness.

protected void Page_Load(object sender, EventArgs e)
{
    CreateUserWizard1.CreatingUser += 
        new LoginCancelEventHandler(CreateUserWizard1_CreatingUser);
}

void CreateUserWizard1_CreatingUser(object sender, LoginCancelEventArgs e)
{
    // We're using the email as the login, but it's still neccessary 
    // to have a username. 
    // So we'll create a "fake" one here.
    CreateUserWizard1.UserName = Guid.NewGuid().ToString();
}

No additional code is needed to enforce uniqueness of the email address. The membership system already does this, and will show an error message on the user creation step if a duplicate email is entered.

Now for the login page.

If the user’s login attempt fails, the Login control will show the username in that field. We don’t want this, so we need to hide the UserName field as we did above, and add a field to accept the email instead. The html is similar. In the code, it’s simple to retrieve the username with the email, then let the control finish the login process:

protected void Page_Load(object sender, EventArgs e)
{
    Login1.LoggingIn += new LoginCancelEventHandler(Login1_LoggingIn);
}

void Login1_LoggingIn(object sender, LoginCancelEventArgs e)
{
    string email = ((TextBox)Login1.Controls[0].FindControl("Email")).Text;

    string username = Membership.GetUserNameByEmail(email);

    if (!string.IsNullOrEmpty(username))
    {
        Login1.UserName = username;
    }
}

If the email address is not found, then the user winds up getting the default message of “Your login attempt was not successful. Please try again.” There’s no mention of UserName or Email, so it doesn’t need updating.

We must follow a similar process with the PasswordRecovery control, which will also populate the username if the security question is answered incorrectly.

Tags: ,

Code

Zombie Exceptions

by Jim Jul 01, 2009 2:12 PM

I’ve occasionally seen this helpful exception message:

An unspecified error occurred on the render thread.

…emanating from the curiously-named framework method NotifyPartitionIsZombie. After much scratching of the head and fruitless searching of the web, I finally found a reproducible case and computer (this appears to happen only with particular video cards.)

image

That allowed me to track down this little bug in my code. I wonder if infinitely large drawing objects are going to be a problem?

Fixing this bug prevented the exception.

I can’t prove this, but my theory is that different video card drivers have different ways of handling infinitely large objects, when told to draw them. Or vanishingly small ones – I’ve seen this in that situation as well. Some drivers appear to make a stab at drawing these things, or simply ignore them, while others throw an exception.

So if you’re getting the NotifyPartitionIsZombie exception, you might try checking that you aren’t drawing something with impossible dimensions.