The built-in blog tool in SharePoint 2010 and SharePoint 2007 is a basic blog tool with everything you need to quickly share ideas and information in your organization. The blog template exists in all SharePoint versions since 2007 which includes all server versions as well as the free wss and Foundation distributions.
A quick research shows that a common request is to show the SharePoint blog posts on multiple SharePoint sites and possibly on external web sites, but lets talk about that in another blog post.
The bad news is that this type of internal integrations quickly becomes very complex and proprietary using CAML, XSLT and the built-in SharePoint Class Libraries and WS References (have a look)
The good news is that when using the Camelot .NET Connector for SharePoint we can build a very versatile and modular web part with just a few functions.
SharePoint blog site basics
The SharePoint blog is a site template that contains lists and libraries, such as a list of blog posts, a list of other blogs (SharePoint 2007 only), and a library for photos. Once you create a blog, you can set up categories, and then customize the blog settings.
Available lists
- Categories
- Comments
- Links
- Other Blogs (SharePoint 2007 only)
- Photos
- Posts
The design
Instead of pushing the posts to lists all over the sites we will build a web part who is able to fetch blog posts filtered by their category. One simple but important design difference between the SharePoint 2007 and 2010 versions is that in the 2010 versions you are able to select multiple categories to a blog post.
Wish list
The design specification tells me that it should be a simple and modular web part who can
- Connect to any SharePoint installation (which we can reach) and fetch blog posts
- Support both SharePoint 2007 and 2010
- Fetch blog posts from a single or multiple categories, no category selected should fetch all posts
- Total posts to fetch
- Decide sort order
- Display blog titles only, the titles must have a href link to the actual post
- Specify how many words to display from retrieved posts
- Display a read more link in the end of each excerpt.
Thats not much is it?
You can read more and download the source and
binaries for this web part here
The code
Requirements
- Microsoft SharePoint 2007/2010 with Camelot .NET Connector installed
- Administrative privileges to deploy Web Parts at the server and in SharePoint
- .NET SDK, you need gacutil to deploy the binaries (http://blogs.iis.net/davcox/archive/2009/07/14/where-is-gacutil-exe.aspx)
/*************************************** * Camelot "Camelot SP Blog Reader" for SharePoint * @version 1.0 * @author Bendsoft * @package Camelot SP Blog Reader * @subpackage Camelot * @license FreeBSD License * Bendsoft 2011 * www.bendsoft.com ***************************************/ using System; using System.ComponentModel; using System.Data; using System.Web.UI; using System.Web.UI.WebControls.WebParts; using Camelot.SharePointConnector.Data; namespace CamelotSPBlogReader { public class CamelotSPBlogReader : System.Web.UI.WebControls.WebParts.WebPart { //Microsoft.SharePoint.WebPartPages.WebPartPage // Set up connection to SharePoint private string connectionString = string.Empty; private SharePointConnection conn; private System.Version v = Microsoft.SharePoint.Administration.SPFarm.Local.BuildVersion; #region ConnectionString private string serverAddress; [Category("ConnectionString"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Server address"), WebDescription("The IP or hostname of the SharePoint server. Required.")] public string ServerAddress { get { if (serverAddress != null) return serverAddress; else return "localhost"; } set { serverAddress = value; } } private string userName; [Category("ConnectionString"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("User name"), WebDescription("User name of the account used to access SharePoint. This account must have sufficient privileges to access the provided site. Required when server uses authentication mode [Ntlm] or [Basic].")] public string UserName { get { if (userName != null) return userName; else return string.Empty; } set { userName = value; } } private string userPassword; [Category("ConnectionString"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Password"), WebDescription("Password for the actual user")] public string UserPassword { get { if (userPassword != null) return userPassword; else return string.Empty; } set { userPassword = value; } } private string userDomain; [Category("ConnectionString"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("User Domain (optional)"), WebDescription("The domain of the specified user. Leave empty if domain user account is not used.")] public string UserDomain { get { if (userDomain != null) return userDomain; else return string.Empty; } set { userDomain = value; } } private string siteName; [Category("ConnectionString"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Site Name"), WebDescription("Corresponds to the site path on the SharePoint server, e.g. marketing or marketing/news. Leave empty for top site.")] public string SiteName { get { return siteName; } set { siteName = value; } } private string authentication; [Category("ConnectionString"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Authentication"), WebDescription("Indicates the authentication mode used on the SharePoint server. [Default]: Use the credentials from the current security context. No specific account details should be provided. Only set this mode when SPC is used in components running inside SharePoint, such as webparts. SPC is then running as the current user. [Ntml]: Specifies client authentication using Ntml. This is the most common scenario. [Basic]: Specifies that user authenticates to the server using basic authentication which is a simpler less secured form.")] public string Authentication { get { if (authentication != null) return authentication; else return "Default"; } set { authentication = value; } } #endregion #region Property Blog feed settings public enum SelectVersionEnum { Version2010, Version2007 }; protected SelectVersionEnum selectVersion; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("SharePoint Version"), WebDescription("SharePoint version where the blog is kept. Version2010 means SharePoint 2010 or newer, Version2007 means SharePoint 2007 or older.")] public SelectVersionEnum SelectVersion { get { return selectVersion; } set { selectVersion = value; } } private string blogCategory; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Category/Categories"), WebDescription("Type category/categories to fetch, separate with semicolon.")] public string BlogCategory { get { return blogCategory; } set { blogCategory = value; } } private int posts; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("No. posts"), WebDescription("How many posts to fetch")] public int Posts { get { if (posts != 0) return posts; else return 5; // Let's have 5 as default! } set { posts = value; } } public enum TitlesByEnum { FALSE, TRUE }; // Set default value first! protected TitlesByEnum titlesOnly; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Show titles only"), WebDescription("If TRUE the control only renders the title link.")] public TitlesByEnum TitlesOnly { get { return titlesOnly; } set { titlesOnly = value; } } public enum SortByEnum { DESC, ASC }; // Set default value first! protected SortByEnum sortOrder; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Sort order"), WebDescription("ASC = Oldest post first, DESC = Newest post first")] public SortByEnum SortOrder { get { return sortOrder; } set { sortOrder = value; } } private int wordCount; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("No. words"), WebDescription("How many words to display, 0 will render the entire post.")] public int WordCount { get { return wordCount; } set { wordCount = value; } } public enum ShowByEnum { TRUE, FALSE }; // Set default value first! protected ShowByEnum showReadMore; [Category("SharePoint Blog Settings"), WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDisplayName("Show read more link"), WebDescription("If TRUE the control renders a read more link.")] public ShowByEnum ShowReadMore { get { return showReadMore; } set { showReadMore = value; } } #endregion /// <summary> /// Base method to construct the web part /// </summary> /// <returns></returns> protected override void CreateChildControls() { base.CreateChildControls(); try { /** Set up connection to your SharePoint server **/ connectionString = "Server=" + ServerAddress + ";Database=" + SiteName + ";Domain=" + UserDomain + ";User=" + UserName + ";Password=" + UserPassword + ";Authentication=" + Authentication + ";TimeOut=60;decodename=true"; conn = new SharePointConnection(connectionString); /** Use the buildCategoryQuery to build the conditional query for selected categories **/ string sqlCategories = buildCategoryQuery(); /** Build query for Posts list **/ string sqlLimit = (posts > 0) ? string.Format(" LIMIT {0}", posts) : ""; string sqlSort = string.Format(" ORDER BY PublishedDate {0}", sortOrder); string sql = (titlesOnly.ToString() == "TRUE") ? string.Format("SELECT Title, ID FROM `Posts` {0} {1} {2}", sqlCategories, sqlSort, sqlLimit) : string.Format("SELECT Title, Body, ID FROM `Posts` {0} {1} {2}", sqlCategories, sqlSort, sqlLimit); SharePointDataAdapter adapter = new SharePointDataAdapter(sql, conn); DataTable dt = new DataTable(); adapter.Fill(dt); /** Output **/ foreach (DataRow row in dt.Rows) { if (titlesOnly.ToString() == "TRUE") { // Header Controls.Add(new LiteralControl(string.Format("<h2 class=""><a href="/{0}/Lists/Posts/Post.aspx?ID={1}">{2}</a></h2>", SiteName, row["ID"], row["Title"]))); } else { // Header Controls.Add(new LiteralControl(string.Format("<h2 class=""><a href="/{0}/Lists/Posts/Post.aspx?ID={1}">{2}</a></h2>", SiteName, row["ID"], row["Title"]))); // Body Controls.Add(new LiteralControl(string.Format("<p class="">{0}</p>", truncateAtWord(row["Body"].ToString())))); // Read More link if (showReadMore.ToString() == "TRUE") Controls.Add(new LiteralControl(string.Format("<a class="" style="text-align: right;" href="/{0}/Lists/Posts/Post.aspx?ID={1}">Read more</a>", SiteName, row["ID"]))); } } } catch (Exception a) { Controls.Add(new LiteralControl(a.Message)); } } /// <summary> /// Builds a partial SQL string to match values in the Post list. /// </summary> /// <returns></returns> private string buildCategoryQuery() { string sqlCategories = string.Empty; string sqlWhere = string.Empty; string sqlReturn = string.Empty; if (!string.IsNullOrEmpty(blogCategory)) { // Handle the input string[] blogCategories = blogCategory.Split(new char[] { ',' }); switch (selectVersion.ToString()) { case "Version2007": // Get category id's. We should be able to query the Posts table directly (looking for CategoryWithLink) but // some SharePoint setups wont allow us to query computed values through the api, thus this small detour. // This mainly applies to SharePoint 2007 or older releases // Build conditions for the query in the categories table for (int i = 0; i < blogCategories.Length; i++) { if (i < blogCategories.Length - 1) // If several sqlWhere += string.Format(" LinkTitle = '{0}' OR ", blogCategories[i].Trim()); else // If single or last sqlWhere += string.Format(" LinkTitle = '{0}' ", blogCategories[i].Trim()); } // Fetch category ID's sqlCategories = string.Format("select ID from categories where {0}", sqlWhere); SharePointDataAdapter adapter = new SharePointDataAdapter(sqlCategories, conn); DataTable dt = new DataTable(); adapter.Fill(dt); // Build querystring if (dt.Rows.Count > 0) { sqlReturn = " WHERE "; int i = 0; foreach (DataRow row in dt.Rows) { i++; sqlReturn += string.Format(" PostCategory = {0} ", row["ID"]); // Add OR if not last row if (i < dt.Rows.Count) sqlReturn += " OR "; } } break; case "Version2010": default: // In SharePoint 2010 or newer we can query the Posts list directly with a string value // matching the values in CategoryWithLink if (blogCategories.Length > 0) { for (int i = 0; i < blogCategories.Length; i++) { if (i < blogCategories.Length - 1) // If several sqlWhere += string.Format(" CategoryWithLink = '{0}' OR ", blogCategories[i].Trim()); else // If single or last sqlWhere += string.Format(" CategoryWithLink = '{0}' ", blogCategories[i].Trim()); } sqlReturn = " WHERE " + sqlWhere; } break; } } return sqlReturn; } /// <summary> /// Cut the input string after n words /// </summary> /// <param name="input"></param> /// <returns>Ready-formated string</returns> private string truncateAtWord(string input) { try { string output = string.Empty; string[] inputArr = input.Split(new char[] { ' ' }); if (inputArr.Length <= wordCount) return input; if (wordCount > 0) { for (int i = 0; i < wordCount; i++) { output += inputArr[i] + " "; } output += "..."; return output; } else { return input; } } catch (Exception a) { return a.Message; } } /// <summary> /// Find and return a hint of the current SharePoint version /// </summary> /// <returns></returns> private string VersionInformation() { string versionReturn = string.Empty; if (Convert.ToInt32(v.Major) <= 12) versionReturn = "2007 or older."; else versionReturn = "2010 or newer."; return string.Format("Hint! You are using SharePoint {0}", versionReturn); } } }
The settings dialogs will look like this
You can download the entire Visual Studio project or precompiled binary at our official web site www.bendsoft.com, the project is stored at the following url http://www.bendsoft.com/downloads/sharepoint-web-parts/sharepoint-blog-reader/
Enjoy
Once you get past the nitty gritty, share point is an excellent tool to get information out there quickly and efficiently.