I have recently worked with several Partners that are using the Customer Portal to expose their Force.com application to their end-users. In order to automate the self-registration process to provision a new user, I built an Apex class that performs the logic to create the Account, Contact and corresponding User record that are required for a Customer Portal user. In more detail, to be a customer portal user, you must be first setup as a Contact record, then create a User record that is associated to that previously created Contact (by populating the User.ContactId field).
This sample includes a Visualforce page and an Apex custom controller.
A few things I want to point out:
A Customer Portal user consists of both a Contact record and a User record. The Contact record should be created first. Then create the User record and associate it to the Contact through the User.ContactId field.
Apex does not allow DML operations of a non-setup sObject and a setup sObject in a single Apex transaction. In this example, the rule prevents us from creating the Contact record in the same transaction as the User record. The Contact is the non-setup sObject type, but the User is a setup type sObject, and therefore Apex won't allow us to have such mixed DML operations in a single transaction. To solve this, the Apex code creates the Contact record synchronously, but it uses the @future annotation to create the User record asynchronously.
When a I create the User record, I need to send the auto-generated password to the new User. With DMLOptions, you can initiate this email automatically when the new User is inserted. The Apex code sets the emailHeader and then inserts the User. If we didn't set this DMLOptions on the User record before inserting it via Apex, the email notification would not be sent to the user.
When creating a new Customer Portal user, their Profile must be a Customer Portal profile. If you dig into the Apex class, I have a query, [Select p.name, p.id From Profile p where p.UserLicense.Name like '%Customer Portal%'], that is specifically querying for profiles that are Customer Portal profiles. You will get an error if you try to associate a non-portal profile to a Customer Portal User.
In my sample code, I only populate the required fields on Account, Contact, and User. Additionally, I have hard-coded some of the User attributes for demo purposes only. If you want to re-use this code in a project, I recommend not hardcoding all the User fields, such as Language, Locale, Time Zone, ect.
Lastly, since this code does create a User record, you can not delete it. You can de-activate the User after the code runs, but you can not delete User records. So please test this in a Developer Edition or Sandbox org and use caution.
Please find the entire source code, both the Visualforce page and Apex class, here.