Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Using Telerik RadControls With DotNetNuke. Complete Rating And Comments Solution

4.77/5 (8 votes)
14 Jun 2010CPOL8 min read 49.2K   597  
This tutorial shows usage of Telerik controls in DotNetNuke. It lead you through a process of creation rating / comments solution for a complete DotNetNuke page or any other content identified by URL.

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Download сomplete source code for the article - 3.8 KB

DotNetNuke Telerik Tutorial: RadRating and RadChart usage

Live demo for the tutorial

Introduction

DotNetNuke 5.2+ goes with powerful Telerik control library. As far as Telerik Controls is a typical ASP.NET library one can think that he need a Visual Studio to create modules using amazing Telerik controls and features. Here I’ll show you how to add Telerik Controls to you DotNetNuke site pages without need to develop and deploy any modules. All tools you need are free and open source. You need a DotNetNuke community Edition 5.2 or higher and XsltDb 01.01.24 or higher.

A very nice task for this Telerik-DotNetNuke tutorial is to create a page rating/comments system. For the rating I take two Telerik controls: RadRating and RadChart and implement a rating system similar to the codeproject article rating. Rating subsystem must support the following features:

  • Display current page rating,
  • Display votes histogram: how votes are distributed by value,
  • Let visitor to vote,
  • Control visitor votes using cookie, if visitor already voted system deletes previous vote. This allows visitor to correct/change their votes but prevent votes duplication.

For the comments I take RadEditor for the comment text and RadComboBox for the Name / E-Mail / Site input boxes. Why Combo? RadTextBox doesn’t support rounded corners. But It is possible to setup a combo to enter text without drop down feature. I believe Telerik will support rounded corners for RadTextBox and RadEditor in future releases. Comments subsystem must support the following features:

  • List of comments in chronological order,
  • Comment submission form,
  • Commenter's avatar,
  • Comment deletion for site admin,
  • New comments E-Mail notification.

One thing is left to define. What is the object of rating/commenting? Actually, we don’t need to know what this object is we just need it’s ID. In the web we can reliably use URL as content ID. Real URL may contain additional parameters that don’t identify content so we must filter them out when building ID of content to rate/comment.

You can view it live before reading

Content Identification

There are 2 methods of getting current URL

  • After URL rewrite: mdo:aspnet('Request.Url.PathAndQuery')
  • Before URL Rewrite: mdo:aspnet('Request.RawUrl')

You must decide what method is suitable for you. I use here original client URL. So PageID is built as follows:

XML
<xsl:variable name="page-id" select="mdo:aspnet('Request.RawUrl')" />

To deliver reliable portal identification we use relative URLs and XsltDb’s feature of data isolation. So the database table for the identified content will be the following:

SQL
create table {databaseOwner}MDO_PageRating
(
  PageID int identity primary key,
  PortalID int,
  PageGuid nvarchar(256)
)

To simplify content identification we use the following combined stored procedure that checks if the page already identified and if not – just insert a new record and returns it’s ID

SQL
create procedure {databaseOwner}MDO_PageRating_EnsurePage
    @PortalID int,
    @PageGuid nvarchar(max),
    @PageID int out
as
begin
    select @PageID = PageID from MDO_PageRating
    where PageGuid = @PageGuid and PortalID = @PortalID
    
    if @PageID is null begin
        insert {databaseOwner}MDO_PageRating(PortalID, PageGuid)
        values(@PortalID, @PageGuid);
    
        set @PageID = SCOPE_IDENTITY();
    end;
end;
)

Now we are ready to begin with rating.

Rating Subsystem

Database objects

First we must create a table that stores each vote

SQL
create table {databaseOwner}MDO_PageRating_Rate
(
    RateID int identity primary key,
    PageID int constraint MDO_PageOfRating foreign key references MDO_PageRating(PageID)
        on delete cascade on update cascade,
    Value float,
    Cookie uniqueidentifier
)

And we need 2 stored procedures: to register a new vote and to retrieve information about votes. New vote registration:

SQL
create procedure {databaseOwner}[{objectQualifier}mdo_xslt_rate_page]
    @PortalID int,
    @PageGuid nvarchar(max),
    @Value float,
    @Cookie nvarchar(max)
as
begin
    declare @PageID int;
    exec {databaseOwner}MDO_PageRating_EnsurePage @PortalID, @PageGuid, @PageID out;

    delete MDO_PageRating_Rate
    where PageID = @PageID
    and Cookie = @Cookie;
    
    insert {databaseOwner}MDO_PageRating_Rate(PageID, Value, Cookie)
    values(@PageID, @Value, @Cookie);
end;

Query for votes and rating:

create procedure {databaseOwner}[{objectQualifier}mdo_xslt_page_rating]
    @PortalID int,
    @PageGuid nvarchar(max)
as
begin
    select pr.* from {databaseOwner}MDO_PageRating_Rate pr
    join {databaseOwner}MDO_PageRating p on p.PageID = pr.PageID
    where p.PageGuid = @PageGuid
    and p.PortalID = @PortalID;
    
    select
        SUM(StatCount) TotalVotes,
        MAX(StatCount) Height,
        SUM(StatValue * StatCount) / SUM(StatCount) Average
    from (
        select pr.Value StatValue, COUNT(*) StatCount
        from {databaseOwner}MDO_PageRating_Rate pr
        join {databaseOwner}MDO_PageRating p on p.PageID = pr.PageID
        where p.PageGuid = @PageGuid
        and p.PortalID = @PortalID
        group by pr.Value) t;
end;

This stored procedure returns 2 result sets: list of votes and rating summary for the histogram. In rating summary we have total count of votes, maximum histogram bar height and current rating as average of votes.

Display current rating

To display current content rating we create 2 objects RadRating – it displays average rating of the page and RadChart – it displays votes distribution. So we make 3 steps

1. Read current page rating info from database

XML
<xsl:variable name="rating" select="mdo:xml('page-rating', 'rating, total', $page-id)" />

2. Create a RadRating control to display current rating

XML
<telerik:RadRating ID="PageRating" runat="server" Value="{mdo:coalesce($rating//Average, 0)}" ReadOnly="True" Skin="Office2007" />

3. Create RadChart control to display votes distribution

XML
<telerik:RadChart runat="server" ID="RadChart2" ChartTitle-Visible="false" Width="100" Height="50" Skin="Office2007" >
  <Series>
    <telerik:ChartSeries Type="Bar">
      <Items>
        <xsl:for-each select="mdo:sequence(1,5,1)">
          <telerik:ChartSeriesItem XValue="{current()}"
                                 YValue="{count($rating//rating[Value=current()])+0.3}"
                                 Label-TextBlock-Text="{count($rating//rating[Value=current()])}"/>
        </xsl:for-each>
      </Items>
    </telerik:ChartSeries>
  </Series>
  <PlotArea Appearance-Dimensions-Margins="0,0,0,0">
    <XAxis AutoScale="False" MinValue="0.5" MaxValue="5.5" Step="1" />
    <YAxis AutoScale="False" MinValue="0"
          MaxValue="{mdo:coalesce($rating//Height,0) + 1}"
          Visible="False" />
  </PlotArea>
  <Legend Visible="false"/>
</telerik:RadChart>

Some fragments of listing above must be clarified.

  • <xsl:for-each select="mdo:sequence(1,5,1)"> executes a loop from 1 to 5. XSL does not support loop variables so XsltDb provides a mdo:sequence function that returns XML-nodes that are actually the list of values for the loop variable. The current value can be captured using standard xsl function current()
  • YValue="{count($rating//rating[Value=current()])+0.3}" allows us to create a 0.3-height bar if there were no such votes.
  • MaxValue="{mdo:coalesce($rating//Height,0) + 1}" assign a vertical scale to keep space for numbers above bars.

Rating input control

To let visitor vote we create another RadRating control as follows:

XML
<telerik:RadRating ID="PageRating2" runat="server" Value="{mdo:coalesce($rating//Average, 0)}" OnClientRated="onClientRated" Skin="Office2007"
  >
  <Items>
    <telerik:RadRatingItem Value="1" ToolTip="Poor" />
    <telerik:RadRatingItem Value="2" ToolTip="Fair" />
    <telerik:RadRatingItem Value="3" ToolTip="Good" />
    <telerik:RadRatingItem Value="4" ToolTip="Excellent" />
    <telerik:RadRatingItem Value="5" ToolTip="Awesome" />
  </Items>
</telerik:RadRating>

For the better understanding stars meaning I assign a meaningful label for each star in RadRating Control. Initially it shows current rating of the page (Value="{mdo:coalesce($rating//Average, 0)}") and is subscribed to a rating event (OnClientRated="onClientRated"). Javascript function onClientRated is very simple

JavaScript
function onClientRated(sender, args)
{
  {{mdo:submit('@rate', '@sender.get_value()')}}
}

It just get a value of rating provided by a visitor and submits it to server. Note that @-sign in @rate means that XsltDb keeps value of @rate only one request (command-like behavior) @-sign in submitting value means that this is javascript code and must be evaluated just before the submission.

After the rating is submitted to the server we must save it to the database. The code is simple:

XML
<xsl:if test="mdo:param('@rate')">
  <xsl:if test="not(mdo:cookie('rater'))">
    <xsl:if test="mdo:set-cookie('rater', mdo:newid(), '2020-01-01')" />
  </xsl:if>

  <xsl:if test="mdo:xml('rate-page', '$script', $page-id, mdo:param('@rate'), mdo:cookie('rater'))" />

  {{mdo:redirect(mdo:aspnet('Request.RawUrl'))}}

</xsl:if>

As you see only 3 steps are need to send ratind value to the database.

First, check if the visitor is identified. If not – create a cookie. I use mdo:newid() as a cookie value. mdo:newid() generates a guid so visitor is uniquely identified.

Second, send new rating value to the database. DB will check whether the visitor is new and insert a new rating or the visitor has already rated this content and DB replace previous vote.

Third, we redirect to the current page. This is not necessary but I suggest making this redirection because:

  • You end a page lifecycle and begin new one, so your standard ASP.NET controls and Telerik controls go to their initial state. This let us avoid writing cleanup code that resets controls values to their initial state without breaking ASP.NET page lifecycle.
  • When visitor pushes F5 it won’t see ugly message box whether he wants to resend data to server. And it also prevents double submission of data to server.

Rating subsystem is almost done, switching to comments subsystem

Comments Subsystem

Database objects

As for the rating here we need a table to store comments and 3 stored procedures to retrieve comment list, to save new comments to the database and to delete a comment by admin.

SQL
create table {databaseOwner}MDO_PageRating_Comment
(
    CommentID int identity primary key,
    ParentID int,
    PageID int constraint MDO_PageOfComment foreign key references MDO_PageRating(PageID)
        on delete cascade on update cascade,
    Comment nvarchar(max),
    EMail nvarchar(128),
    URL nvarchar(128),
    Name nvarchar(128),
    DTCreated datetime default getdate()
)
create procedure {databaseOwner}[{objectQualifier}mdo_xslt_comment_page]
    @PortalID int,
    @PageGuid nvarchar(max),
    @ParentID int,
    @Comment nvarchar(max),
    @Name nvarchar(max),
    @EMail nvarchar(max),
    @URL nvarchar(max)
as
begin
    declare @PageID int;
    exec {databaseOwner}MDO_PageRating_EnsurePage @PortalID, @PageGuid, @PageID out;
    
    if @ParentID < 1 set @ParentID = null;

    insert {databaseOwner}MDO_PageRating_Comment(ParentID,PageID,Comment,EMail,URL,Name)
    values(@ParentID,@PageID,@Comment,@EMail,@URL,@Name);
end;
create procedure {databaseOwner}[{objectQualifier}mdo_xslt_page_comments]
    @PortalID int,
    @PageGuid nvarchar(max)
as
begin
    select c.* from {databaseOwner}MDO_PageRating_Comment c
    join {databaseOwner}MDO_PageRating p on p.PageID = c.PageID
    where p.PageGuid = @PageGuid
    and p.PortalID = @PortalID;
end;

create procedure {databaseOwner}[{objectQualifier}mdo_xslt_delete_page_comment]
    @PortalID int,
    @CommentID int
as
begin
    delete {databaseOwner}MDO_PageRating_Comment
    where CommentID = @CommentID
    and PageID in (select PageID from {databaseOwner} MDO_PageRating where PortalID = @PortalID);
end;

Comment input form

I love rounded corners, so I want to use Telerik controls to create rounded input boxes. The problem is that RadTextBox does not provide an option to round corners. There for I switch to use RadComboBox. The following code creates an input with rounded corners based on RadComboBox:

XML
<telerik:RadComboBox runat="server" ShowToggleImage="False" ShowDropDownOnTextboxClick="False" AllowCustomText="True"/>

This approach is for the commenter’s Name, E-Mail and Web Site fields. I also want my comments be easy-to-read so I use RadEditor for the comment itself. I let only basic formatting for the commenter so I must enumerate tools that are allowed in comment window:

XML
<telerik:RadEditor runat="server" ID="Message" EditModes="Design" Width="491px" Height="200px">
      <Tools>
            <telerik:EditorToolGroup >
                <telerik:EditorTool Name="Bold" />
                <telerik:EditorTool Name="Italic" />
                <telerik:EditorTool Name="Underline" />
            </telerik:EditorToolGroup>
            <telerik:EditorToolGroup >
                <telerik:EditorTool Name="InsertUnorderedList" />
                <telerik:EditorTool Name="InsertOrderedList" />
            </telerik:EditorToolGroup>
            <telerik:EditorToolGroup >
                <telerik:EditorTool Name="JustifyLeft" />
                <telerik:EditorTool Name="JustifyCenter" />
                <telerik:EditorTool Name="JustifyRight" />
                <telerik:EditorTool Name="JustifyFull" />
            </telerik:EditorToolGroup>
            <telerik:EditorToolGroup >
                <telerik:EditorTool Name="Cut" />
                <telerik:EditorTool Name="Copy" />
                <telerik:EditorTool Name="Paste" />
            </telerik:EditorToolGroup>
           </Tools>     
</telerik:RadEditor>

Under the RadEditor I put a button that submits comment form

XML
<input type="button" onclick="{mdo:jsubmit('@comment', 'yes')}" value="Save Comment" />

I want my "Send Comment" button to be skinned with Office2007 skin like others controls. For that purpose I use RadFormDecorator that allows me to apply a skin to a particular section of a page.

XML
<telerik:RadFormDecorator runat="server" DecorationZoneID="rating-and-comments" Skin="Office2007"/>
<!-- All controls go here -->
<div id="rating-and-comments">
</div>

Server side comments processing

After comment is submitted we must put it into the database and notify site/page owner that the content was commented. He code is following:

XML
<xsl:if test="mdo:param('@comment')">
    <xsl:if test="mdo:xml('comment-page', '$script', $page-id, -1,
        mdo:url-decode(mdo:request(mdo:client-name('Message'))),
        mdo:request(mdo:client-name('Name')),
        mdo:request(mdo:client-name('EMail')),
        mdo:request(mdo:client-name('Url'))
    )" />

  <xsl:variable name="mail">
    <p>
      New comment added to the site http://xsltdb.com.
      <a href="http://xsltdb.com{mdo:aspnet('Request.RawUrl')}">Click here to read comments.</a>
    </p>
  </xsl:variable>
  <xsl:if test="mdo:mail('admin@xsltdb.com', 'admin@xsltdb.com', 'New comments added', mdo:text($mail))" />

  {{mdo:redirect(mdo:aspnet('Request.RawUrl'))}}
</xsl:if>

Here we make 3 steps

1. mdo:xml('comment-page', '$script'…) saves new comment to the database. Here I want to clarify how values of ASP.NET controls can be retrieved. As usual, ASP.NET control has a hidden input associated that stores the value. You can get the name of the control using mdo:client-name() function. This function gets a local ID of ASP.NET control and returns global client name of the control’s hidden input. After that you can get a value using mdo:request() function. RadEditor stores an Url-encoded text in this input so we have to decode it before sending to the database using mdo:url-decode() function.

2. After comment is saved to the database we create and send a mail message to the site admin using mdo:mail() function. This function sends a HTML message that we build as XSL variable and convert to string using mdo:text() function.

3. After processing is finished we restart ASP.NET page lifecycle using mdo:redirect() exactly as we did it for the rating.

Displaying comments

To display a comments list for a page we first read comment list from database

XML
<xsl:variable name="comments" select="mdo:xml('page-comments', 'comment', $page-id)" />

The following code displays comments list

XML
<xsl:for-each select="$comments//comment">
  <div style="width:100px;float:left;">
    <img src="http://www.gravatar.com/avatar/{mdo:md5(EMail)}" />
    <div style="color:grey">
      <a rel="external nofollow" style="font-size:smaller" href="{URL}">{{Name}}</a>,
      {{mdo:fmt-date(DTCreated, 'd')}}
    </div>
  </div>
  <div style="float:left;background-color:white;width:375px;padding:10px;border:1px solid silver;">
    {h{Comment}}
    <div style="padding-top:45px;">
      <xsl:if test="mdo:isinrole('Administrators')">
        <a href="{mdo:jsubmit('@delete-comment', CommentID)}" class="CommandButton">Delete</a>
      </xsl:if>
    </div>
  </div>
  <div style="clear:both"/>
</xsl:for-each>

This code display the following for each comment

  • Image of commenters. This is very easy since we have a gravatar service. Just take a e-mail and create a commenter image URL: src="http://www.gravatar.com/avatar/{mdo:md5(EMail)}"
  • Comment text is displayed as HTML. {h{Comment}} as far as we use a RadEditor we can be sure that HTML does not contain script sections.
  • If current user is a portal Administrator it must be able to delete comment. So we have a block of code doing that after <xsl:if test="mdo:isinrole('Administrators')">.

Conclusion

This tutorial shows how to create a simple rating/commenting system with flat comments. In the live demo we can see a little bit more complex comments with replys. This is not included in the article text, but article attachments contain the real code from xsltdb.com site that supports comment replys. I excluded this function from the article to reduce the article size as it doesn’t demonstrate unique features of Telerik Controls or XsltDb Module.

Using code attached to the article

To use this code you need DotNetBuke 5.2 or higher, 5.4.2 is the recommended version. Deployment is very simple:

  • Login as superuser to your DotNetNuke site.
  • Install XsltDb module, download it from here
  • Create database objects. Go to the Host/SQL tab, paste DatabaseObject.sql into the textbox, select RunAsScript, click Execute.
  • Navigate to a page you want to rate/comment Add new XsltDb module to the page Click EditXSLT link Paste ModuleCode.xslt in the textarea Look through the code and change emails and urls to yours. Click Update & Publish.
  • Enjoy Rating/Commenting.

Tools used to create Comments/Rating module:

  • Windows Server 2008 / IIS 7.5 / .NET 3.5
  • DotNetNuke 5.4.2 Community Edition with Telerik RadControls inside
  • XsltDb DotNetNuke integration module

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)