Knowledge is power. We love to share it.

News related to Mono products, services and latest developments in our community.

khorvat

How to: Extend MonoX and ASP.NET Profile

03/14/2011Categories: MonoX
Almost a year ago, I wrote a tutorial about MonoX Profile support, describing two possible approaches supported by MonoX when you need to develop a custom user profile:

  • MonoX profile templates
  • default ASP.NET profile infrastructure

Almost every custom Web application has its own custom profile scheme, and our default profile system needs to be changed very often. The ASP.NET Profile approach was covered in detail in our previous article. This tutorial will help you jump start a custom profile solution using MonoX profile templates. Please download and unzip the attached source code to be able to follow the instructions.

Here is a brief descritiption of all files and folders included in the attachement:

Root folder
Code folder (Contains base and utility classes)
ProjectName folder (Contains sample files)
ProfileSamples folder (Contains files for this example)
ASPNETProfileSample folder (Contains ASP.NET Profile sample covered by the previous article) MonoXProfileTemplates folder (sample files relevant to this tutorial)
MonoXProfileSample.aspx (This sample home page)
MainContentTemplate.ascx (Editing control sample #1)
  SideContentTemplate.ascx (Editing control sample #2)
WebParts folder (Contains Web part sample files)
Default.aspx (Sample project home page)
Portal.csproj (Sample VS project file)
ProjectName.sln (Sample VS solution file used to compile and run the project)

For a start, let's take a look at all available sections in the default MonoX profile part. Content of the each section is held by a single ASP.NET ITemplate based container - more details on this topic can be found here and here.  These sections are called MainContentTemplate, SideBarTemplate and FooterTemplate.


Layout of the default user profile page in MonoX

These sections can hold any ASP.NET control with accompanying markup. We would recommend to encapsulate your functionality into separate user controls to avoid markup mess caused by having too many individual basic ASP.NET controls. This is our markup code:

  <MonoX:EditProfile ID="ctlProfile" runat="server" AutoDetectUser="false"
    HiddenFieldsString="FirstName,LastName">
    <MainContentTemplate>
        <h3>Main content</h3>
        <ProjectName:MainContentTemplate id="mainContentTemplate" runat="server"> </ProjectName:MainContentTemplate>
    </MainContentTemplate>
    <SideBarTemplate>
        <h3>Side content</h3>
        <ProjectName:SideContentTemplate id="sideContentTemplate" runat="server"></ProjectName:SideContentTemplate>
    </SideBarTemplate>
    <FooterTemplate>                               
        <h3>Footer content</h3>                               
        <p>
            Lorem ipsum dolor sit amet, ...
        </p>
    </FooterTemplate>
</MonoX:EditProfile>

Note that the profile Web part automatically handle data binding of profile subcontrols placed inside its sections. Profile part will automatically search for the following properties and methods in all controls placed in the profile sections:

  • UserId property - will be automatically set to the user ID of the top level profile part.
  • ValidationGroup property - will be automatically set so all editing controls inside the profile can be set under one validation group
  • DataBind method - will be automatically called to perform data binding operation on all custom controls placed in our sections

Before we move to the code-behind implementation, note that "MonoXProfileSample.aspx" contains some "housekeeping" code that is related to the standard MonoX profile behavior (same code that is used in our MonoX demo profiles that you can see here). The only thing that is specific to our scenario  is related to transferring the initial profile state to custom subcontrols:

mainContentTemplate.IsPreviewMode = ctlProfile.WorkingMode.Equals(ProfileWorkingMode.Preview);
sideContentTemplate.IsPreviewMode = ctlProfile.WorkingMode.Equals(ProfileWorkingMode.Preview);

The "MainContentTemplate" control consists of a few data entry controls used to add user specific data that is not supported by the built-in MonoX profile. Both of our controls "MainContentTemplate" and "SideContentTemplate" contain the working mode property used to hold the control state (preview or edit). Working mode switch implementation is totally up to you - you can even remove the switch and rely on the profile mode switching technique that uses the "WorkingModeChanged" event in the MonoX profile part.

<div class="profile-status-top" style="background-image: none;">
    <div class="buttons">
        <ul id="Ul1" class="button" runat="server">
            <li class="<%= !IsPreviewMode ? String.Empty : "current" %>">
                <asp:LinkButton ID="lnkViewProfile2" runat="server" CausesValidation="false">
                    <span id="labViewProfile2" runat="server"></span>
                </asp:LinkButton></li>
            <li class="<%= !IsPreviewMode ? "current" : String.Empty %>">
                <asp:LinkButton ID="lnkEditProfile2" runat="server" CausesValidation="false">
                    <span id="labEditProfile2" runat="server"></span>
                </asp:LinkButton></li>
        </ul>
    </div>
</div>

Rest of the "MainContentTemplate" mark-up consists of the following fields: "GPS Location", "Hometown" and "Update date". Save button is placed in the markup code, but this can be implemented in any way you want (you can use the "Saved" event in the built-in MonoX profile part). In our sample it is used to transfer the data to our demo data object. Here is the sample input form:

<div class="input-form">
    <dl class="profile-details" style="margin-top: 20px;">
        <dd>
            <asp:Label ID="labGPSLocation" AssociatedControlID="GPSLocation" runat="server" Text="GPS Location" CssClass="label-bold"></asp:Label>
            <asp:Label ID="prevGPSLocation" runat="server"></asp:Label>
            <asp:TextBox runat="server" ID="GPSLocation" />
        </dd>
        <dd>
            <asp:Label ID="labHometown" AssociatedControlID="Hometown" runat="server" Text="Hometown" CssClass="label-bold"></asp:Label>
            <asp:Label ID="prevHometown" runat="server"></asp:Label>
            <asp:TextBox runat="server" ID="Hometown" />
        </dd>
        <dd>
            <asp:Label ID="labUpdateDate" AssociatedControlID="UpdateDate" runat="server" Text="Update date" CssClass="label-bold"></asp:Label>
            <asp:Label ID="prevUpdateDate" runat="server"></asp:Label>
            <asp:TextBox runat="server" ID="UpdateDate" />
        </dd>
    </dl>
</div>
<div class="profile-status-top" style="background-image: none; padding: 0px; overflow: hidden;">
        <MonoX:StyledButton ID="btnSave" runat="server" Text="Save" CausesValidation="true" />   
</div>

Code-behind part of the "MainContentTemplate" consists of a demo POCO class used to store the data.  "DataManager" is a MonoX-specific utility class used to transfer data to the front end controls and back. It removes the need for the "plumbing" code that retrieves, sets and converts the data. For more details refer to the MonoX API documentation.

/// <summary>
/// Class is serializable due to MonoX ViewState optimization that needs this flag.
/// </summary>
[Serializable]
public class DemoData
{
    public string GPSLocation { get; set; }
    public string Hometown { get; set; }
    public DateTime UpdateDate { get; set; }
}

The Serializable attribute is needed to support the MonoX built-in ViewState optimization mechanism - for more details, please refer to the Web Application Optimizer (WAO) demo site, you can also find more details on WAO here . Our default ViewState provider stores ViewState data on the disk, and all classes that are going to be used by it must be decorated with this attribute.

We will now add our custom properties such as "UserId"; "IsPreviewMode" which is used to maintain working mode state inside the control, and a "DemoDataObject" - a POCO object used to store our data.   

private Guid _userId;
/// <summary>
/// Gets or sets user id.
/// </summary>
public Guid UserId
{
    get { return _userId; }
    set { _userId = value; }
}
 
/// <summary>
/// Gets or sets a flag if module is initially in preview mode.
/// </summary>
public bool IsPreviewMode
{
    get
    {
        if (ViewState["IsPreviewMode"] != null)
            return (bool)ViewState["IsPreviewMode"];
        return true;
    }
    set
    {
        ViewState["IsPreviewMode"] = value;
    }
}
 
/// <summary>
/// Gets or sets demo data.
/// </summary>
private DemoData DemoDataObject
{
    get
    {
        if (ViewState["DemoDataObject"] != null)
            return (DemoData)ViewState["DemoDataObject"];
        return null;
    }
    set
    {
        ViewState["DemoDataObject"] = value;
    }
}

Now it is time to add data binding to our data manager control:

protected void Page_Init(object sender, EventArgs e)
{
    //Note: UserId will be auto populated by the UserProfileModule if this user control is placed in one of UserProfileModule templates
    //Note: DataBind will be automatically called by the UserProfileModule if this user control is placed in one of UserProfileModule templates
 
    dataManagerMain.DataBindings.Add(GPSLocation, null, prevGPSLocation, null, "GPSLocation");
    dataManagerMain.DataBindings.Add(Hometown, null, prevHometown, null, "Hometown");
    dataManagerMain.DataBindings.Add(UpdateDate, null, prevUpdateDate, null, "UpdateDate");            
 
    Page.LoadComplete += new EventHandler(Page_LoadComplete);
}

Let's add some demo data. In this example we are not using the real database back end, so we need to use ViewState as the storage mechanism.

protected void Page_Load(object sender, EventArgs e)
{
    ....
    if (!Page.IsPostBack)
    {
        DemoDataObject = new DemoData();
        DemoDataObject.GPSLocation = "45.562141,18.676414";
        DemoDataObject.Hometown = "My Home Town";
        DemoDataObject.UpdateDate = DateTime.Now;
    }
}

Data manager's "InitControlVisibility" method is called so it can switch the control visibility and decide which set of controls to show (data entry or preview):

void Page_LoadComplete(object sender, EventArgs e)
{
    dataManagerMain.InitControlVisibility();           
}

The data bind method is where the action actually happens:

/// <summary>
/// Module databind.
/// </summary>
/// <param name="rebind">Refetch the user data</param>
public new void DataBind(bool rebind)
{
    dataManagerMain.IsPreviewMode = IsPreviewMode;
    dataManagerMain.TransferDataToControls(DemoDataObject);
    if (rebind)
    {
         //you can reload data from the database here
    }
    else
    {               
        dataManagerMain.InitControlVisibility();
    }
}

It is now time to save the data. We will just store the data back to POCO object which is saved to ViewState.

void btnSave_Click(object sender, EventArgs e)
{
    dataManagerMain.TransferDataFromControls(DemoDataObject);
    //Here goes your custom data save code
    IsPreviewMode = true;
}

One other thing that we need to mention is the custom rewriting that needs to be placed inside the web.config rewriting section in case of your own custom profile page (when you do not use the MonoX user profile demo page). The rewriting that we need to add should look similar to the code below:

<rewrite url="^(.*)/custom-profile/(.*)/(\?(.+))?$" to="$1/ProjectName/Pages/CustomProfile/CustomProfile.aspx?UserName=$2&$4" name="CustomProfile" urlPattern="/custom-profile/{UserNameUrlEncoded}/"/>

On the other side (the code-behind) you need to use the code below to navigate to your profile page with SEO Url.

string urlPattern = "/custom-profile/{UserNameUrlEncoded}/"
UrlUtility.RewritePagePath(urlPattern, new { UserNameUrlEncoded = userName })



To see the full source code for this sample please download the attached source code and follow the instructions below.

Note:
To run this sample you need to copy it to the working MonoX installation. Please use a separate MonoX installation for such tasks, to be sure that a production copy is not compromised with any of the work you do on sample projects.

Revision 05.04.2011
We have attached an upgraded version of the Profile sample so for MonoX 4.0.2580 and above please download the "SampleSolution_4_0_2580.zip".

Revision 20.01.2012
We have attached an upgraded version of the Profile sample so for MonoX 4.7.33xx and above please download the "SampleSolution_4_7_33xx.zip".
Rated 5.00, 1 vote(s). 
inanc
By inanc
Great documentation..not for only extended profile...it is very useful also to see the MonoX picture as whole!
khorvat
Yes, we do our best to cover not only the subject in hand but some other techniques involved in developing MonoX Web parts.

Thanks
By joe
What all would be involved in adding additional fields to the profile, like for example, company, title, facebook, twitter?

Do you already have documentation on doing this?
khorvat
This article should explain everything you need to add few custom fields, if you need more help please open a new topic in the support forum.

Regards
By Mike
Hi, Thanks for this great tutorial. It has helped me understand how to modify profiles in monox.

However, one question I do have is - how would I go about storing and retrieving the new profile in the database rather than just in viewstate. is there a sample project or tutorial on the best practice for doing this?

regards

Mike
khorvat
Hi Mike, there are two more blog posts on that subject that can help you

Building a custom ASP.NET project based on MonoX
http://www.mono-software.com/blog/post/Mono/95/Building-a-custom-ASP-NET-project-based-on-MonoX/

Building a custom Web part
http://www.mono-software.com/blog/post/Mono/93/Building-a-custom-Web-part/

Basically you need to create a 1:1 table with your custom profile data in it and add that to DAL project etc. For the sake of the discussion can we please move this to our support forum where we can discuss this in detail, if we continue this conversation here we will be spamming others.

Thanks