The content you're reading is getting on in years
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.
View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.
Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.
Learn MoreSubscribe to get all the news, info and tutorials you need to build better business apps and sites
Progress collects the Personal Information set out in our Privacy Policy and the Supplemental Privacy notice for residents of California and other US States and uses it for the purposes stated in that policy.
You can also ask us not to share your Personal Information to third parties here: Do Not Sell or Share My Info
We see that you have already chosen to receive marketing materials from us. If you wish to change this at any time you may do so by clicking here.
Thank you for your continued interest in Progress. Based on either your previous activity on our websites or our ongoing relationship, we will keep you updated on our products, solutions, services, company news and events. If you decide that you want to be removed from our mailing lists at any time, you can change your contact preferences by clicking here.