An Alternative Archives Web Part to Solve the Pre-dated Posts Provisioning in the OOTB Blog Site Archives Web Part
The 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 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: " <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 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
- 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