A common Community Server customization involves adding fields to the registration form (CreateUserForm control) and performing custom actions based on these fields.  Chameleon provides an easy way to extend the CreateUserForm control by using Sub-Forms.

Sub-Forms are controls that can be registered against a Form control (such as the CreateUserForm control) and participate in the action of the form.  In this guide, a sample Sub-Form control will be created to allow registering users to select their brand of car.

WrappedSubFormBase

All Sub-Form controls must inherit from CommunityServer.Controls.WrappedSubFormBase.  This base control defines the mechanism by which Form controls can interact with Sub-Forms, enabling Sub-Forms to customize and extend the behavior of Form controls.  When implementing a new Sub-Form control, the following class members of WrappedSubFormBase are notable:

  • public abstract bool IsEnabled()

    The IsEnabled method must be implemented to return true if the sub-form is enabled. 

    If, for example, the sub-form depends on the accessing user having a particular permission, this method could implement that permission check.
  • protected abstract void AttachChildControls()

    The AttachChildControls method is called when the sub-form control should locate any form field controls that will be automated by the sub-form. 

    For example, if the sub-form control will interact with a DropDownList control, the sub-form control can "attach" to the DropDownList control by locating it by its ID, using code such as:

    dropDownList = CSControlUtility.Instance().FindControl(this, "ID_OF_DROPDOWNLIST") as DropDownList;

    Note the use of the CSControlUtility class's FindControl method.  This method should be used to locate child controls by their ID as it is a more powerful version of Control.FindControl.
  • public virtual void DataBind()

    As with any .net web control, any data binding required by the sub-form control should be implemented within the DataBind method.
  • public virtual void ApplyChangesBeforeCommit(object activeObject)

    The ApplyChangesBeforeCommit method is called by the Form control with which the sub-form control is registered when the Form is committing the data/object that it manages.  The ApplyChangesBeforeCommit method is called before the object is committed by the host Form control, allowing the sub-form control to modify the object before it is committed.

    This method should be used to implement any custom modifications to the object being edited by the host form, for example, updating fields on the User object being created by the CreateUserForm control.
  • public virtual void ApplyChangesAfterCommit(object activeObject)

    The ApplyChangesAfterCommit method is similar to the ApplyChangesBeforeCommit method but is called after the object is committed by the host Form control.  This method allows the sub-form control to perform its behavior on the newly committed object.

    This method should be used to implement any custom modifications  that require the object being edited by the host form to be committed, for example, automatically creating a post for the new user created by the CreateUserForm control.

Creating the SelectUserCarBrandSubForm Control

The SelectUserCarBrandSubForm control uses a DropDownList control to save the user's favorite brand of car as an extended attribute of a User and can be used with the CreateUserForm control to allow registering users to select their favorite brand of car and with the EditUserForm control to edit their selection. 

The full source of the SelectUserCarBrandSubForm is below:

using System;
using CommunityServer.Controls;
using CommunityServer.Components;
using System.Web.UI.WebControls;

namespace CSSamples
{
    public class SelectUserCarBrandSubForm : WrappedSubFormBase
    {
        // storage for the reference to the DropDownList containing car brands
        DropDownList CarBrandsDropDownList;

        // this property will be used to allow the theme developer to provide the ID of the DropDownList containing car brands
        public string CarBrandsDropDownListId
        {
            get { return (string)(ViewState["CarBrandsDropDownListId"] ?? ""); }
            set { ViewState["CarBrandsDropDownListId"] = value; }
        }

        public override bool IsEnabled()
        {
            // permissions can also be checked here.
            return true;
        }

        protected override void AttachChildControls()
        {
            // find the DropDownList that will contain car brands
            this.CarBrandsDropDownList = CSControlUtility.Instance().FindControl(this, this.CarBrandsDropDownListId) as DropDownList;

            // if the DropDownList couldn't be found, throw an exception
            if (this.CarBrandsDropDownList == null)
                throw new InvalidOperationException("The CarBrandsDropDownListId must identify a valid DropDownList to render a SelectUserCarBrandSubForm");

            // retrieve the current selected value
            string selectedValue = this.CarBrandsDropDownList.SelectedValue;

            // clear and (re)populate the DropDownList
            this.CarBrandsDropDownList.Items.Clear();
            this.CarBrandsDropDownList.Items.Add(new ListItem("Audi", "Audi"));
            this.CarBrandsDropDownList.Items.Add(new ListItem("BMW", "BMW"));
            this.CarBrandsDropDownList.Items.Add(new ListItem("GM", "GM"));
            this.CarBrandsDropDownList.Items.Add(new ListItem("Toyota", "Toyota"));

            // attempt to re-set the selected value
            if (this.CarBrandsDropDownList.Items.FindByValue(selectedValue) != null)
                this.CarBrandsDropDownList.SelectedValue = selectedValue;
        }

        public override void DataBind()
        {
            // call the base DataBind method -- it enabled DisplayConditions and other Chameleon features
            base.DataBind();

            // the DataSource is automatically set to the host form's DataSource
            // if it is a User object, load the user's existing brand selection
            User user = this.DataSource as User;
            if (user != null && !this.Page.IsPostBack)
                       this.CarBrandsDropDownList.SelectedValue = user.GetExtendedAttribute("CarBrand"); 
        }

        public override void ApplyChangesBeforeCommit(object activeObject)
        {
            // set the extended attribute "CarBrand" to the user's selected car brand
            User user = activeObject as User;
            if (user != null)
                user.SetExtendedAttribute("CarBrand", this.CarBrandsDropDownList.SelectedValue);

            // because these changes were made in ApplyChangesBeforeCommit, the host form will commit the changes
        }
    }
}

Note the use of the methods mentioned above.  The code is commented to identify what each control member is doing.

To use this control to extend the CreateUserForm control, the above code would need to be compiled into a class library assembly and deployed into the bin/ folder of Community Server's web.  Assuming the assembly name is "CSSamples", the following changes can be made to the web/themes/[theme name]/user/createuser.aspx page:

  1. Register the New Control Namespace

    Register the control namespace containing the SelectUserCarBrandSubForm on the page by adding the following declaration to the top of the createuser.aspx page:

    <%@ Register Assembly="CSSamples" Namespace="CSSamples" TagPrefix="CSSamples" %>
  2. Define the SelectUserCarBrandSubForm Control

    The SelectUserCarBrandSubForm should then be defined on the createuser.aspx page.  The following markup will render the form using markup similar to the CS2007 default theme.

    <CSSamples:SelectUserCarBrandSubForm id="CarBrandSubForm" CarBrandsDropDownListId="CarBrands" runat="server">
        <FormTemplate>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr>
                    <td class="CommonFormFieldName">Favorite Car Brand</td>
                    <td class="CommonFormField">
                       <asp:DropDownList runat="server" id="CarBrands" />
                    </td>
                </tr>
            </table>
        </FormTemplate>
    </CSSamples:SelectUserCarBrandSubForm>

  3. Register the SelectUserCarBrandSubForm Control with the CreateUserForm Control

    To let the SelectUserCarBrandSubForm control extend the CreateUserForm control, it must be registered.  To register sub-forms to a host-form, the host-form's SubFormIds property should include the ID of the sub-form.  The SubFormIds property is a comma-delimited list of sub-form IDs, but, if the SelectUserCarBrandSubForm control is the first to be registered, the following property should be added to the CreateUserForm control on the createuser.aspx page:

    SubFormIds="CarBrandSubForm"

    Note that "CarBrandSubForm" is the ID of the SelectUserCarBrandSubForm control defined in step 2.

Similar changes to steps 1-3 can be applied to the web/themes/[theme name]/user/edituser.aspx page to add the sub-form to the EditUserForm control implementing the "Edit Profile" page.