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:
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