I have used CKS FBA (http://www.codeplex.com/cks) lots of times in order to provide authenticated access to public portals over the internet and, one of the most common extensibiliy scenario was adding custom fields to the user profile. The solution was different for each project and I realized I really needed a generic one to be re-used as many times as I wanted to. Some time ago, when designing the architecture of CSP (http://www.codeplex.com/csp) we thought on that generic solution to be used to integrate MOSS and CKS. The middleware component (MembershipInterface) we developed using a WCF contract was the key of this architecture.
Today I've had a new thought: it would be fantastic to have a simpler generic solution, without the need of developing any line of code. So, first of all, I have started looking for existing extensions developed on that direction and I have found this thread: http://www.codeplex.com/CKS/Thread/View.aspx?ThreadId=35189 where Anthony Summer posted an extension of CKS to allow adding custom fields. It was a great start and, after some modifications, I got it to work. Here you are the key points of the solution:
If you read the original thread you will understand perfectly what Anthony was suggesting. Summarizing, all the fields added to the User Information List marked as required would automatically appear in the user registration form and would automatically be saved. The solution was suitable for what I was looking for. Some of my project's requirements where not fullfilled, though:
- I didn't want the users to be created automatically.
- I wanted the custom information visible in the membership requests list.
So I started to think how to extend Anthony's class.
First, you need to look for the User Information List into your site collection. For those who doesn't know how to reach this list (it is hidden), you can see it when you access the All People list. Manage this list and add as many fields as you want, ensuring you mark them as required. As you will see in the code, there are some references to this list:
- web.Lists["User Information List"];
- site2.RootWeb.Lists[SPUtility.GetLocalizedString("$Resources:userinfo_schema_listtitle", "core", web.Language)];
Anthony was worried, as you will see in his comments, about hardcoding the name of the list, and so was I. Living in a country such as Spain, with several different languages means we are also very concerned about globalization issues so I added another way of accessing this list.
- web.SiteUserInfoList; // Simple, isn't it?
After customizing this list, go to the FBA membership request list, to be found on the site settings area, and add the same fields you added in the previous list.
So, what do we need in order to make it work?
First, open the MembershipRequest class and add a NameValueCollection member which will host the custom properties
protected NameValueCollection _CustomProperties; public NameValueCollection CustomProperties { get
{ if (_CustomProperties == null)
_CustomProperties = new NameValueCollection(); return _CustomProperties; }
set
{ _CustomProperties = value; }
}
Then, still in this class, locate the ApproveMembership function, and insert this piece of code to add the custom properties to the created user once it becomes accepted.
if (createStatus == MembershipCreateStatus.Success) { //... if (!String.IsNullOrEmpty(request.DefaultGroup)) { string name = string.Format("{0}:{1}", System.Web.Security.Membership.Provider.Name.ToLower(), request.UserName.ToLower());
web.Groups[request.DefaultGroup].AddUser(name, request.UserEmail, request.FirstName + " " + request.LastName, "Self Registration");
if (request.CustomProperties.Count > 0) { // Instantiates the User Information List SPList userInformationList = web.SiteUserInfoList;
// Get the current user SPUser user = web.EnsureUser(name);
// The actual User Information is within this SPListItem SPListItem userItem = userInformationList.Items.GetItemById(user.ID);
// Whe custom fields are added foreach (string customField in request.CustomProperties)
{ userItem[customField] = request.CustomProperties[customField];
}
userItem.Update();
}
//...
Last thing to do on this file. Locate the CopyToReviewList function and add these lines:
foreach (string customField in request.CustomProperties.AllKeys)
{ if (reviewItem.Fields.ContainsField(customField)) { // TODO: Only working for texts reviewItem[customField] = request.CustomProperties[customField];
}
}
Now, open the MembershipRequestControl class and locate the OnCreatingUser function to insert this portion of code:
Table cuwTable = GetCreateUserTable();
foreach (SPField customSignupField in customSignupFields)
{ // grab matching customfield Control customFieldControl = cuwTable.FindControl(customSignupField.StaticName);
switch (customSignupField.Type) { case SPFieldType.Text: TextBox textBox = (TextBox)customFieldControl;
request.CustomProperties.Add(customSignupField.StaticName, textBox.Text);
break; case SPFieldType.Note: TextBox noteBox = (TextBox)customFieldControl;
request.CustomProperties.Add(customSignupField.StaticName, noteBox.Text);
break; case SPFieldType.MultiChoice: CheckBoxList checkBoxList = (CheckBoxList)customFieldControl;
string value = string.Empty;
foreach (ListItem item in checkBoxList.Items)
{ if (item.Selected) { value = value + item.Value + ";";
}
}
request.CustomProperties.Add(customSignupField.StaticName, value); break; case SPFieldType.Choice: DropDownList dropDown = (DropDownList)customFieldControl;
request.CustomProperties.Add(customSignupField.StaticName, dropDown.SelectedValue.ToString());
break; case SPFieldType.DateTime: DateTimeControl dateTime = (DateTimeControl)customFieldControl;
request.CustomProperties.Add(customSignupField.StaticName, dateTime.SelectedDate.ToLongDateString());
break; }
}
MembershipRequest.CopyToReviewList(request);
You should modify also the OnCreatedUser function, but in my case, I need to have the approval process in place, so I haven't done it. Instead of it, I have changed the GetMembershipRequest method into the MembershipReviewHandler class, adding the following piece of code just before the return clause:
SPList userlist = web.SiteUserInfoList;
foreach (SPField userField in userlist.Fields)
{ if (item.Fields.ContainsField(userField.Title)) { if (userField.Required) { switch (userField.Type) { case SPFieldType.Text: request.CustomProperties.Add(userField.Title, item[userField.Title].ToString());
break; case SPFieldType.Note: request.CustomProperties.Add(userField.Title, item[userField.Title].ToString());
break; case SPFieldType.MultiChoice: // TODO: not developed yet request.CustomProperties.Add(userField.Title, item[userField.Title].ToString());
break; case SPFieldType.Choice: request.CustomProperties.Add(userField.Title, item[userField.Title].ToString());
break; case SPFieldType.DateTime: request.CustomProperties.Add(userField.Title, item[userField.Title].ToString());
break; }
}
}
}