An Alternative Archives Web Part to Solve the Pre-dated Posts Provisioning in the OOTB Blog Site Archives Web Part

imageThe OOB SharePoint 2010 site templates are very useful in different scenarios. The guys at Microsoft were very generous to spare some time and build a site template for blogs. After provisioning the site, I was so happy with it! but then stumbled on the fact that the Archives Web Part did not generate links for my old posts, apparently by design - after doing a decent search over forums - pre dated posts were not provisioned by the Blog Site Template Archives Web Part. I had to remove the Web Part and place a link to the All Posts view on the top link bar and called it Archives. On the long run, it did not sound like a good alternative, I had to have that Web Part.

  

I spent couple of hours today to code the Archives Web Part, and I can’t express how happy I am with it. The approach was very simple: I had to get all published posts; find out in which years the posts were authored; find out in which months the posts were authored, and finally push all this to a Tree View control.

After populating the Tree View nodes, I had to handle the SelectedNodeChanged event to redirect to the Date.aspx page and pass the correct StartDateTime and EndDateTime query string parameters. The query string structure is fairly easy, you needed to supply the time span parameters and the title. For example, to get all the posts in December 2011 the URL should look like this: https://www.sharepointstack.com/Lists/Posts/Date.aspx?StartDateTime=2011-12-01T00:00:00Z&EndDateTime=2012-01-01T02:00:00Z&LMY=December,%202011. Notice that the start and end times are passed in ISO8601 formats.

Web Part Download (WSP)

If you are not a technical blogger and just want to download the Web Part, click here. You don’t have to complete this post. On the other hand, if you are a SharePoint developer like me, get your fingers cracking for some code.

Web Part Development

I created a Visual Web Part. The Web Part had 3 controls:

    1: <SharePoint:CssRegistration 
    2:     Name="<% $SPUrl:~sitecollection/_layouts/styles/SharePointStack/SPBlogTemplate/SPBlogTemplate.css %>" 
    3:     After="CoreV4.css" runat="server"></SharePoint:CssRegistration>
    4: <div id="SPBlogContainer">
    5:     <div id="ArchiveSummaryTitle"><a id="ViewArchive" title="Click to view Archive" runat="server">Archives</a></div>
    6:     <asp:TreeView ID="ArchiveSummaryTree" runat="server" ExpandDepth="0" 
    7:         NodeIndent="0" onselectednodechanged="ArchiveSummaryTree_SelectedNodeChanged" ></asp:TreeView>
    8:     <asp:Literal ID="ArchiveSummaryErrors" runat="server"></asp:Literal>
    9: </div>

The markup is straight forward: I place a title, Tree View, and a Literal. I had to apply some CSS formatting to make sure that the Web Part looks like a SharePoint control. I created a CSS file and placed it under the STYLES mapped folder. The CSS helped me adjust the padding and text formatting.

    1: #ArchiveSummaryTitle
    2: {
    3:     color: #0072bc;
    4:     font-size: 1.2em;
    5:     font-weight: normal;
    6: }
    7:  
    8: #SPBlogContainer
    9: {
   10:     padding-left: 11px;
   11: }

Before moving to the code behind, I needed to create a class to help handle the posts using a Generic List. More details on this later on.

    1: namespace SharePointStack.SPBlogTemplate
    2: {
    3:     class SPBlogPost
    4:     {        
    5:         public SPBlogPost(string title, string month, string year)
    6:         {
    7:             postTitle = title;
    8:             publishingMonth = month;
    9:             publishingYear = year;
   10:         }
   11:  
   12:         private string postTitle;
   13:  
   14:         public string PostTitle
   15:         {
   16:             get { return postTitle; }
   17:             set { postTitle = value; }
   18:         }
   19:  
   20:         private string publishingMonth;
   21:  
   22:         public string PublishingMonth
   23:         {
   24:             get { return publishingMonth; }
   25:             set { publishingMonth = value; }
   26:         }
   27:  
   28:         private string publishingYear;
   29:  
   30:         public string PublishingYear
   31:         {
   32:             get { return publishingYear; }
   33:             set { publishingYear = value; }
   34:         }
   35:     }
   36: }

Now let’s get some Code Behind love Smile I commented wherever needed to explain the code as much as possible.

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Linq;
    4: using System.Web.Caching;
    5: using System.Web.UI;
    6: using System.Web.UI.WebControls;
    7: using Microsoft.SharePoint;
    8: using Microsoft.SharePoint.Utilities;
    9:  
   10: namespace SharePointStack.SPBlogTemplate.ArchiveSummary
   11: {
   12:     public partial class ArchiveSummaryUserControl : UserControl
   13:     {
   14:         static object _lock =  new object();
   15:         List<SPBlogPost> postsBuffer = new List<SPBlogPost>();
   16:         List<int> years = new List<int>();
   17:         string[] months = { "January", "February", "March", "April", "May", "June", "July", "August", 
   18:                               "September", "October", "November", "December" };
   19:         bool duplicates;
   20:  
   21:         protected void Page_Load(object sender, EventArgs e)
   22:         {
   23:             if (!Page.IsPostBack)
   24:             {
   25:                 try
   26:                 {
   27:                     //I chose "using" to automatically dispose SharePoint objects
   28:                     using (SPSite blogSiteCollection = SPContext.Current.Site)
   29:                     {
   30:                         using (SPWeb blogWeb = blogSiteCollection.OpenWeb(SPContext.Current.Web.ServerRelativeUrl))
   31:                         {
   32:                             SPList blogPosts = blogWeb.Lists["Posts"];
   33:  
   34:                             //Set the header link
   35:                             ViewArchive.HRef = blogPosts.DefaultViewUrl;
   36:  
   37:                             SPQuery queryPosts = new SPQuery();
   38:                             
   39:                             //CAML Query that returns only published posts and orders them by date
   40:                             queryPosts.Query = "<OrderBy><FieldRef Name='PublishedDate'/></OrderBy>" + 
   41:                                                 "<Where><Eq><FieldRef Name='_ModerationStatus' />" + 
   42:                                                 "<Value Type='ModStat'>0</Value></Eq></Where>";
   43:                             
   44:                             //Pull the query results directly from the Cache, we don't want to query
   45:                             //SharePoint everytime the Archives Summary Web Part runs. This practice increases 
   46:                             //the performance and comes very handy if the user is an active blogger.
   47:                             SPListItemCollection publishedPosts = (SPListItemCollection)Cache["PublishedPosts"];
   48:                             
   49:                             //If the query results are not avilable in the Cache, then we need to execute the query and
   50:                             //store the results in the Cache for the next request.
   51:                             //The Cache is set with Sliding Expiration of 1 day.
   52:                             if (publishedPosts == null)
   53:                             {
   54:                                 //Since SPWeb is not thread safe, we need to place a lock.
   55:                                 lock (_lock)
   56:                                 {
   57:                                     //Ensure that the data was not loaded by a concurrent thread while waiting for lock.
   58:                                     publishedPosts = blogPosts.GetItems(queryPosts);
   59:                                     Cache.Add("PublishedPosts", publishedPosts, null, Cache.NoAbsoluteExpiration, 
   60:                                         TimeSpan.FromDays(1), CacheItemPriority.High, null);
   61:                                 }
   62:                             }
   63:  
   64:                             //Load all published posts into the postsBuffer. The query results will not be available
   65:                             //outside the "using" block.
   66:                             foreach (SPListItem post in publishedPosts)
   67:                                 postsBuffer.Add(new SPBlogPost(post["Title"].ToString(), 
   68:                                     DateTime.Parse(post["PublishedDate"].ToString()).Month.ToString(), 
   69:                                     DateTime.Parse(post["PublishedDate"].ToString()).Year.ToString()));
   70:                         }
   71:                     }
   72:  
   73:                     //Provision years
   74:                     foreach (SPBlogPost post in postsBuffer)
   75:                         years.Add(int.Parse(post.PublishingYear));
   76:  
   77:                     //Make sure we only have distinct years that are sorted in descending order
   78:                     var yearsList = years.Distinct().ToList();
   79:                     yearsList.Sort();
   80:                     yearsList.Reverse();
   81:  
   82:                     //Add the years to the Tree View
   83:                     foreach (int year in yearsList)
   84:                         ArchiveSummaryTree.Nodes.Add(new TreeNode(year.ToString()));
   85:  
   86:                     //Find out which months have posts in each year and add them to the Tree View as ChildNodes
   87:                     foreach (TreeNode year in ArchiveSummaryTree.Nodes)
   88:                     {
   89:                         for (int i = 12; i >= 1; i--)
   90:                         {
   91:                             foreach (SPBlogPost post in postsBuffer)
   92:                             {
   93:                                 if (post.PublishingMonth == i.ToString() && post.PublishingYear == year.Text)
   94:                                 {
   95:                                     duplicates = false;
   96:                                     foreach (TreeNode item in year.ChildNodes)
   97:                                     {
   98:                                         //Check for any duplicate month entries.
   99:                                         //This will become an issue if the user posts more than one post in a month time.
  100:                                         if (item.Text.ToLower() == months[int.Parse(post.PublishingMonth) - 1].ToLower())
  101:                                             duplicates = true;
  102:                                     }
  103:                                     if (!duplicates)
  104:                                         year.ChildNodes.Add(new TreeNode(months[int.Parse(post.PublishingMonth) - 1], 
  105:                                             post.PublishingMonth, null, null, null));
  106:                                 }
  107:                             }
  108:                         }
  109:                     }
  110:  
  111:                     //Expand the latest year node
  112:                     if (ArchiveSummaryTree.Nodes[0] != null)
  113:                         ArchiveSummaryTree.Nodes[0].Expand();
  114:                 }
  115:                 catch (Exception ex)
  116:                 {
  117:                     //Print the error messege to the user using a Literal
  118:                     ArchiveSummaryErrors.Text = "<img src='" + SPContext.Current.Site + 
  119:                         "_layouts/images/SharePointStack/SPBlogTemplate/error.png' alt='Error' style='display: block;' />" + 
  120:                         "&nbsp;<font color='#ff0000' size='12'>" + ex.Message + "</font>";
  121:                 }
  122:                 finally
  123:                 {
  124:                     //Objects will be disposed during the next Garbage Collector run
  125:                     postsBuffer = null;
  126:                     years = null;
  127:                 }
  128:             }
  129:         }
  130:  
  131:         protected void ArchiveSummaryTree_SelectedNodeChanged(object sender, EventArgs e)
  132:         {
  133:             TreeView summaryLinks = (TreeView)sender;
  134:  
  135:             //Make sure that this is a month node, we don't want to handle a year node selection change
  136:             if (summaryLinks.SelectedNode.Parent != null)
  137:             {
  138:                 string month = string.Empty, year = string.Empty;
  139:                 month = summaryLinks.SelectedNode.Value;
  140:                 year = summaryLinks.SelectedNode.Parent.Value;
  141:  
  142:                 //Build the date strings to be converted later on to ISO8601 dates
  143:                 string startDate = month + "/1/" + year;
  144:                 string endDate = startDate;
  145:  
  146:                 //Convert the dates and set the range to be a one month time span
  147:                 startDate = SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Parse(startDate));
  148:                 endDate = SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Parse(startDate).AddMonths(1));
  149:  
  150:                 //Build the URL and set the Query String parameters for redirection to the Date.aspx page
  151:                 string url = SPContext.Current.Site + SPContext.Current.Web.ServerRelativeUrl + 
  152:                     "/Lists/Posts/Date.aspx?StartDateTime=" + startDate + "&EndDateTime=" + endDate + 
  153:                     "&LMY=" + months[int.Parse(month) - 1] + ", " + year;
  154:  
  155:                 summaryLinks.Dispose();
  156:  
  157:                 //SharePoint will add "SPSite Url=" to the URL we built. I used Substring to remove it.
  158:                 Response.Redirect(url.Substring(11));
  159:             }
  160:         }
  161:     }
  162: }

Web Part Source Code Download

Well that’s about it Smile If you want the source code you can download it here.

Please don’t hesitate to contact me if you have any questions.

Comments

  • Anonymous
    March 24, 2012
    Super :-)
  • Anonymous
    March 28, 2012
    Just tried it and it worksThanks
  • Anonymous
    March 28, 2012
    I am glad you like it Jorgan :-)
  • Anonymous
    March 29, 2012
    any plans for implementing in sandbox?
  • Anonymous
    March 29, 2012
    In VS11 you can build Visual WebParts to run with the Sandbox subset. No need for rewriting the WebPart :-)
  • Anonymous
    April 05, 2012
    I installed your webpart and it is not working for me.  I receive a 404 error when clicking on the tree nodes for archived months.
  • Anonymous
    April 06, 2012
    Hi Charles, Can you please post the blog site URL and the URL you are redirected to by the tree nodes (404)?  I think you are being redirected to a wrong URL.
  • Anonymous
    April 12, 2012
    Thanks for the response.  The blog site is on a site behind our company firewall.  The url of the blog is the same after clicking on the archive link as shown in the pics.www.flickr.com/.../photostreamwww.flickr.com/.../photostream
  • Anonymous
    April 13, 2012
    Hi Charles, I need the URL or at least a hint on how it looks before you click and how it becomes after you click and get the 404 from the address bar, not the status bar please :-)
  • Anonymous
    April 23, 2012
    I can't seem to get this to work. I installed the webpart and made posting dated "back in time". As I understand the tree view is then supposed to create the months automatically, but that doesn't happen. What am I doing wrong?
  • Anonymous
    April 24, 2012
    Hi Kathrine, Can you please send a screenshot? As you can see in my code above, I query the Posts list for any Published Posts regardless when they where posted. Are these posts published?
  • Anonymous
    April 24, 2012
    Hello BandarI just went in to look at bit more at the issue and found that it had picked up the postings I did yesterday. Do you have any idea would could cause that?Please let me know, if a screendump would still help after this piece of information.Thank you for the great work on this webpart and your help.
  • Anonymous
    April 24, 2012
    The comment has been removed
  • Anonymous
    April 25, 2012
    Hello BandarThat makes sense - Thank you! :)BRKathrine
  • Anonymous
    April 25, 2012
    You are most welcome :-)
  • Anonymous
    May 29, 2012
    Hello againWe have now deployed the webpart in our production environment and somehow it isn't working as it should. The webpart displays the months as it should, but when clicking on each month it still shows all posts, instead of the posts from that specific month. I have checked the url and compared it to the url in the test enviroment and the both look like this:Test environment (that works)/Lists/Posts/Date.aspx?StartDateTime=2012-02-01T00:00:00Z&EndDateTime=2012-03-01T01:00:00Z&LMY=February,%202012Production (Shows all posts)/Lists/Posts/Date.aspx?StartDateTime=2012-04-01T00:00:00Z&EndDateTime=2012-05-01T02:00:00Z&LMY=April,%202012I am really puzzled as to why this isn't working...Can you help out?Thank youKathrine
  • Anonymous
    September 09, 2012
    I am trying to deploy the web part but i do not know how can i use it
  • Anonymous
    December 02, 2012
    Sorry - scratch my last post - it seems to be working now. Thanks.Dave
  • Anonymous
    December 02, 2012
    The comment has been removed
  • Anonymous
    December 27, 2012
    Hi! I need help deploying this. Can this be deployed at the site level or must it be deployed at the farm level only? Thank you!
  • Anonymous
    December 27, 2012
    I am trying to deploy this webpart. Can it be deployed at the site level or must it be deployed at the farm level?
  • Anonymous
    December 29, 2012
    Hi Dave,Does it work if you flip the day and month in the query string? If yes, then I guess its related to the culture settings, never thought if that when I wrote the web part loool.Please let me know what you find out.
  • Anonymous
    December 29, 2012
    Hi Matt,Unfortunately no, this is a farm solution with a feature on the collection level to activate/deactivate. A quick work around would be using the VS2012 Visual WebPart template which can be deployed later as a Sandbox solution.Please let me know if you need support with it.
  • Anonymous
    September 15, 2013
    The comment has been removed
  • Anonymous
    September 26, 2013
    Thank for this.I just fixed some translation issues with this webpart and submitted patch to codeplex. I hope you can apply patch and make it available as new version.I'm just noob  with c# and web parts, but lets hope that patch is fine. ;)  
  • Anonymous
    March 03, 2014
    does this one work on SP2013? If so, can someone post a guide how to install it.
  • Anonymous
    September 16, 2014
    Hi,This solution seems to be very useful. How can we add this code into SharePoint 2013? Is it possible to do theese thing in SharePoint 2013? İf possible how? Can you help me please?
  • Anonymous
    September 16, 2014
    Yes, it should work in SP2013. It's just aggregating list items and rendering the content. What do you need help with deployment? Do you have specific questions or just never deployed farm solutions before? In both cases I am here :)
  • Anonymous
    September 17, 2014
    Same issue here. SP13 seems to not like the wsp file. I am getting this message:"The file you imported is not valid. Verify that the file is a Web Part description file (*.webpart or *.dwp) and that it contains well-formed XML."
  • Anonymous
    September 17, 2014
    You can't use the same WSP file, you will have to download the source, reference the right assemblies, and then package. Code will work of course, not changes needed. If you can't do it, then I will do it over the weekend and add it to the post. Let me know if you want me to :)
    • Anonymous
      October 10, 2016
      Any Chance you did this for SP2013 or SP Online?This is exactly what I need...Please help?Thanks,Sam
  • Anonymous
    February 11, 2015
    Hi,Did you ever got the chance to repackaged so it can be use with SharePoint 2013?  This can be very useful for those migrating blogs from different sources to SP.Great Work!Thanks
  • Anonymous
    February 15, 2015
    Hi,I want to create archive like this--November 2007 (2)February 2006 (1)... but i don't have Visual studio, so How can create this kind archive with SharePoint Designer only, please suggest.ThanksRenuSh
  • Anonymous
    April 09, 2015
    Hi Bandar,your webpart looks exactly like what I've been looking for since ages. Unfortunately I only have Office365's sharepoint, and no clue on how to create a new webpart from code (and no Visual Studio either).Could you please please please :) supply the SP13 packaged version you were talking about?Thank you very much