November Happy Hour will be moved to Thursday December 5th.

Anders Hattestad
Feb 25, 2011
  6212
(3 votes)

Rating/Like & Nr of visits saved in DDS per user

There are several ways of implementing these kinds of functions. But I have started to like Dynamic Data Store a lot, and are using it in all sorts of ways.

There are some issues regarding these methods that should be considered. I think that you should only be able to rate/like a page once. To archive this we need to track each users values.

If the user is logged on this is not a problem, since we have a username, but if its a anonymous user we need to track the user with a cookie. One key note here is to send a cookie to the user, but also add it to the HttpContext.Current.Items. When you read the cookie you check the Items first, since the cookie doesn’t exit yet.

Enough talk, lets look at some code.

My DDS classes are inheriting from a base class that have easy access to some useful methods

Code Snippet
  1. public class BaseData<T> : IDynamicData
  2. {
  3.     public EPiServer.Data.Identity Id { get; set; }
  4.  
  5.     public static IOrderedQueryable<T> Items
  6.     {
  7.         get
  8.         {
  9.             return Store.Items<T>();
  10.         }
  11.     }
  12.     public static DynamicDataStore Store
  13.     {
  14.         get
  15.         {
  16.             return DynamicDataStoreFactory.Instance.GetCreateEnsureStore(typeof(T));
  17.         }
  18.     }
  19.  
  20. }

I made myself one DDS class to store the aggregated data for each page. (I’m not handling languages since these values are not languages specific)

I’m updating LastChange by attaching myself to the PagePublished event. I’m also tracking when a page is deleted, and update the IsDeleted flag.

Code Snippet
  1. public class PageInfo : BaseData<PageInfo>
  2. {
  3.     [EPiServerDataIndex]
  4.     public int PageID { get; set; }
  5.         
  6.     public int VisitedCount { get; set; }
  7.     public DateTime LastChanged { get; set; }
  8.     public DateTime LastVisited { get; set; }
  9.     public bool IsDeleted { get; set; }
  10.     public bool DontIncludeInMostRead { get; set; }
  11.  
  12.     public double Rating { get; set; }
  13.     public int NrOfRatings { get; set; }
  14.  
  15.     public int NrOfLike { get; set; }
  16.  
  17.     public static void AddPage(PageData page, PageReference pageRef)
  18.     {
  19.         var row = GetMyRow(pageRef);
  20.         if (page != null)
  21.         {
  22.             row.LastChanged = page.Changed;
  23.             row.IsDeleted = page.IsDeleted;
  24.             var prop = PropertyDataCollection.GetHandler("DontIncludeInMostRead", page.Property);
  25.             row.DontIncludeInMostRead = (prop == null) ? false : (prop as PropertyBoolean).Boolean;
  26.         }
  27.         else
  28.         {
  29.             row.IsDeleted = true;
  30.         }
  31.         Store.Save(row);
  32.     }
  33.     public static void AddVisit(PageReference pageRef)
  34.     {
  35.         var row = GetMyRow(pageRef);
  36.         row.VisitedCount++;
  37.         Store.Save(row);
  38.     }
  39.     public static void ChangeRating(PageReference pageRef,double snitt,int nrOfRatings)
  40.     {
  41.         var row = GetMyRow(pageRef);
  42.         row.Rating = snitt;
  43.         row.NrOfRatings = nrOfRatings ;
  44.         Store.Save(row);
  45.     }
  46.     public static void ChangeNrOfLike(PageReference pageRef, int nrOfLike)
  47.     {
  48.         var row = GetMyRow(pageRef);
  49.         row.NrOfLike = nrOfLike;
  50.         Store.Save(row);
  51.     }
  52.     public static PageInfo GetMyRow( PageReference pageRef)
  53.     {
  54.         var query = from item in Items where item.PageID == pageRef.ID select item;
  55.         var item2 = new PageInfo() { PageID = pageRef.ID, VisitedCount = 0, LastVisited = DateTime.MinValue };
  56.         foreach (var a in query)
  57.         {
  58.             item2 = a;
  59.             break;
  60.         }
  61.             
  62.         return item2;
  63.     }
  64.  
  65.        
  66.         
  67.  
  68. }

Then I have a DDS class for each user and each page

Code Snippet
  1. public class UserPageInfo : BaseData<UserPageInfo>
  2. {
  3.     [EPiServerDataIndex]
  4.     public int PageID { get; set; }
  5.     [EPiServerDataIndex]
  6.     public string UserName { get; set; }
  7.     [EPiServerDataIndex]
  8.     public Guid UserCookieGuid { get; set; }
  9.     [EPiServerDataIndex]
  10.     public DateTime VisitedDate { get; set; }
  11.  
  12.     public int NrOfVisits { get; set; }
  13.     public int Rateing { get; set; }
  14.     public bool Like { get; set; }
  15.  
  16.     public static void AddVisit(PageReference pageRef, bool addCount)
  17.     {
  18.         if (HttpContext.Current.Items["AddedTrackVisit"] == null)
  19.         {
  20.             var row = GetMyRow(pageRef);
  21.             if (addCount)
  22.                 row.NrOfVisits++;
  23.             row.VisitedDate = DateTime.Now;
  24.             Store.Save(row);
  25.             if (addCount)
  26.                 PageInfo.AddVisit(pageRef);
  27.             HttpContext.Current.Items["AddedTrackVisit"] = true;
  28.         }
  29.     }
  30.     public static int GetRating(PageReference pageRef)
  31.     {
  32.         var row = GetMyRow(pageRef);
  33.         return row.Rateing;
  34.     }
  35.     public static void ChangeRating(PageReference pageRef, int rating)
  36.     {
  37.         var row = GetMyRow(pageRef);
  38.         row.Rateing = rating;
  39.         Store.Save(row);
  40.         var query = from item in Items where item.PageID == pageRef.ID && item.Rateing > 0 select item.Rateing;
  41.         var result = query.ToArray();
  42.         var snitt =result.Length==0?0: result.Average();
  43.         var count = result.Length;
  44.         PageInfo.ChangeRating(pageRef, snitt, count);
  45.     }
  46.     public static UserPageInfo GetMyRow(PageReference pageRef)
  47.     {
  48.         var userName = GetUserName();
  49.         var userCookieGuid = GetUserGuid(userName);
  50.         var query = from item in Items
  51.                     where
  52.                         item.PageID == pageRef.ID
  53.                     select item;
  54.         if (!string.IsNullOrEmpty(userName))
  55.             query = from item2 in query where item2.UserName == userName select item2;
  56.         else
  57.             query = from item2 in query where item2.UserCookieGuid == userCookieGuid select item2;
  58.  
  59.         var row = new UserPageInfo() { PageID = pageRef.ID, NrOfVisits = 0 };
  60.         foreach (var item in query)
  61.         {
  62.             row = item;
  63.             break;
  64.         }
  65.         row.UserName = userName;
  66.         row.UserCookieGuid = userCookieGuid;
  67.         return row;
  68.     }
  69.  
  70.     private static Guid GetUserGuid(string userName)
  71.     {
  72.         Guid userCookieGuid = Guid.Empty;
  73.         if (string.IsNullOrEmpty(userName) && HttpContext.Current != null)
  74.         {
  75.  
  76.             string guidString = GetCookieValue("UserCookieString");
  77.             if (string.IsNullOrEmpty(guidString) || !IsGuid(guidString))
  78.             {
  79.                 userCookieGuid = Guid.NewGuid();
  80.                 SetCookieValue("UserCookieString", userCookieGuid.ToString());
  81.             }
  82.             else
  83.             {
  84.                 userCookieGuid = new Guid(guidString);
  85.             }
  86.  
  87.         }
  88.         return userCookieGuid;
  89.     }
  90.  
  91.     private static string GetUserName()
  92.     {
  93.         string userName = "";
  94.         if (EPiServer.Security.PrincipalInfo.CurrentPrincipal.Identity.IsAuthenticated)
  95.         {
  96.             userName = EPiServer.Security.PrincipalInfo.CurrentPrincipal.Identity.Name;
  97.         }
  98.         return userName;
  99.     }
  100.  
  101.     public static string GetCookieValue(string key)
  102.     {
  103.         if (HttpContext.Current.Items["CookieTemp_" + key] != null)
  104.             return (string)HttpContext.Current.Items["CookieTemp_" + key];
  105.  
  106.         string result = "";
  107.         HttpCookie storeCookie = HttpContext.Current.Request.Cookies[key];
  108.  
  109.         if (storeCookie != null)
  110.         {
  111.             HttpContext.Current.Items["CookieTemp_" + key] = storeCookie.Value;
  112.             // HttpContext.Current.Items.Add("CookieTemp_" + key,storeCookie.Value);
  113.             result = storeCookie.Value;
  114.         }
  115.         //Check if user is logged on, if value exists there use that
  116.            
  117.  
  118.         return result;
  119.     }
  120.  
  121.  
  122.     public static string SetCookieValue(string key, string val)
  123.     {
  124.             
  125.         HttpContext.Current.Items["CookieTemp_" + key] = val;
  126.         HttpContext.Current.Response.Cookies.Set(new HttpCookie(key, val));
  127.         return val;
  128.     }
  129.     private static Regex isGuid = new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);
  130.  
  131.     private static bool IsGuid(string candidate)
  132.     {
  133.         if (candidate != null)
  134.         {
  135.             if (isGuid.IsMatch(candidate))
  136.             {
  137.                 return true;
  138.             }
  139.         }
  140.  
  141.         return false;
  142.     }
  143.  
  144.  
  145. }

If a user is not logged on I create a cookie and use that as the lookup.

Then I need to add a page count for each visited to the page. That is easy when the user are logged on, but if not an the caching is enabled we need to do another way. I have made one HttpModule, and one UrlRewriter. The rewriter is called for each page request even when the page is cached. So if you are using your own rewriter you only need to add this code

anonymous

Code Snippet
  1. public class FriendlyUrlRewriteWithPageVisited : FriendlyUrlRewriteProvider
  2. {
  3.     public override bool ConvertToInternal(UrlBuilder url, out object internalObject)
  4.     {
  5.         var result = base.ConvertToInternal(url, out internalObject);
  6.         if (internalObject != null && internalObject is PageReference)
  7.             UserPageInfo.AddVisit(internalObject as PageReference, true);
  8.         return result;
  9.     }
  10. }

or you can add a HttpModule in the web.config like this

Code Snippet
  1. <system.webServer>
  2.     <modules runAllManagedModulesForAllRequests="true">
  3.       <add name="InitializationModule" type="EPiServer.Framework.Initialization.InitializationModule, EPiServer.Framework" preCondition="managedHandler" />
  4.       <add name="FirstBeginRequestModule" type="EPiServer.Web.InitializationModule, EPiServer" preCondition="managedHandler" />
  5.       <add name="Initializer" type="EPiServer.Scheduler.Initializer, EPiServer.Scheduler" preCondition="managedHandler" />
  6.       <add name="WorkflowRuntime" type="EPiServer.WorkflowFoundation.WorkflowSystem, EPiServer.WorkflowFoundation" preCondition="managedHandler" />
  7.       <add name="UrlRewriteModule" type="EPiServer.Web.UrlRewriteModule, EPiServer" preCondition="managedHandler" />
  8.       <add name="ShellRoutingModule" type="EPiServer.Shell.Web.Routing.ShellRoutingModule, EPiServer.Shell" />
  9.       <add name="TrackVisits" type="Itera.HttpModules.TrackVisits, Itera.TrackPages"/>
  10.     </modules>

the code looks like this

Code Snippet
  1. public class TrackVisits : IHttpModule
  2. {
  3.     #region IHttpModule Members
  4.     public void Dispose()    {}
  5.  
  6.     public void Init(HttpApplication context)
  7.     {
  8.         context.BeginRequest += new EventHandler(context_BeginRequest);
  9.     }
  10.  
  11.     void context_BeginRequest(object sender, EventArgs e)
  12.     {
  13.         if (HttpContext.Current != null && HttpContext.Current.Request["id"] != null)
  14.         {
  15.             var pageRef = PageReference.Parse(HttpContext.Current.Request["id"]);
  16.             if (pageRef.WorkID==0)
  17.                 UserPageInfo.AddVisit(pageRef, true);
  18.         }
  19.     }
  20.     #endregion
  21. }

Then we are good to go to make a user control.

If we have some markup like this

Code Snippet
  1. <asp:DropDownList ID="MyRateing" runat="server"
  2.     onselectedindexchanged="MyRateing_SelectedIndexChanged" AutoPostBack="true">
  3.     <asp:ListItem Value="" Text="" />
  4.     <asp:ListItem Value="1" Text="1" />
  5.     <asp:ListItem Value="2" Text="2" />
  6.     <asp:ListItem Value="3" Text="3" />
  7.     <asp:ListItem Value="4" Text="4" />
  8.     <asp:ListItem Value="5" Text="5" />
  9. </asp:DropDownList><br />
  10.   <asp:CheckBox ID="ILike" runat="server" Text="I like"AutoPostBack="true"
  11.     oncheckedchanged="ILike_CheckedChanged" />
  12. <asp:Literal ID="SnittRating" runat="server" />

with some code behind like this

Code Snippet
  1. public partial class Rate : EPiServer.UserControlBase
  2. {
  3.     protected void Page_Load(object sender, EventArgs e)
  4.     {
  5.         if (!IsPostBack)
  6.         {
  7.             var myRow = UserPageInfo.GetMyRow(CurrentPage.PageLink);
  8.             if (myRow.Rateing > 0)
  9.                 MyRateing.SelectedValue = myRow.Rateing.ToString();
  10.             else
  11.                 MyRateing.SelectedValue = "";
  12.             ILike.Checked = myRow.Like;
  13.         }
  14.     }
  15.  
  16.     protected void MyRateing_SelectedIndexChanged(object sender, EventArgs e)
  17.     {
  18.         if (MyRateing.SelectedValue != "")
  19.             UserPageInfo.ChangeRating(CurrentPage.PageLink, int.Parse(MyRateing.SelectedValue));
  20.         else
  21.             UserPageInfo.ChangeRating(CurrentPage.PageLink, 0);
  22.     }
  23.     protected override void OnPreRender(EventArgs e)
  24.     {
  25.         var row=PageInfo.GetMyRow(CurrentPage.PageLink);
  26.         SnittRating.Text = "avg=" + row.Rating + "<br />nr of users rated=" + row.NrOfRatings + "<br />nr of users visited=" + row.VisitedCount+"<br />Nr of likes="+row.NrOfLike;
  27.         base.OnPreRender(e);
  28.     }
  29.  
  30.     protected void ILike_CheckedChanged(object sender, EventArgs e)
  31.     {
  32.         UserPageInfo.ChangeLike(CurrentPage.PageLink, ILike.Checked);
  33.  
  34.     }
  35. }

we will get this result

image

not very designish thou, but the concept is clear.

As you may have noticed, I update the PageInfo class with the aggregated values from the UserPageInfo class. This is because you can’t join 2 DDS tables. I think this is scheduled for CMS 7, but while we wait this is one solution. Another solution is of course to use sql against the views.

The code is available in the code section here

Feb 25, 2011

Comments

Please login to comment.
Latest blogs
Optimizely SaaS CMS + Coveo Search Page

Short on time but need a listing feature with filters, pagination, and sorting? Create a fully functional Coveo-powered search page driven by data...

Damian Smutek | Nov 21, 2024 | Syndicated blog

Optimizely SaaS CMS DAM Picker (Interim)

Simplify your Optimizely SaaS CMS workflow with the Interim DAM Picker Chrome extension. Seamlessly integrate your DAM system, streamlining asset...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Optimizely CMS Roadmap

Explore Optimizely CMS's latest roadmap, packed with developer-focused updates. From SaaS speed to Visual Builder enhancements, developer tooling...

Andy Blyth | Nov 21, 2024 | Syndicated blog

Set Default Culture in Optimizely CMS 12

Take control over culture-specific operations like date and time formatting.

Tomas Hensrud Gulla | Nov 15, 2024 | Syndicated blog

I'm running Optimizely CMS on .NET 9!

It works 🎉

Tomas Hensrud Gulla | Nov 12, 2024 | Syndicated blog

Recraft's image generation with AI-Assistant for Optimizely

Recraft V3 model is outperforming all other models in the image generation space and we are happy to share: Recraft's new model is now available fo...

Luc Gosso (MVP) | Nov 8, 2024 | Syndicated blog