Database Extension

Topics: ASP.NET 2.0, Controls
Apr 15, 2009 at 3:28 AM
Edited Apr 15, 2009 at 4:16 AM
I created a new database field on the be_Posts table called Clickers which basically matches the Rating field.  I have this field representing how many times a user has clicked on a link associated with a Post.  Right now it is done very similar to rating.  I created a new ClickerHandler class that is registered as an IHttpHandler and it is working fine.  I also updated the Post class so that it will perform the proper insert, select and update commands to handle the new database field.

I also have a new web control which displays a list of posts and sizes their titles by a weighted list similar to the TagCloud.  Everything seems to be displaying great and the data is being stored, however it appears that any time that I make any modifications to PopularPosts.cs and upload it to the web server all of the posts have their Clickers field reset to zero.  Here is my code file for PopularPosts.cs:
Edit: I am running off of 1.5 RC1.


   1:  using System.Collections.Generic;
   2:  using System.Web.UI;
   3:  using BlogEngine.Core;
   4:   
   5:  namespace Controls
   6:  {
   7:   
   8:      /// <summary>
   9:      /// Renders a list of posts and sizes their titles by a weighted list.
  10:      /// </summary>
  11:      public class PopularPosts : Control
  12:      {
  13:   
  14:          /// <summary>
  15:          /// Stores a constant string that contains the link for a post.
  16:          /// </summary>
  17:          private const string LINK = "<a href=\"{0}\" class=\"{1}\" onclick=\"javascript:BlogEngine.click('{2}');\" title=\"{3}\">{4}</a>";
  18:   
  19:          /// <summary>
  20:          /// Renders the control.
  21:          /// </summary>
  22:          /// <param name="p_htmlWriter">Inputs the html to write with.</param>
  23:          public override void RenderControl(HtmlTextWriter p_htmlWriter)
  24:          {
  25:              // Retrieve posts from the database.
  26:              List<Post> listByClickers = Post.Posts;
  27:              List<Post> listByDate = new List<Post>(listByClickers.ToArray());
  28:              Dictionary<Post, string> dictWeightedClasses = new Dictionary<Post, string>();
  29:   
  30:              // Sort the posts by clickers descending.
  31:              listByClickers.Sort
  32:                  (
  33:                  delegate(Post p1, Post p2) { return p2.Clickers.CompareTo(p1.Clickers); }
  34:                  );
  35:   
  36:              // Sort posts by date created descending.
  37:              listByDate.Sort
  38:                  (
  39:                  delegate(Post p1, Post p2) { return p2.DateCreated.CompareTo(p1.DateCreated); }
  40:                  );
  41:   
  42:              // Store the total number of clicks.
  43:              int iClicks = 0;
  44:   
  45:              // Loop through each of the posts and calculate the total clickers.
  46:              foreach (Post post in listByClickers)
  47:                  iClicks += post.Clickers;
  48:   
  49:              // If there has been at least one clicker.
  50:              if (iClicks > 0)
  51:              {
  52:                  // Loop thorugh each of the posts and weight them.
  53:                  foreach (Post post in listByClickers)
  54:                  {
  55:                      // Calculate the weight of the post.
  56:                      double weight = ((double)post.Clickers / (double)iClicks) * 100;
  57:   
  58:                      // Assign the class by weight.
  59:                      if (weight >= 99)
  60:                          dictWeightedClasses.Add(post, "biggest");
  61:                      else if (weight >= 70)
  62:                          dictWeightedClasses.Add(post, "big");
  63:                      else if (weight >= 40)
  64:                          dictWeightedClasses.Add(post, "medium");
  65:                      else if (weight >= 20)
  66:                          dictWeightedClasses.Add(post, "small");
  67:                      else dictWeightedClasses.Add(post, "smallest");
  68:   
  69:                  }   // foreach (Post post in listByClickers)
  70:   
  71:                  p_htmlWriter.WriteLine(iClicks);
  72:   
  73:              }   // if (iClicks > 0)
  74:   
  75:              // Begin the UL tag.
  76:              p_htmlWriter.WriteLine("<ul id=\"tagcloud\" class=\"tagcloud\">");
  77:   
  78:              // Loop through the unordered posts.
  79:              foreach (Post post in listByDate)
  80:              {
  81:                  string strClass = string.Empty;
  82:   
  83:                  // Try and get the class.
  84:                  if (dictWeightedClasses.ContainsKey(post))
  85:                      strClass = dictWeightedClasses[post];
  86:   
  87:                  // Otherwise default to smallest.
  88:                  else strClass = "smallest";
  89:   
  90:                  // Begin the LI tag.
  91:                  p_htmlWriter.WriteLine("<li>");
  92:   
  93:                  // Render the link.
  94:                  p_htmlWriter.WriteLine
  95:                      (
  96:                      PopularPosts.LINK,
  97:                      "#" + post.Title,
  98:                      strClass,
  99:                      post.Id,
 100:                      "Post: " + post.Title,
 101:                      post.Title
 102:                      );
 103:   
 104:                  // End the LI tag.
 105:                  p_htmlWriter.WriteLine("</li>");
 106:   
 107:              }   // foreach (Post post in listByDate)
 108:   
 109:              // End the UL tag.
 110:              p_htmlWriter.WriteLine("</ul>");
 111:   
 112:          }   // public override void RenderControl(HtmlTextWriter p_htmlWriter)
 113:   
 114:      }   // public class PopularPosts : Control
 115:   
 116:  }   // namespace Controls
Coordinator
Apr 15, 2009 at 6:22 AM
I'm guessing the Clickers column in the be_Posts table isn't getting updated.  You might want to confirm whether the Clickers values in the database are getting updated before uploading PopularPosts.cs.

If you're updating the Clickers value in the Post object, the updated clickers value will be available in memory along with all the other Post data.  When you modify a file in the App_Code folder, data in memory gets cleared out and on the next request, the data is re-read from the database.

So my guess is that you're updating the clickers value for each post, but you are maybe not calling Post.Save() after each update to save changes to the database.  Another possibility is that you are calling Post.Save(), but the post isn't considered 'dirty' because the MarkChanged() method isn't called when the Clickers property is updated.  If the post isn't considered 'dirty', then nothing is saved to the DB when Post.Save() is called.  You can see how the MarkChanged() function works by looking at some of the other properties in the Post class -- the Author property, for example.
Apr 15, 2009 at 2:52 PM
Edited Apr 15, 2009 at 2:59 PM
So it turns out that I forgot to update the SELECT statement to retrieve the Clickers field, but still evaluated the next column in the database trying to update the post.Clickers value.

===========Ignore===========
I could have sworn that I had checked the database and it returned a value greater than zero, but I will have to check again. EDIT: I just logged into the page again and all my values were reset.  I clicked on one of my links and the size was visible changed so at least in memory it was increased.  I also did a query on the database and the post associated with the link I clicked had its Clickers field increased to one.

So here is my code for implementing the clickers functionality on the Post class:

   1:  private int _iClickers;
   2:   
   3:  /// <summary>
   4:  /// Gets or sets the number of clickers that have clicked on this link.
   5:  /// </summary>
   6:  public int Clickers
   7:  {
   8:      get { return this._iClickers; }
   9:      set
  10:      {
  11:          if (this._iClickers != value)
  12:              this.MarkChanged("Clickers");
  13:   
  14:          this._iClickers = value;
  15:   
  16:      }   // set
  17:  }   // public int Clickers
  18:   
  19:  /// <summary>
  20:  /// Increase the number of times this post has been viewed.
  21:  /// </summary>
  22:  public void Click()
  23:  {
  24:      // Increment the clickers.
  25:      this.Clickers++;
  26:   
  27:      // Update the data.
  28:      this.DataUpdate();
  29:   
  30:  }   // public void Click()

I also have this method implemented on my ClickerHandler:

   1:  /// <summary>
   2:  /// Processes the click request.
   3:  /// </summary>
   4:  /// <param name="context">Http context sent in.</param>
   5:  public void ProcessRequest(HttpContext context)
   6:  {
   7:      string strId = context.Request.QueryString["id"];
   8:   
   9:      // Ensure that the unique id is set and is valid.
  10:      if (strId != null && strId.Length == 36)
  11:      {
  12:   
  13:          // If the user has already clicked.
  14:          if (ClickerHandler.HasClicked(strId))
  15:          {
  16:              context.Response.Write("HASCLICKED");
  17:              context.Response.End();
  18:   
  19:          }   // if (ClickerHandler.HasClicked(strId))
  20:   
  21:          // Click the post.
  22:          Post post = Post.GetPost(new Guid(strId));
  23:          post.Click();
  24:          post.Save();
  25:   
  26:          // Set the cookie and proceed.
  27:          ClickerHandler.SetCookie(strId, context);
  28:          context.Response.Write("OK");
  29:          context.Response.End();
  30:   
  31:      }   // if (strId != null && strId.Length == 36)
  32:      
  33:      // Fail the contetxt.
  34:      context.Response.Write("FAIL");
  35:   
  36:  }   // public void ProcessRequest(HttpContext context)