Knowledge is power. We love to share it.

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

khorvat

Building a custom Web part

02/25/2011Categories: MonoX
MonoX team has been very busy implementing cool new features for the next release, but never forgets to listen to what our community has to say. We have heard a lot of requests for programming tutorials, and most often people are looking for instructions on how to build advanced Web parts for their applications.
Although there is a "hello world" example that comes with the MonoX package, we decided to push more realistic examples and provide our users with several "hands-on" tutorials that will describe best MonoX development practices.

This time, by popular request, we will build a social networking Web part aptly named "Popular Groups". It will be included in the next release, but the full source code is available now (see below for a download link).

Before you start building custom web part we advise you to read the following articles and familiarize yourself with the terms and concepts that we will use in this tutorial:

  • MonoX manual - section 6 "Working with Web parts"
  • MonoX manual - section 14.3 "Developing custom Web parts"
  • MonoX manual - section 14.6.3 "Web part templates"
  • MSDN Article related to repository model from Domain Driven Design (DDD)

We will divide this how to tutorial in a few short steps:

  • Custom web part development
  • Repository layer used to fetch the data
  • Template design

Custom Web part development

We will start by building a custom Web part which is inherited from the social networking (SN) group base Web part. Our class declaration will look like this:

public partial class PopularGroupsList : GroupListBasePart

Let's move on by implementing the Web part constructor. It specifies that our new part is templated and defines the default template name and path. A Web part template is simply a chunk of HTML that defines the look and feel of our Web part and is placed in the .htm or .ascx file located in a specific directory (under the root directory /App_Templates/{ThemeName}/) Here it goes:

/// <summary>
/// Constructor.
/// </summary>
public PopularGroupsList()
{
    //Set the title that will be visible in the Module gallery so user can pick the module from the list
    Title = "SN popular groups list";
    //Define the template root folder for where MonoX can search for specified template
    ControlSpecificTemplateSubPath = "SocialNetworkingTemplates";
    //Define the template file name (without the extension)
    Template = "PopularGroupsList";
    IsTemplated = true;
}

After we have inherited our class from the group list base part and implemented the constructor properly, we continue by exposing public properties that can be used later for various configuration purposes . It is very important to distinguish between the properties exposed to other developers and properties exposed to CMS administrators via the Web part personalization interface. Properties exposed to developers will be available in the mark-up and code-behind, while properties exposed via personalization system can be changed interactively at runtime using the administrative toolbar. Both approaches can be used at the same time.

By default, administrators can change properties in a shared scope (changes are visible to all users). Authenticated users can change properties in a user scope (changes are visible only to the user that made them).

You can read more about personalization scopes here.

Exposing a property to the personalization system involves setting a few attributes on a public class property:

private bool _pagingEnabled = true;
/// <summary>
/// Gets or sets a value indicating whether paging is enabled.
/// </summary>
[WebBrowsable(true), Personalizable(true)]
[WebDescription("Paging enabled")]
[WebDisplayName("Paging enabled")]
public bool PagingEnabled
{
    get { return _pagingEnabled; }
    set { _pagingEnabled = value; }
}

Now we can move on to the data binding. MonoX Web parts perform data binding in a Page_Load event, or in Page_PreRender for late-bound scenarios:

protected void Page_Load(object sender, EventArgs e)
{
    //Optimize the binding process
    if (this.Visible)
        BindData();
}

Note: If Web parts contains controls that perform delete or similar actions that can change the state of the control, it is very important to call the BindData after each such action.

The binding method will perform data fetching via the popular groups repository. The fetched data will be cached to reduce the number of page hits and improve performance. Note that data caching is configurable on a Web part level and by default it is turned off. You can also use a page level caching that uses different caching mechanism, more details are available in the user's manual. PopularGroups BindData implementation will look like this:

/// <summary>
/// Binds and formats group data for display in the main list view control.
/// </summary>
[WebPartApplyChanges]
public override void BindData()
{
    //Optimize the DB access by using the MonoX cache mechanism (Note: To gain from this kind of optimization you need to set the CacheDuration for this web part)
    MonoXCacheManager cacheManager = MonoXCacheManager.GetInstance(CacheRootKey, this.CacheDuration);
    PopularGroupRepository repository = PopularGroupRepository.GetInstance();
    int recordCount = cacheManager.Get<int>(PopularGroupRepository.CacheParamMonoXPopularGroupsList, "recordCount");
    List<SnGroupDTO> groups = cacheManager.Get<List<SnGroupDTO>>(PopularGroupRepository.CacheParamMonoXPopularGroupsList, pager.CurrentPageIndex + 1, pager.PageSize);
    if (groups == null)
    {
        //Fetch the popular groups
        groups = repository.GetPopularGroups(String.Empty, Guid.Empty, pager.CurrentPageIndex + 1, pager.PageSize, out recordCount);
        //Store the values to the cache (Note: If CacheDuration is set to "0" values are not saved to the cache)
        cacheManager.Store(groups, PopularGroupRepository.CacheParamMonoXPopularGroupsList, pager.CurrentPageIndex + 1, pager.PageSize);
        cacheManager.Store(recordCount, PopularGroupRepository.CacheParamMonoXPopularGroupsList, "recordCount");
    }
    //Perform the binding process that will automatically bind the control and pager
    PagerUtility.BindPager(pager, BindData, lvItems, groups, recordCount);
    ltlNoData.Visible = (recordCount == 0);
}

BindData method has an attribute "WebPartApplyChanges" attached to it. This attribute is used to flag the methods that are called when an administrator changes personalized properties interactively and Web part needs to be refreshed.  

To perform templating, we need to attach to control's item binding method:
 
protected void Page_Init(object sender, EventArgs e)
{
    ....
    this.TemplatedControl.ItemDataBound += new EventHandler<ListViewItemEventArgs>(lvItems_OnItemDataBound);
    ....
}
 
protected void lvItems_OnItemDataBound(object sender, ListViewItemEventArgs e)
{
    if (e.Item.ItemType == ListViewItemType.DataItem)
    {
        SnGroupDTO group = ((ListViewDataItem)e.Item).DataItem as SnGroupDTO;
        //Parse template tags will extract all the needed information from SnGroupDTO and fill the tags collection with tags that are built-in
        //Note: To add any custom tags to web part template you need to override the ParseTemplateTags and add custom tags, then you need to place them in web part template file
        Hashtable tags = ParseTemplateTags(group);
        //Render templated item to the place holder
        //Note: This built-in method will parse the provided html template and replace the tags with the values stored in the tags collection
        RenderTemplatedPart(e.Item, CurrentTemplateHtml, tags);
    }
}

All template tag parsing is done in the group base class so you don't need any additional work, except when you need to add your custom tags to the template.

Repository layer used to fetch the data

To quote the MonoX manual, "Although they share same names and some similarities, MonoX repository model does not directly follow the repository model from Domain Driven Design (DDD), which is defined as "set of methods forretrieving domain objects should delegate to a specialized Repository object such that alternative storage implementations may be easily interchanged". However, this approach allows for much greater degree of encapsulation in your code (when compared with the "codebehind data access" approach) and is generally recommended."

Our practice is to inherit from an existing MonoX repository and to add new or to override the existing methods. In our scenario we will add new method to make the example even simpler and "GetPopularGroups" will be the only method that we will implement for this example.

There are some method parameters that we tend to use in every method that is fetching a collection such as pager page number, page size and the record count:

/// <summary>
/// Retrieves a list of groups matching the criteria specified via method parameters.
/// </summary>
/// <param name="category">Category name.</param>
/// <param name="categoryId">Category Id</param>
/// <param name="pageNumber">Page number.</param>
/// <param name="pageSize">Page size.</param>
/// <param name="recordCount">Record count.</param>
/// <returns>List of groups.</returns>
public List<SnGroupDTO> GetPopularGroups(string category, Guid categoryId, int pageNumber, int pageSize, out int recordCount)
{
}
(To check the whole method implementation please open attached example)

Template design

After we have prepared the back-end and data access, the only thing left to be done is templating. We will proceed by creating a HTML file that contains the templated item and save it under "/App_Templates/YourCustomThemeName/SocialNetworkingTemplates/PopularGroupsList.htm". To start designing the template you will need to get familiar with the MonoX tag structure. A basic template tag looks like this: 

<# TagName #>

MonoX will try to parse all tags in the tags collection provided by the RenderTemplatedPart method mentioned above. If a corresponding tag cannot be found in the template no exception will be thrown.

Here is a basic template for our new part:

<div class="sn-group">
    <table cellpadding="0" cellspacing="0" style="border: none;width:100%;">
        <tr>
            <td style="width: 0%; min-width: 0%; max-width: 0%;">
                <a href="<# UrlView #>"><img src="<# ImageUrl #>" style="border: none;" alt="<# Name #>" /></a>
            </td>
            <td style="width: 100%;">
                <a class="group-name" href="<# UrlView #>"><# Name #></a>
                <div class="group-content">
                    <# Description #>
                </div>
            </td>
        </tr>
    </table>
</div>


To use the attached example please note that you need to copy the web part mark-up and code-behind to the module gallery path so you can dynamically instantiate it. Keep in mind that you need to copy the template to the path mentioned at the beginning of  the "Template design" section.

UPDATE: We have removed attached example because it was outdated and moved the Popular groups sample WebPart to MonoX sample solution on GitHub - MonoX-Sample-Solution

NOTE: In order to properly compile the sample solution you will have to use MonoX CMS as an engine and to do that you need to copy the following folders into sample solution root folder:

- App_Themes
- App_Templates
- MonoX
- Bin
- web.config
Rated 1.00, 1 vote(s). 
By Darryl
While I appreciate the attempt to provide examples of creating web parts for MonoX, I must question the effort and overall usefulness of the example provided. Simply put, it's outdated and virtually unusable. It would be nice to see samples such as this that are applicable to the current release of MonoX. Do you provide any other resources to Web Part development for MonoX?
Thank you,
Darryl
denis
We will review the sample and correct any errors that may be present. The majority of the concepts described in the article still apply, though.
khorvat
Darryl, recently we have provided our sample source code on the GitHub so please follow the activity there (https://github.com/MonoSoftware) also there you can find few other sample of MonoX WebParts such as Twitter Feed module, Notification module and we will update this outdated sample and post the source code there.

Thanks for the feedback we really appreciate the effort.
darryljneil
Denis, Thank you for your attention to the matter. The challenge I was facing was identifying the differences from the original code examples on the Custom Web Part blog entry and accounting for them in the code I was attempting to write while following along.

Kristijan ,Thank you for the link to your Git repository, I'm confident it will come in handy, and thank you for supplying these blog entries as they are helpful starting points to customizing MonoX to fit our needs.
khorvat
We have reviewed the MonoX sample solution and added updated popular groups sample to GitHub so you should use that as stated in UPDATE paragraph of this blog post.

Regards