This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.
One of the most requested features for Sitefinity 4 has recently been a rating control for blogs (and other content). This is the reason why I decided to show you in this post how to make one yourself. However, this post is not only about how to integrate a RadRating control with Content modules, but also how to make use of WCF Restful services, hosted in Sitefinity. In the guide, there are also various other know-hows, including - getting the value of a custom field from a template, using jquery ajax in templates and gathering data from an external resource. In the example you will also see how you can make use of Entity Framework with Sitefinity.
First, I will briefly explain the plan. Here are the steps that we are going to take in order to make a working RadRating control integrated with Blog Posts:
1) Creating an extra table inside Sitefinity's database. This table will hold information about the rating.
2) Creating the model of the rating and preparing its CRUD operations inside a RatingManager.
3) Creating a WCF Restful service that makes the connection between the manager and the template.
4) Creating a template and consuming the service from inside it.
As a side feature, I have shown how to put the rating data not only in the database but also in custom fields inside our Blog Posts, so that the rating information can be seen from the backend. In addition to that, I have binded the value of the custom fields inside our template.
So, let's start this step by step:
1)Creating the database table
There isn't much to explain here. It's just a manual adding of a table in Sitefinity's database. The easiest way would be using SQL Management Studio. Our table needs to contain the following:
Two columns that will hold the ID of the content item (blog post in this case) and the ID of the user that voted for this item. This way we will always be able to keep track of who voted for what and prevent double voting. These columns are of type uniqueidentifier and are primary keys.
I also added a column with the rating of the user (I called it Count). This column is of type decimal with 1 symbol after the decimal point (18,1):

Before we proceed with the next step, we must create a new project in our solution that will hold the model, the CRUD operations and the WCF service. Here's a picture of my folder structure:

2) For the model, I simply used Entity Framework, just to show you that it can be easily worked with in Sitefinity. However, you are free to use Open Access or whatever else you like. Creating the model goes the old-fashioned way (right-click on the DataModel folder, Add New Item >> ADO.NET Entity Data Model and then just go through the steps of creating your model). We should build the application here. An App.Config with the connection string to the database will be created inside your project. However, you won't need that. We already said that the service will be hosted inside Sitefinity, so you have to go to the app.config and copy the connection string inside Sitefinity's web.config (that goes directly before <system.web>):
<connectionStrings> <add name="First43DBEntities"connectionString="metadata=res://*/DataModel.RatingModel.csdl|res://*/DataModel.RatingModel.ssdl|res://*/DataModel.RatingModel.msl;provider=System.Data.SqlClient;provider connection string="data source=.\MSSQLR2;initial catalog=First43DB;integrated security=SSPI;multipleactiveresultsets=True;App=EntityFramework""providerName="System.Data.EntityClient" /> </connectionStrings>using System;using System.Collections.Generic;using System.Linq;using System.Data.Entity;using System.Text;using RatingControl.DataModel;using Telerik.Sitefinity.Modules.Blogs;namespace RatingControl.DataManager{ class RatingManager { public decimal GetCurrentRatingOfItem(Guid contentItemId) { decimal sum = 0; decimal result = 0; List<sf_rating> controlsCount = new List<sf_rating>(); using (First43DBEntities context = new First43DBEntities()) { controlsCount = context.sf_rating.Where(r => r.ItemId == contentItemId).ToList(); } if (controlsCount.Count == 0) { return -1; } if (controlsCount.Count > 0) { foreach (var item in controlsCount) { sum += item.Count; } result = sum / controlsCount.Count; } return result; } public void UpdateRating(Guid userID, decimal newCount, Guid itemId) { using (First43DBEntities context = new First43DBEntities()) { sf_rating currentRating = FindRatingByItemIDAndUserID(userID, itemId); currentRating.Count = newCount; context.Attach(currentRating); context.ObjectStateManager.ChangeObjectState(currentRating, System.Data.EntityState.Modified); context.SaveChanges(); } } public sf_rating FindRatingByItemIDAndUserID(Guid userID, Guid itemId) { sf_rating result = new sf_rating(); using (First43DBEntities context = new First43DBEntities()) { result = context.sf_rating.Where(r => r.UserId.Equals(userID) && r.ItemId.Equals(itemId)).FirstOrDefault(); } return result; } public Guid CreateRating(Guid itemId, Guid userID, decimal count) { var ratingUserId = Guid.Empty; using (First43DBEntities context = new First43DBEntities()) { sf_rating rating = new sf_rating(); rating.Count = count; rating.ItemId = itemId; rating.UserId = userID; context.sf_rating.AddObject(rating); context.SaveChanges(); ratingUserId = rating.UserId; } return ratingUserId; } }}using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ServiceModel;using System.ServiceModel.Web;using System.Runtime.Serialization;using Telerik.Sitefinity.Utilities.MS.ServiceModel.Web;using Telerik.Sitefinity.Web.Services;namespace RatingControl.Service{ [ServiceContract] interface IRatingService { [WebHelp(Comment = "Requests the rating sum, passing content item ID and current user rating if such")] [WebInvoke(Method = "POST", UriTemplate = "/RequestRating", ResponseFormat = WebMessageFormat.Json, BodyStyle=WebMessageBodyStyle.Wrapped)] [OperationContract] RatingSerialized RequestRating(string itemID, string userRating); } [DataContract] public class RatingSerialized { [DataMember] public Guid RatingId { get; set; } [DataMember] public decimal RateSum { get; set; } [DataMember] public decimal UserRate { get; set; } [DataMember] public Guid ItemId { get; set; } [DataMember] public Guid UserID { get; set; } }}blgManager.Provider.SuppressSecurityChecks = true;using System.Text;using System.ServiceModel;using System.ServiceModel.Activation;using RatingControl.DataModel;using RatingControl.DataManager;using Telerik.Sitefinity.Security;using Telerik.Sitefinity.Blogs.Model;using Telerik.Sitefinity;using Telerik.Sitefinity.Model;using Telerik.Sitefinity.Modules.Blogs;namespace RatingControl.Service{ [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] class RatingService : IRatingService { private static RatingManager manager = new RatingManager(); public RatingSerialized RequestRating(string itemID, string userRating) { Guid itemIdGuid = Guid.Parse(itemID); decimal userRatingParsed = decimal.Parse(userRating); Guid currentUserId = SecurityManager.GetCurrentUser().UserId; RatingSerialized serialized = new RatingSerialized(); if (currentUserId != Guid.Empty) { sf_rating rating = manager.FindRatingByItemIDAndUserID(currentUserId, itemIdGuid); if (rating == null) { manager.CreateRating(itemIdGuid, currentUserId, userRatingParsed); rating = manager.FindRatingByItemIDAndUserID(currentUserId, itemIdGuid); AddBlogFieldsValue(itemIdGuid, userRatingParsed); } else { UpdateBlogFieldsValue(itemIdGuid, userRatingParsed, rating.Count); manager.UpdateRating(currentUserId, userRatingParsed, itemIdGuid); } serialized = TransformToSerialized(rating, manager.GetCurrentRatingOfItem(itemIdGuid)); } else { serialized = TransformToSerialized(null, manager.GetCurrentRatingOfItem(itemIdGuid)); } return serialized; } private RatingSerialized TransformToSerialized(sf_rating rating, decimal sum) { RatingSerialized serialized = new RatingSerialized(); if (rating != null) { serialized.ItemId = rating.ItemId; serialized.UserID = rating.UserId; serialized.UserRate = rating.Count; } serialized.RateSum = sum; return serialized; } public void UpdateBlogFieldsValue(Guid blogPostId, decimal ratingValue, decimal oldValue) { BlogsManager blgManager = BlogsManager.GetManager(); bool securityChecksSettings = blgManager.Provider.SuppressSecurityChecks; blgManager.Provider.SuppressSecurityChecks = true; var post1 = blgManager.GetBlogPost(blogPostId); var master = blgManager.GetBlogPost(post1.OriginalContentId); decimal ratingSum = decimal.Parse(DataExtensions.GetValue<string>(master, "RatingSum")); int ratingCount = int.Parse(DataExtensions.GetValue<string>(master, "RatingCount")); ratingSum -= oldValue; ratingSum += ratingValue; decimal ratingResult = ratingSum / ratingCount; DataExtensions.SetValue(master, "RatingSum", ratingSum); DataExtensions.SetValue(master, "RatingCount", ratingCount); DataExtensions.SetValue(master, "RatingResult", ratingResult); blgManager.Publish(master); blgManager.SaveChanges(); blgManager.Provider.SuppressSecurityChecks = securityChecksSettings; } public void AddBlogFieldsValue(Guid blogPostId, decimal ratingValue) { BlogsManager blgManager = BlogsManager.GetManager(); bool securityChecksSettings = blgManager.Provider.SuppressSecurityChecks; blgManager.Provider.SuppressSecurityChecks = true; var post1 = blgManager.GetBlogPost(blogPostId); var master = blgManager.GetBlogPost(post1.OriginalContentId); decimal ratingSum = decimal.Parse(DataExtensions.GetValue<string>(master, "RatingSum")); int ratingCount = int.Parse(DataExtensions.GetValue<string>(master, "RatingCount")); ratingCount++; ratingSum += ratingValue; decimal ratingResult = ratingSum / ratingCount; DataExtensions.SetValue(master, "RatingSum", ratingSum); DataExtensions.SetValue(master, "RatingCount", ratingCount); DataExtensions.SetValue(master, "RatingResult", ratingResult); blgManager.Publish(master); blgManager.SaveChanges(); blgManager.Provider.SuppressSecurityChecks = securityChecksSettings; } }}<%@ ServiceHost Language="C#" Debug="false" Service="RatingControl.Service.RatingService" Factory="Telerik.Sitefinity.Web.Services.WcfHostFactory" %>function ServiceSucceeded(result) {// Here you can add code that makes use of the returned object }All properties of the returned object that we earlier created can be accessed through result.RatingSum (for example)
<%@ Control Language="C#" %><%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.ContentUI" Assembly="Telerik.Sitefinity" %><%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.Comments" Assembly="Telerik.Sitefinity" %><%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %><%@ Register TagPrefix="sfPanel" Namespace="SitefinityWebApp.Controls" Assembly="SitefinityWebApp" %><%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.PublicControls.BrowseAndEdit" Assembly="Telerik.Sitefinity" %><%@ Register TagPrefix="telerik" Namespace="Telerik.Web.UI" Assembly="Telerik.Web.UI" %><%@ Import Namespace="Telerik.Sitefinity" %><%@ Import Namespace="Telerik.Sitefinity.Model" %><%@ Import Namespace="Telerik.Sitefinity.Blogs.Model" %><%@ Import Namespace="Telerik.Sitefinity.Modules.Blogs" %><style type="text/css"> .label { visibility: hidden; }</style><script type="text/javascript">var Type;var Url;var Data;var ContentType;var DataType;var ProcessData;var method;function CallService() { $.ajax({ type: Type, url: Url, data: Data, contentType: ContentType, dataType: DataType, processdata: ProcessData, success: function(msg) { ServiceSucceeded(msg); }, error: ServiceFailed// When Service call fails }); }function ServiceSucceeded(result) {// Here you can add code that makes use of the returned object } function ServiceFailed(result) { alert('Service call failed: ' + result.status + '' + result.statusText); Type = null; Url = null; Data = null; ContentType = null; DataType = null; ProcessData = null;} function CallRequestRatingService(itemId, userRating) { Type = "Post"; Url = "http://localhost:42093/Sitefinity/Services/Rating/RatingService.svc/RequestRating"; var msg2 = {"itemID": itemId, "userRating": userRating}; Data = JSON.stringify(msg2); ContentType = "application/json; charset=utf-8"; DataType = "json"; Processdata = true; method = "RequestRating"; CallService(); } function OnClientRated(obj, args) {var currentValue = obj.get_value();var ratingID = obj.get_id()var parsedID = '#' + ratingID;var label = $(parsedID).siblings('.label');var id = label.text(); CallRequestRatingService(id, currentValue); }</script><telerik:RadListView ID="Repeater" ItemPlaceholderID="ItemsContainer" runat="server" EnableEmbeddedSkins="false" EnableEmbeddedBaseStylesheet="false"> <LayoutTemplate> <sf:ContentBrowseAndEditToolbar ID="MainBrowseAndEditToolbar" runat="server" Mode="Add"> </sf:ContentBrowseAndEditToolbar> <ul class="sfpostsList sfpostListTitleDateContent"> <asp:PlaceHolder ID="ItemsContainer" runat="server" /> </ul> </LayoutTemplate> <ItemTemplate> <li class="sfpostListItem"> <h2 class="sfpostTitle"> <sf:DetailsViewHyperLink ID="DetailsViewHyperLink1" TextDataField="Title"ToolTipDataField="Description" runat="server" /> </h2> <div class="sfpostAuthorAndDate"> <asp:Literal ID="Literal2" Text="<%$ Resources:Labels, By %>" runat="server" /> <sf:PersonProfileView ID="PersonProfileView1" runat="server" /> <sf:FieldListView ID="PostDate" runat="server" Format=" | {PublicationDate.ToLocal():MMM dd, yyyy}" /> </div> <sf:FieldListView ID="PostContent" runat="server" Text="{0}" Properties="Content" WrapperTagName="div" WrapperTagCssClass="sfpostContent" /> <sf:CommentsBox ID="itemCommentsLink" runat="server" CssClass="sfpostCommentsCount" /> <br /><asp:Label runat="server" Text="Item rating"></asp:Label> <telerik:RadRating CssClass="rating" ID="RadRating1" runat="server" ReadOnly="true" ItemCount="7"Skin="Sitefinity" Orientation="Horizontal" Value='<%# decimal.Parse(((BlogPost)Container.DataItem).GetValue<string>("RatingResult")) %>' ></telerik:RadRating> <asp:Label runat="server" Text="Your rating"></asp:Label> <telerik:RadRating ID="RadRating3" runat="server" Precision="Exact" SelectionMode="Continuous"ItemCount="7" OnClientRated="OnClientRated" Skin="Sitefinity"> </telerik:RadRating><asp:Label CssClass="label" runat="server" Text='<%# Eval("Id")%>'></asp:Label> <sf:ContentBrowseAndEditToolbar ID="BrowseAndEditToolbar" runat="server" Mode="Edit,Delete,Unpublish"> </sf:ContentBrowseAndEditToolbar> </li> </ItemTemplate></telerik:RadListView><sf:Pager ID="pager" runat="server"></sf:Pager>I hope this would be useful for you guys, if you have any questions, please feel free to ask.
Subscribe to get all the news, info and tutorials you need to build better business apps and sites