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
- public class BaseData<T> : IDynamicData
- {
- public EPiServer.Data.Identity Id { get; set; }
- public static IOrderedQueryable<T> Items
- {
- get
- {
- return Store.Items<T>();
- }
- }
- public static DynamicDataStore Store
- {
- get
- {
- return DynamicDataStoreFactory.Instance.GetCreateEnsureStore(typeof(T));
- }
- }
- }
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.
- public class PageInfo : BaseData<PageInfo>
- {
- [EPiServerDataIndex]
- public int PageID { get; set; }
- public int VisitedCount { get; set; }
- public DateTime LastChanged { get; set; }
- public DateTime LastVisited { get; set; }
- public bool IsDeleted { get; set; }
- public bool DontIncludeInMostRead { get; set; }
- public double Rating { get; set; }
- public int NrOfRatings { get; set; }
- public int NrOfLike { get; set; }
- public static void AddPage(PageData page, PageReference pageRef)
- {
- var row = GetMyRow(pageRef);
- if (page != null)
- {
- row.LastChanged = page.Changed;
- row.IsDeleted = page.IsDeleted;
- var prop = PropertyDataCollection.GetHandler("DontIncludeInMostRead", page.Property);
- row.DontIncludeInMostRead = (prop == null) ? false : (prop as PropertyBoolean).Boolean;
- }
- else
- {
- row.IsDeleted = true;
- }
- Store.Save(row);
- }
- public static void AddVisit(PageReference pageRef)
- {
- var row = GetMyRow(pageRef);
- row.VisitedCount++;
- Store.Save(row);
- }
- public static void ChangeRating(PageReference pageRef,double snitt,int nrOfRatings)
- {
- var row = GetMyRow(pageRef);
- row.Rating = snitt;
- row.NrOfRatings = nrOfRatings ;
- Store.Save(row);
- }
- public static void ChangeNrOfLike(PageReference pageRef, int nrOfLike)
- {
- var row = GetMyRow(pageRef);
- row.NrOfLike = nrOfLike;
- Store.Save(row);
- }
- public static PageInfo GetMyRow( PageReference pageRef)
- {
- var query = from item in Items where item.PageID == pageRef.ID select item;
- var item2 = new PageInfo() { PageID = pageRef.ID, VisitedCount = 0, LastVisited = DateTime.MinValue };
- foreach (var a in query)
- {
- item2 = a;
- break;
- }
- return item2;
- }
- }
Then I have a DDS class for each user and each page
- public class UserPageInfo : BaseData<UserPageInfo>
- {
- [EPiServerDataIndex]
- public int PageID { get; set; }
- [EPiServerDataIndex]
- public string UserName { get; set; }
- [EPiServerDataIndex]
- public Guid UserCookieGuid { get; set; }
- [EPiServerDataIndex]
- public DateTime VisitedDate { get; set; }
- public int NrOfVisits { get; set; }
- public int Rateing { get; set; }
- public bool Like { get; set; }
- public static void AddVisit(PageReference pageRef, bool addCount)
- {
- if (HttpContext.Current.Items["AddedTrackVisit"] == null)
- {
- var row = GetMyRow(pageRef);
- if (addCount)
- row.NrOfVisits++;
- row.VisitedDate = DateTime.Now;
- Store.Save(row);
- if (addCount)
- PageInfo.AddVisit(pageRef);
- HttpContext.Current.Items["AddedTrackVisit"] = true;
- }
- }
- public static int GetRating(PageReference pageRef)
- {
- var row = GetMyRow(pageRef);
- return row.Rateing;
- }
- public static void ChangeRating(PageReference pageRef, int rating)
- {
- var row = GetMyRow(pageRef);
- row.Rateing = rating;
- Store.Save(row);
- var query = from item in Items where item.PageID == pageRef.ID && item.Rateing > 0 select item.Rateing;
- var result = query.ToArray();
- var snitt =result.Length==0?0: result.Average();
- var count = result.Length;
- PageInfo.ChangeRating(pageRef, snitt, count);
- }
- public static UserPageInfo GetMyRow(PageReference pageRef)
- {
- var userName = GetUserName();
- var userCookieGuid = GetUserGuid(userName);
- var query = from item in Items
- where
- item.PageID == pageRef.ID
- select item;
- if (!string.IsNullOrEmpty(userName))
- query = from item2 in query where item2.UserName == userName select item2;
- else
- query = from item2 in query where item2.UserCookieGuid == userCookieGuid select item2;
- var row = new UserPageInfo() { PageID = pageRef.ID, NrOfVisits = 0 };
- foreach (var item in query)
- {
- row = item;
- break;
- }
- row.UserName = userName;
- row.UserCookieGuid = userCookieGuid;
- return row;
- }
- private static Guid GetUserGuid(string userName)
- {
- Guid userCookieGuid = Guid.Empty;
- if (string.IsNullOrEmpty(userName) && HttpContext.Current != null)
- {
- string guidString = GetCookieValue("UserCookieString");
- if (string.IsNullOrEmpty(guidString) || !IsGuid(guidString))
- {
- userCookieGuid = Guid.NewGuid();
- SetCookieValue("UserCookieString", userCookieGuid.ToString());
- }
- else
- {
- userCookieGuid = new Guid(guidString);
- }
- }
- return userCookieGuid;
- }
- private static string GetUserName()
- {
- string userName = "";
- if (EPiServer.Security.PrincipalInfo.CurrentPrincipal.Identity.IsAuthenticated)
- {
- userName = EPiServer.Security.PrincipalInfo.CurrentPrincipal.Identity.Name;
- }
- return userName;
- }
- public static string GetCookieValue(string key)
- {
- if (HttpContext.Current.Items["CookieTemp_" + key] != null)
- return (string)HttpContext.Current.Items["CookieTemp_" + key];
- string result = "";
- HttpCookie storeCookie = HttpContext.Current.Request.Cookies[key];
- if (storeCookie != null)
- {
- HttpContext.Current.Items["CookieTemp_" + key] = storeCookie.Value;
- // HttpContext.Current.Items.Add("CookieTemp_" + key,storeCookie.Value);
- result = storeCookie.Value;
- }
- //Check if user is logged on, if value exists there use that
- return result;
- }
- public static string SetCookieValue(string key, string val)
- {
- HttpContext.Current.Items["CookieTemp_" + key] = val;
- HttpContext.Current.Response.Cookies.Set(new HttpCookie(key, val));
- return val;
- }
- 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);
- private static bool IsGuid(string candidate)
- {
- if (candidate != null)
- {
- if (isGuid.IsMatch(candidate))
- {
- return true;
- }
- }
- return false;
- }
- }
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
- public class FriendlyUrlRewriteWithPageVisited : FriendlyUrlRewriteProvider
- {
- public override bool ConvertToInternal(UrlBuilder url, out object internalObject)
- {
- var result = base.ConvertToInternal(url, out internalObject);
- if (internalObject != null && internalObject is PageReference)
- UserPageInfo.AddVisit(internalObject as PageReference, true);
- return result;
- }
- }
or you can add a HttpModule in the web.config like this
- <system.webServer>
- <modules runAllManagedModulesForAllRequests="true">
- <add name="InitializationModule" type="EPiServer.Framework.Initialization.InitializationModule, EPiServer.Framework" preCondition="managedHandler" />
- <add name="FirstBeginRequestModule" type="EPiServer.Web.InitializationModule, EPiServer" preCondition="managedHandler" />
- <add name="Initializer" type="EPiServer.Scheduler.Initializer, EPiServer.Scheduler" preCondition="managedHandler" />
- <add name="WorkflowRuntime" type="EPiServer.WorkflowFoundation.WorkflowSystem, EPiServer.WorkflowFoundation" preCondition="managedHandler" />
- <add name="UrlRewriteModule" type="EPiServer.Web.UrlRewriteModule, EPiServer" preCondition="managedHandler" />
- <add name="ShellRoutingModule" type="EPiServer.Shell.Web.Routing.ShellRoutingModule, EPiServer.Shell" />
- <add name="TrackVisits" type="Itera.HttpModules.TrackVisits, Itera.TrackPages"/>
- </modules>
the code looks like this
- public class TrackVisits : IHttpModule
- {
- #region IHttpModule Members
- public void Dispose() {}
- public void Init(HttpApplication context)
- {
- context.BeginRequest += new EventHandler(context_BeginRequest);
- }
- void context_BeginRequest(object sender, EventArgs e)
- {
- if (HttpContext.Current != null && HttpContext.Current.Request["id"] != null)
- {
- var pageRef = PageReference.Parse(HttpContext.Current.Request["id"]);
- if (pageRef.WorkID==0)
- UserPageInfo.AddVisit(pageRef, true);
- }
- }
- #endregion
- }
Then we are good to go to make a user control.
If we have some markup like this
- <asp:DropDownList ID="MyRateing" runat="server"
- onselectedindexchanged="MyRateing_SelectedIndexChanged" AutoPostBack="true">
- <asp:ListItem Value="" Text="" />
- <asp:ListItem Value="1" Text="1" />
- <asp:ListItem Value="2" Text="2" />
- <asp:ListItem Value="3" Text="3" />
- <asp:ListItem Value="4" Text="4" />
- <asp:ListItem Value="5" Text="5" />
- </asp:DropDownList><br />
- <asp:CheckBox ID="ILike" runat="server" Text="I like"AutoPostBack="true"
- oncheckedchanged="ILike_CheckedChanged" />
- <asp:Literal ID="SnittRating" runat="server" />
with some code behind like this
- public partial class Rate : EPiServer.UserControlBase
- {
- protected void Page_Load(object sender, EventArgs e)
- {
- if (!IsPostBack)
- {
- var myRow = UserPageInfo.GetMyRow(CurrentPage.PageLink);
- if (myRow.Rateing > 0)
- MyRateing.SelectedValue = myRow.Rateing.ToString();
- else
- MyRateing.SelectedValue = "";
- ILike.Checked = myRow.Like;
- }
- }
- protected void MyRateing_SelectedIndexChanged(object sender, EventArgs e)
- {
- if (MyRateing.SelectedValue != "")
- UserPageInfo.ChangeRating(CurrentPage.PageLink, int.Parse(MyRateing.SelectedValue));
- else
- UserPageInfo.ChangeRating(CurrentPage.PageLink, 0);
- }
- protected override void OnPreRender(EventArgs e)
- {
- var row=PageInfo.GetMyRow(CurrentPage.PageLink);
- 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;
- base.OnPreRender(e);
- }
- protected void ILike_CheckedChanged(object sender, EventArgs e)
- {
- UserPageInfo.ChangeLike(CurrentPage.PageLink, ILike.Checked);
- }
- }
we will get this result
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
Comments