+1-888-365-2779
Try Now
More in this section
Categories
Bloggers
Blogs RSS feed

New Year special - Integrate rating with Generic Content based modules

by Radoslav Georgiev
With the the release of Sitefinity 3.7 SP2 the RadControls for ASP.NET AJAX have been updated and now have a new control in the collection - RadRating. This blog post will go through the steps you have to take in order to implement rating for content items in Generic Content based modules. Please note that the RadRating control is available in Sitefintiy from version 3.7 SP2 (build 2057). If you do not have this version and find the topic interesting you should upgrade your Sitefinity.

Let me first start by wrapping up what has to be done. Since we are going to rate content items we would need to associate the Rating Control with the content item being rated. For these purposes we will need to create two controls - one IScriptControl which will wrap the functionality of the RadRating control and tie it up to a content item and a custom ContentView control, which will pass the content item id and provider name to the first control. To rate content items without making post backs we would also need to create Web Service to handle this. Finally we should keep track of who has rated which item in order to prevent double rating.

For the purposes of this sample I will use the Generic Content module, however this can be easily integrated with any Generic Content based module. Lets start by adding the necessary meta fields to the meta fields collection of Generic Content module:

<add key="Generic_Content.Rating" valueType="ShortText" visible="True" searchable="True" sortable="True" defaultValue="0.0"/> 
<add key="Generic_Content.RatingSum" valueType="ShortText" visible="True" searchable="false" sortable="false" defaultValue="0.0"/> 
<add key="Generic_Content.RatingCount" valueType="Integer" visible="True" searchable="True" sortable="true" defaultValue="0"/> 

 

The next step is to build our custom IScript control, create a control template for it and expose content item information to our client control. Code code bellow is for the server part of our IScript control:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.Web.Security; 
using System.Web.UI; 
using System.Web.UI.WebControls; 
using Telerik.Cms.Engine; 
using Telerik.Cms.Web.UI; 
using Telerik.Web.UI; 
 
namespace Telerik.Samples.WebControls 
    /// <summary> 
    /// Summary description for SitefinityRating 
    /// </summary> 
    public class SitefinityRating : SimpleControl, IScriptControl 
    { 
        #region Private Properties 
        private double ratingSum; 
        private Guid contentID; 
        private string providerName; 
        //path to the client code for our IScript control 
        private string jsFilePath = "~/Sitefinity/Admin/Scripts/SitefinityRating.js"
        #endregion 

        #region Public Properties 
        public Guid ContentID 
        { 
            get 
            { 
                return this.contentID; 
            } 
            set 
            { 
                this.contentID = value; 
            } 
        } 
 
        public string ProviderName 
        { 
            get 
            { 
                return this.providerName; 
            } 
            set 
            { 
                this.providerName = value; 
            } 
        } 
 
        public double RatingSum 
        { 
            get  
            { 
                return this.ratingSum; 
            } 
            set 
            { 
                this.ratingSum = value; 
            } 
        } 
        public RadRating RadRating 
        { 
            get 
            { 
                return base.Container.GetControl<RadRating>("RadRating1",false, Telerik.Framework.Web.TraverseMethod.BreadthFirst); 
            } 
            set 
            { 
                this.RadRating = value; 
            } 
        } 
 
        public Label Rating 
        { 
            get 
            { 
                return base.Container.GetControl<Label>("Rating"false, Telerik.Framework.Web.TraverseMethod.BreadthFirst); 
            } 
            set 
            { 
                this.Rating = value; 
            } 
        } 
 
        public Label RatingCount 
        { 
            get  
            { 
                return base.Container.GetControl<Label>("RatingCount"false, Telerik.Framework.Web.TraverseMethod.BreadthFirst); 
            } 
            set 
            { 
                this.RatingCount = value; 
            } 
        } 
        #endregion 

        #region IScriptControl Members 
        //expose the necessary data to the client control 
        public IEnumerable<ScriptDescriptor> GetScriptDescriptors() 
        { 
            var descriptor = new ScriptBehaviorDescriptor(this.GetType().FullName, this.ClientID); 
            descriptor.AddProperty("contentId"this.ContentID); 
            descriptor.AddProperty("providerName"this.ProviderName); 
            descriptor.AddProperty("radRatingId",this.RadRating.ClientID); 
            descriptor.AddProperty("ratingId"this.Rating.ClientID); 
            descriptor.AddProperty("ratingCountId"this.RatingCount.ClientID); 
            descriptor.AddProperty("ratingSum"this.RatingSum); 
            return new ScriptDescriptor[] { descriptor }; 
        } 
        //create a script reference 
        public IEnumerable<ScriptReference> GetScriptReferences() 
        { 
            ScriptReference reference = new ScriptReference(this.jsFilePath); 
            return new ScriptReference[] { reference };  
        } 
        #endregion 

        #region Overriden Methods 
 
        protected override void Render(HtmlTextWriter writer) 
        { 
            if (!(this.Page == null)) 
            { 
                var scriptManager = ScriptManager.GetCurrent(this.Page); 
                if (scriptManager != null
                { 
                    scriptManager.RegisterScriptDescriptors(this); 
                } 
            } 
            base.Render(writer); 
        } 
        //set layout template path for the control 
        public override string LayoutTemplatePath 
        { 
            get 
            { 
                return "~/CustomControls/ControlTemplates/SitefinityRating.ascx"
            } 
            set 
            { 
                base.LayoutTemplatePath = value; 
            } 
        } 
       
        protected override void OnPreRender(EventArgs e) 
        { 
            if (!(this.Page == null)) 
            { 
                var scriptManager = ScriptManager.GetCurrent(this.Page); 
                if (scriptManager != null
                { 
                    scriptManager.RegisterScriptControl<SitefinityRating>(this); 
                } 
            } 
            //we check if user is authenticated and if user has already rated item 
            //if yes then the user will not be able to rate item 
            MembershipUser currentUser = Telerik.Security.UserManager.Default.GetUser(); 
            if (!HttpContext.Current.User.Identity.IsAuthenticated) 
                this.RadRating.ReadOnly = true
            else 
            { 
                RatingDataDataContext _db = new RatingDataDataContext(); 
                IQueryable<sf_Rating> userRatings = from ratings in _db.sf_Ratings 
                                                    where ratings.UserID == (Guid)currentUser.ProviderUserKey && ratings.ContentId == this.ContentID 
                                                    select ratings; 
                if (userRatings.ToList<sf_Rating>().Count > 0) 
                { 
                    this.RadRating.ReadOnly = true
                } 
            } 
            //get necessary data which will be passed to the client control 
            //we get meta field data from by instatiating a content item from the ID passed by the content view 
            ContentManager contentManager = new ContentManager(ProviderName); 
            IContent contentItem = contentManager.GetContent(ContentID); 
            this.RatingCount.Text = contentItem.GetMetaData("RatingCount").ToString(); 
            this.Rating.Text = contentItem.GetMetaData("Rating").ToString(); 
            if(this.Rating.Text==""
                this.Rating.Text="0"
            if (contentItem.GetMetaData("RatingSum").ToString() != ""
                this.RatingSum = double.Parse(contentItem.GetMetaData("RatingSum").ToString()); 
            else this.RatingSum = 0.0; 
            this.RadRating.Value = RatingSum / Int32.Parse(RatingCount.Text); 
            base.OnPreRender(e); 
        } 
        #endregion 
    } 

This is the markup of our control:

<%@ Control Language="C#"%> 
<%@ Register Namespace="Telerik.Web.UI" Assembly="Telerik.Web.UI" TagPrefix="telerik"%> 
<telerik:RadRating ID="RadRating1" runat="server"  
    Precision="Exact" ItemCount="5" Skin="Vista" 
    SelectionMode="Continuous" Orientation="Horizontal" /> 
<asp:Label ID="RatingLabel" runat="server" AssociatedControlID="Rating">Rating: </asp:Label> 
<asp:Label ID="Rating" runat="server" Text=""></asp:Label> 
<br /> 
<asp:Label ID="RatingCountLabel" runat="server" AssociatedControlID="RatingCount">Times Rated: </asp:Label> 
<asp:Label ID="RatingCount" runat="server" Text=""></asp:Label> 

And lastly the client code:

// Register the namespace for client control. 
Type.registerNamespace('Telerik.Samples.WebControls'); 
 
// ------------------------------------------------------------------------ 
// Definition of SitefinityRating class and properties 
// ------------------------------------------------------------------------ 
Telerik.Samples.WebControls.SitefinityRating = function(element) { 
 
    Telerik.Samples.WebControls.SitefinityRating.initializeBase(this, [element]); 
    // ------------------------------------------------------------------------ 
    // SitefinityRating class properties 
    // ------------------------------------------------------------------------ 
    this._contentId = null
    this._providerName = null
    this._radRatingId = null
    this._ratingId = null
    this._ratingCountId = null
    this._ratingSum = null
 
Telerik.Samples.WebControls.SitefinityRating.prototype = { 
    // ------------------------------------------------------------------------ 
    // initialization and clean-up 
    // ------------------------------------------------------------------------ 
    initialize: function() { 
        Telerik.Samples.WebControls.SitefinityRating.callBaseMethod(this'initialize'); 
        Sys.Application.add_load(Function.createDelegate(thisthis._onLoad)); 
 
    }, 
    dispose: function() { 
        Telerik.Samples.WebControls.SitefinityRating.callBaseMethod(this'dispose'); 
    }, 
    // ------------------------------------------------------------------------ 
    // public functions 
    // ------------------------------------------------------------------------ 
    rate: function(rating) { 
        this.get_radRating().set_readOnly(true); 
        Telerik.Samples.WebServices.RatingService.RateContent(this.get_contentId(), this.get_providerName(), rating, Function.createDelegate(thisthis._onRateSuccess), this._onRateFailure); 
    }, 
    // ------------------------------------------------------------------------ 
    // private functions 
    // ------------------------------------------------------------------------ 
    _onLoad: function(sender, args) { 
        var radRating = this.get_radRating(); 
        if (radRating) { 
            radRating.add_rating(Function.createDelegate(thisthis._onRatingPreview)); 
            debugger
        } 
    }, 
    _onRatingPreview: function(sender, args) { 
        var rating = args.get_newValue(); 
        this.rate(rating); 
    }, 
    _onRateSuccess: function(caller, successData) { 
        var rating = this.get_rating(); 
        var ratingCount = this.get_ratingCount(); 
        var radRating = this.get_radRating(); 
        var ratingSum = this.get_ratingSum(); 
        var newRatingSum = ratingSum + radRating.get_value(); 
        var newRatingCount = parseInt(ratingCount.textContent) + 1; 
        var newRating = newRatingSum / newRatingCount; 
        radRating.set_value(newRating); 
        rating.textContent = newRating; 
        ratingCount.textContent = newRatingCount; 
    }, 
    _onRateFailure: function(errorData) { 
        alert("" + errorData); 
    }, 
    // ------------------------------------------------------------------------ 
    // property accessors 
    // ------------------------------------------------------------------------ 
    get_contentId: function() { 
        return this._contentId; 
    }, 
    set_contentId: function(value) { 
        if (this._contentId !== value) { 
            this._contentId = value; 
            this.raisePropertyChanged('contentId'); 
        } 
    }, 
    get_providerName: function() { 
        return this._providerName; 
    }, 
    set_providerName: function(value) { 
        if (this._providerName !== value) { 
            this._providerName = value; 
            this.raisePropertyChanged('providerName'); 
        } 
    }, 
    get_radRatingId: function() { 
        return this._radRatingId; 
    }, 
    set_radRatingId: function(value) { 
        if (this._radRatingId !== value) { 
            this._radRatingId = value; 
            this.raisePropertyChanged('radRatingId'); 
        } 
    }, 
    get_radRating: function() { 
        var rating = $find(this.get_radRatingId()); 
        return rating; 
    }, 
    get_ratingId: function() { 
        return this._ratingId; 
    }, 
    set_ratingId: function(value) { 
        if (this._ratingId !== value) { 
            this._ratingId = value; 
            this.raisePropertyChanged('ratingId'); 
        } 
    }, 
    get_rating: function() { 
        var rating = $get(this._ratingId); 
        return rating; 
    }, 
    get_ratingCountId: function() { 
        return this._ratingCountId; 
    }, 
    set_ratingCountId: function(value) { 
        if (this._ratingCountId !== value) { 
            this._ratingCountId = value; 
            this.raisePropertyChanged('ratingCountId'); 
        } 
    }, 
    get_ratingCount: function() { 
        var rating = $get(this.get_ratingCountId()); 
        return rating; 
    }, 
    get_ratingSum: function() { 
        return this._ratingSum; 
    }, 
    set_ratingSum: function(value) { 
        if (this._ratingSum !== value) { 
            this._ratingSum = value; 
            this.raisePropertyChanged('ratingSum'); 
        } 
    } 
Telerik.Samples.WebControls.SitefinityRating.registerClass('Telerik.Samples.WebControls.SitefinityRating', Sys.UI.Control); 

 

After we have created our SitefinityRating control, it is time to customize the ContentView control to pass the content item ID and the provider name to it. We have to create a control which inherits from ContentView override the LayoutTemplatePaths and point to custom templates to which we have added our SitefinityRating control, finally we should override the SetItemMetadata and OnPreRender methods. The first one is to pass id and provider name. The second one is to add a reference to the web service which is going to update the ratings:

protected override void OnPreRender(EventArgs e) 
    base.OnPreRender(e); 
    ScriptManager sm = ScriptManager.GetCurrent(this.Page); 
    ServiceReference reference = new ServiceReference("~/Sitefinity/Admin/Services/RatingService.asmx"); 
    reference.InlineScript = true
    sm.Services.Add(reference); 
 
protected override void SetItemMetadata(Control itemContainer, Telerik.Cms.Engine.IContent contentItem) 
    base.SetItemMetadata(itemContainer, contentItem); 
      
    SitefinityRating rating = (SitefinityRating)this.FindContentViewControl("sfRating", itemContainer); 
 
    if (rating != null
    { 
        rating.ContentID = contentItem.ID; 
        rating.ProviderName = contentItem.ProviderName; 
    } 

 

Now we have to create the WebService and implement the RateContent WebMethod which is will update content ratings :

[WebMethod] 
public void RateContent(Guid contentID, string providerName, double ratingValue) 
    MembershipUser user = Telerik.Security.UserManager.Default.GetUser(); 
    if (user != null
    { 
        //get content item being rated 
        ContentManager contentManager = new ContentManager(providerName); 
        IContent contentItem = contentManager.GetContent(contentID); 
        //get rating sum 
        double RatingSum = 0; 
        if (contentItem.GetMetaData("RatingSum").ToString() != ""
            RatingSum = double.Parse(contentItem.GetMetaData("RatingSum").ToString()); 
        RatingSum = RatingSum + ratingValue; 
        //increase rating count 
        int RatingCount = Int32.Parse(contentItem.GetMetaData("RatingCount").ToString()); 
        RatingCount = RatingCount + 1; 
        //calculate rating 
        double Rating = RatingSum / RatingCount; 
        //set new values to metafields and save item 
        contentItem.SetMetaData("RatingSum", RatingSum.ToString()); 
        contentItem.SetMetaData("RatingCount", RatingCount); 
        contentItem.SetMetaData("Rating", Rating.ToString()); 
        contentManager.SaveContent(contentItem); 
        //save record that user has rated this content items 
        RatingDataDataContext _db = new RatingDataDataContext(); 
        sf_Rating newRating = new sf_Rating(); 
        newRating.ContentId = contentID; 
        newRating.UserID = (Guid)user.ProviderUserKey; 
        _db.sf_Ratings.InsertOnSubmit(newRating); 
        _db.SubmitChanges(); 
    } 

 

In the above code you have probably noticed that I am using LINQ to retrieve and store data about which user has rated which item. For this I have created a table in my DB to store each user's unique identifier and the content item ID. Both of those values are of type Guid. I have set those two values that I retain to be a composite primary key for this table. This table is added to my Sitefinity DB and is called sf_Ratig. Bellow is a snapshot of how my table looks in MS SQL.

Lastly you have to add hidden text boxes to the control template for editing content items and map this template, in order to prevent loosing the rating values once content item is being edited. The control template is ~/Sitefinity/Admin/ControlTemplates/Generic_Content/ContentEditView.ascx. I have added this to my template:

 
<sf:ContentMetaFields id="MetaFields" runat="server"
            <ItemTemplate> 
               ... 
                <asp:TextBox ID="Rating" runat="server" visible="false"/> 
                <asp:TextBox ID="RatingSum" runat="server" visible="false"/> 
                <asp:TextBox ID="RatingCount" runat="server" visible="false"/> 
                <div class="bottom"><div><!-- --></div></div> 
               ... 
            </ItemTemplate> 
        </sf:ContentMetaFields> 

 

All you have to do now is to add the control to your controls collection:

<toolboxControls> 
    <clear/> 
    ... 
    <add name="Custom Content View" section="Custom Controls" type="Telerik.Samples.WebControls.CustomContentView, App_Code"/> 
</toolboxControls> 

You can download all sample code and other related files from the following link: SitefinityRating.

Please, note that after you have added new meta fields to the Generic Content module items which you previously had will not contain the default values for those meta fields. You will have to go through all content items and "dummy" edit and save them.

Leave a comment