I had a requirement for a feature that would enable admin users to post comments to blog entries that would appear as if they were posted by the original author of the blog article. The scenario here is a corporate intranet. The CEO would post a blog article that can be read and commented on by all employees. The CEO is very busy and delegates the job of replying to comments to her staff. For relationship purposes however, the replies should appear as if they were posted by the CEO herself.
This poses as an interesting problem and this post describes my solution to this requirement. The goal is to make this work with SharePoint’s OOTB blog feature and use as little customisation as possible.
User Experience
All users would use the standard OOTB UIs to post blog comments. If the user has appropriate permissions (e.g. member of the site’s Owner group), he would get an additional button to post the comment as the original author of the blog post, as shown belown.
If the user does not have the required permissions, he would not see this special button, and everything will appear as they do OOTB.
Approach
This solution involves 3 elements:
- A special button that inherits from the OOTB “Submit Comment” button. In the OnLoad event, this custom button checks the permissions of the current user. The button hides itself if the user does not have the required permissions. The button also overrides the SaveItem method to add a flag indicating that the Comment item should be saved as being posted by the original author of the blog post.
- A custom rendering template to add our special button to the form that is used to post Comments
- An event receiver attached to the Comments list that handles the ItemAdded event. This event receiver checks for the flag that is raised by our special button. If found, it updates the Comment item and set the Author field to be the original author of the blog post.
Below is a walkthrough of these elements. You can download the code at the end of this post.
1. Custom submit button
The OOTB “Submit Comment” button is actually a special class in SharePoint, namely the SubmitCommentButton class. We will create a custom button inherited from this class and override some of its methods.
In your Visual Studio project (which needs to be a Farm solution), add a new class (mine is called SubmitCommentAsAuthorButton). Make this class public and inherits from the SubmitCommentButton class, which is defined in Microsoft.SharePoint.dll.
First, we’ll override the OnLoad method to ensure that the button is only visible to users with appropriate permissions.
protected override void OnLoad(EventArgs e) { if (!Page.IsPostBack) { if (!DoesCurrentUserHaveRequiredPermissions()) { this.Visible = false; return; } } base.OnLoad(e); } private bool DoesCurrentUserHaveRequiredPermissions() { var ownerGroup = SPContext.Current.Web.AssociatedOwnerGroup; return ownerGroup.ContainsCurrentUser; }
Next, we’ll override the SaveItem method. Here we simply add a flag indicating that the Comment item should be updated to appear as being posted by the original author of the blog post. Review the in-code comment below.
protected override bool SaveItem() { //Raise our special flag in the Properties bag of the current Comment item. Our //event receiver attached to the Comments list will look for this flag. this.ItemContext.ListItem.Properties[Constants.IsPostingAsAuthorPropertyKey] = true; return base.SaveItem(); }
Add the supporting Constants class:
class Constants { public const string IsPostingAsAuthorPropertyKey = "IsPostingAsAuthor"; public const string ParentBlogPostIDFieldName = "Post Id"; public const string ParentBlogListRelativeUrl = "Lists/Posts"; }
2. Custom rendering template
Next we need to make our custom submit button appears on the form that is used to post comments. This form is controlled by a rendering template, namely BlogCommentsForm. This template is defined in the DefaultTemplates.ascx file, located at {SharePointRoot}\TEMPLATE\CONTROLTEMPLATES.
SharePoint allows us to override these rendering templates. We do this by deploying a custom ASCX file (to the same folder above) containing a rendering template with the same ID as the one we want to override.
In your Visual Studio project, created a mapped folder to CONTROLTEMPLATES. Add an ASCX file to the mapped CONTROLTEMPLATES folder. Mine is called BNH.SharePoint.BlogCommentAsAuthor.ascx. Note that this ASCX file must sit directly in the CONTROLTEMPLATES folder, and not in a sub-folder underneath this folder. Your project should now look like this:
Add the following directives to the ASCX (these are copied from the OOTB DefaultTemplates.ascx):
<%@ Control Language="C#" AutoEventWireup="false" %> <%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%> <%@ Register TagPrefix="wssuc" TagName="ToolBar" src="~/_controltemplates/ToolBar.ascx" %>
Add one more directive, this is for our custom button (replace the assembly’s full name with your own):
<%@ Register TagPrefix="BNH" Assembly="BNH.SharePoint.BlogCommentAsAuthor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=70cbde78fe619ed5" namespace="BNH.SharePoint.BlogCommentAsAuthor"%>
Copy the BlogCommentsForm rendering template from the OOTB DefaultTemplates.ascx and add it to our custom template:
<SharePoint:RenderingTemplate id="BlogCommentsForm" runat="server"> <Template> <table width="275" cellpadding="0" cellspacing="0" border="0"> <tr><td><h3 class="ms-CommentHeader"><asp:Label id="BlogCommentsFormTitle" runat="server" Visible="true" Text="<%$Resources:wss,comments_AddComment%>"/></h3></td></tr> <tr><td> <span id='part1' style="padding-top: 10px"> <SharePoint:InformationBar ID="InformationBar1" runat="server"/> <table class="ms-formtable" border="0" cellpadding="0" cellspacing="0" width="100%"> <SharePoint:ChangeContentType ID="ChangeContentType1" runat="server"/> <SharePoint:FolderFormFields ID="FolderFormFields1" runat="server"/> <SharePoint:ListFieldIterator ID="ListFieldIterator1" runat="server" ExcludeFields="PostTitle"/> <SharePoint:ApprovalStatus ID="ApprovalStatus1" runat="server"/> <SharePoint:FormComponent ID="FormComponent1" TemplateName="AttachmentRows" runat="server"/> </table> <table cellpadding="0" cellspacing="0" width="100%" style="padding-top: 10px"><tr><td width="100%"> <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&#160;" runat="server"> <Template_RightButtons> <SharePoint:SubmitCommentButton ID="SubmitCommentButton1" CssClass="ms-ButtonHeightWidth2" runat="server" Text="<%$Resources:wss,tb_submitcomment%>"/> </Template_RightButtons> </wssuc:ToolBar> <SharePoint:InitContentType ID="InitContentType1" runat="server"/> <SharePoint:ItemHiddenVersion ID="ItemHiddenVersion1" runat="server"/> </td></tr></table> </span> </td></tr></table> <SharePoint:AttachmentUpload ID="AttachmentUpload1" runat="server"/> </Template> </SharePoint:RenderingTemplate>
Now add our custom button to the BlogCommentsForm template in our custom ASCX. Add the code below just above the OOTB submit button (search for SubmitCommentButton):
<BNH:SubmitCommentAsAuthorButton CssClass="ms-ButtonHeightWidth2" runat="server" Text="Submit Comment as Post Author"/>
The complete custom ASCX should look like this:
<%@ Control Language="C#" AutoEventWireup="false" %> <%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%> <%@ Register TagPrefix="wssuc" TagName="ToolBar" src="~/_controltemplates/ToolBar.ascx" %> <%@ Register TagPrefix="BNH" Assembly="BNH.SharePoint.BlogCommentAsAuthor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=70cbde78fe619ed5" namespace="BNH.SharePoint.BlogCommentAsAuthor"%> <SharePoint:RenderingTemplate id="BlogCommentsForm" runat="server"> <Template> <table width="275" cellpadding="0" cellspacing="0" border="0"> <tr><td><h3 class="ms-CommentHeader"><asp:Label id="BlogCommentsFormTitle" runat="server" Visible="true" Text="<%$Resources:wss,comments_AddComment%>"/></h3></td></tr> <tr><td> <span id='part1' style="padding-top: 10px"> <SharePoint:InformationBar ID="InformationBar1" runat="server"/> <table class="ms-formtable" border="0" cellpadding="0" cellspacing="0" width="100%"> <SharePoint:ChangeContentType ID="ChangeContentType1" runat="server"/> <SharePoint:FolderFormFields ID="FolderFormFields1" runat="server"/> <SharePoint:ListFieldIterator ID="ListFieldIterator1" runat="server" ExcludeFields="PostTitle"/> <SharePoint:ApprovalStatus ID="ApprovalStatus1" runat="server"/> <SharePoint:FormComponent ID="FormComponent1" TemplateName="AttachmentRows" runat="server"/> </table> <table cellpadding="0" cellspacing="0" width="100%" style="padding-top: 10px"><tr><td width="100%"> <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&#160;" runat="server"> <Template_RightButtons> <BNH:SubmitCommentAsAuthorButton CssClass="ms-ButtonHeightWidth2" runat="server" Text="Submit Comment as Post Author"/> <SharePoint:SubmitCommentButton ID="SubmitCommentButton1" CssClass="ms-ButtonHeightWidth2" runat="server" Text="<%$Resources:wss,tb_submitcomment%>"/> </Template_RightButtons> </wssuc:ToolBar> <SharePoint:InitContentType ID="InitContentType1" runat="server"/> <SharePoint:ItemHiddenVersion ID="ItemHiddenVersion1" runat="server"/> </td></tr></table> </span> </td></tr></table> <SharePoint:AttachmentUpload ID="AttachmentUpload1" runat="server"/> </Template> </SharePoint:RenderingTemplate>
3. Event receiver
In your Visual Studio project add a feature (mine is called BlogCommentAsAuthor) and set its scope to be Web. Add an Event Receiver project item (mine is called BlogCommentsEventReceiver). The designer will prompt you to select a list and an event type. Select any list (we will fix this up later) and select An item was added (i.e. ItemAdded).
Once the event receiver has been added to your project, edit its Elements.xml. Change the ListTemplateId to be 302 (this is the template ID for the Comments list). Add the following element to the Receiver element:
<Synchronization>Synchronous</Synchronization>
The XML above set the ItemAdded event receiver to be synchronous. This is to ensure that the user will not see the newly added item until our event receiver has finished its job.
The complete XML should appear as below:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Receivers ListTemplateId="302"> <Receiver> <Name>BlogCommentsEventReceiverItemAdded</Name> <Type>ItemAdded</Type> <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly> <Class>BNH.SharePoint.BlogCommentAsAuthor.BlogCommentsEventReceiver.BlogCommentsEventReceiver</Class> <SequenceNumber>10000</SequenceNumber> <Synchronization>Synchronous</Synchronization> </Receiver> </Receivers> </Elements>
In the event receiver class, replace the ItemAdded method and add the code below. Review the in-code comments.
public override void ItemAdded(SPItemEventProperties properties) { if (properties.ListItem != null) { //Look for our special flag. If we find it, update the Author field to be the same value as the //Author field of the parent Blog post. if (properties.ListItem.Properties.ContainsKey(Constants.IsPostingAsAuthorPropertyKey)) { var isPostingAsAuthor = Boolean.Parse((string)properties.ListItem.Properties[Constants.IsPostingAsAuthorPropertyKey]); if (isPostingAsAuthor) { var blogPostItem = GetParentBlogPostForCommentItem(properties.ListItem); properties.ListItem[SPBuiltInFieldId.Author] = blogPostItem[SPBuiltInFieldId.Author]; properties.ListItem.Update(); } } } base.ItemAdded(properties); } private SPListItem GetParentBlogPostForCommentItem(SPListItem commentListItem) { //We have to hardcode the URL to the parent Blog list as this info is not contained within the //Comment list item. OOTB however, the Blog list can only be created as part of the Blog site template //and its URL cannot be changed. This is therefore relatively safe. var blogListUrl = commentListItem.Web.ServerRelativeUrl.TrimEnd('/') + "/" + Constants.ParentBlogListRelativeUrl; var blogList = commentListItem.Web.GetList(blogListUrl); var parentBlogPostIDLookup = new SPFieldLookupValue((string)commentListItem[Constants.ParentBlogPostIDFieldName]); return blogList.GetItemById(parentBlogPostIDLookup.LookupId); }
That is all! Deploy the solution, perform an IISRESET (this is required for SharePoint to pick up the new rendering template) and activate the feature.
4. Download the source code
You can download the complete Visual Studio solution here.
5. Other posts you may be interested in
Want to improve the user experience in SharePoint? Check out my other custom solutions here.