Customizing ASP.NET Membership and Profile: What Goes Where?

I have been working with two separate customers over the past few days on the same problem.  Both have an existing web application that they are migrating to ASP.NET 2.0.  They both wrote their own authentication functionality, and are now considering how to leverage the existing store with ASP.NET 2.0 Membership.

Consider a table that looks something like this:

UserID int
UserName nvarchar(50)
PasswordHash nvarchar(50)
OfficePhone nchar(10)
CellPhone nchar(10)
Pager nchar(10)

Obviously, this looks a lot different than the schema for Membership that is created when you run aspnet_regsql.  If you want to retrofit your existing table into the Membership system, do you create a custom MembershipUser type and expose the OfficePhone, CellPhone, and Pager values as public properties, or do you leverage the Profile system instead?

You could extend the MembershipUser class and expose a few properties, but that ties your application to that specific provider.  For instance, any time you want to access the Pager value, you would need to do something like:

 CustomMembershipUser u = Membership.GetUser("bob",true) As CustomMembershipUser;
if(null != u)
{
    TextBox1.Text = u.Pager;
}

You would not only have to cast to your custom MembershipUser type, but you also need to check to see if the correct type is returned, lest someone switch to a different provider.  This is the real aversion I have to extending MembershipUser, since your application cannot easily take advantage of new providers later without rework. 

A better approach would be to split the information into two providers, Membership and Profile.  The Membership API is only concerned with authentication, where the Profile API allows you to access any other characteristic data regarding a user.  Since the attributes OfficePhone, CellPhone, and Pager are not related to Authentication in our system, it makes sense to classify them as attributes of the entity and store them via the Profile API.

The really interesting part is that you can achieve this without making changes to the backing store, only customizing the internal implementation within your custom Profile provider.  That is, you could create a custom Profile that leverages the same exact store as Membership on the backend, but this is completely transparent to the developer and the end user. 

Splitting this into the 2 APIs with a common backing store is a good approach because it allows you to swap out the backend providers at a later time, or make changes to the APIs independently of each other.  Suppose you want to move to Active Directory at some later point, leveraging Active Directory Application Mode for application-specific catalog extensions without affecting the global catalog.  Leveraging AD for the backing store would allow you to modify user profile data on a global basis so that it is available across the enterprise (the same properties show up in your Outlook contacts, Office Communicator presence information, Address Book, and your applications) without maintaining it separately in multiple locations.  If you extend MembershipUser as discussed previously, this is going to be a much more difficult migration.  Leverage Profile as suggested, and you will be able to simply swap out providers.

See Scott Guthrie's blog entry for an example of implementing a Membership and Profile system.