Tuesday, October 30, 2012

Adding microdata format to your blog posts with Sitefinity

Our team @Telerik is currently busy with the migration of blogs.telerik.com to the latest version of Sitefinity 5.x. This task comes with some challenges, one of which is the improvement of the markup rendered by Sitefinity - we would like to make it semantic using schema.org. I already showed how to decorate your breadcrumb control with microdata format. To achieve the same for blog posts is just a little more work.

Here is an example of how a blog post should look like according to schemata.org:
<body itemscope itemtype="http://schema.org/Blog">
<!-- Blog post -->
    <article itemprop="blogPost">
        <header>
     <h1 itemprop="headline">Headline</h1>  
         <time itemprop="datePublished" datetime="2012-08-08">Wednesday, August 08, 2012</time>
  by 
  <a href="#" itemprop="author">Just* Team</a> | <a href="">Comments 0</a>
 </header>

<p>Blog post body paragraph 1</p>

<p>Blog post body paragraph 2</p>

    <section>
 <h2>2 Comments</h2>
 <article class="comment" itemscope itemtype="http://schema.org/UserComments">
           <div>
  <span itemprop="creator">Dejan</span>
  <time itemprop="commentTime" datetime="2012-08-09">09 Aug 2012</time>
     </div>

     <p itemprop="commentText">Comment text 1</p>
 </article>

 <article class="comment" itemscope itemtype="http://schema.org/UserComments">
     <div>
  <span itemprop="creator">Dejan</span>
  <time itemprop="commentTime" datetime="2012-08-09">09 Aug 2012</time>
     </div>

 <p itemprop="commentText">Comment text 2</p>
   </article>

  </section>
 </article>
<!-- Blog post -->
</body>
Notice that we have to add some attributes to the body tag. One way to achieve this is to use a master page. This will eventually lead to having a lot of master pages with just a different body tag for every content type that you have in your website. Another way is to create a custom control that renders these attributes for you - again quite inconvenient as you have to remember to drag this control on every page you need semantic markup ( and nothing will alert you that you have forgotten somewhere). The approach we choose to take is to derive from the Details view class for blog posts and override the PreRender event handler:
public class DetailPostViewSemanticMarkup : DetailPostView
    {
        protected override void OnPreRender(System.EventArgs e)
        {
         base.OnPreRender(e);

            if (this.Page != null)
            {
                HtmlGenericControl ctrlBody = this.Page.FindControlRecursively("grid") as HtmlGenericControl;
                if (ctrlBody != null)
                {
                    ctrlBody.Attributes.Add("itemtype", @"http://schema.org/Blog");
                    ctrlBody.Attributes.Add("itemscope", "itemscope");
                }
                else
                {
                    throw new ConfigurationException("The configured master page doesn't have <body runat=\"server\", which is required by the selected detail template for blogs");
                }
            }
        }
    }
After that you need to make Sitefinity to use this template for your details pages:
Go to the Administration and navigate to Settings > Advanced > Blogs > Controls > BlogPostsFrontend > DetailBlogPostsFrontend and change the property called "ViewType" to the fully qualified type name of the new class you created (in my case "BlogsWebApp.Views.Blogs.DetailPostViewSemanticMarkup"). After saving this setting you should be able to see the desired attributes in the body tag of the details pages in your blogs.

Next, navigate Design > Widget Templates and find the templates that is used in your blogs detail pages, by default it is called: "Full blog post item" and it applies to Blog posts - single. Go to edit it and change it in the way that fits the above specification. Our template looks similar to:
<%@ Control Language="C#" %>
<%@ Register TagPrefix="telerik" Namespace="Telerik.Web.UI" Assembly="Telerik.Web.UI" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.ContentUI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.PublicControls.BrowseAndEdit"
    Assembly="Telerik.Sitefinity" %>
<%@ Import Namespace="Telerik.Sitefinity" %>
<telerik:RadListView ID="SingleItemContainer" ItemPlaceholderID="ItemContainer" AllowPaging="False"
    runat="server" EnableEmbeddedSkins="false" EnableEmbeddedBaseStylesheet="false">
    <layouttemplate>
        <article itemprop="blogPost">
            <asp:PlaceHolder ID="ItemContainer" runat="server" />
        </article>
    </layouttemplate>
    <itemtemplate>
        <header>
          <h1 itemprop="headline" class="mb20">
            <%# Eval("Title")%>
          </h1>
        </header>
        <div class="sfpostAuthorAndDate cuArticleInfo mb20">
            <time itemprop="datePublished" datetime='<%#((DateTime)Eval("PublicationDate")).ToString("yyyy-MM-dd")%>'>
               <%# ((DateTime)Eval("PublicationDate")).ToString("dddd, MMMM dd, yyyy")%>
            </time>

            <asp:Literal ID="Literal2" Text="<%$ Resources:Labels, By %>" runat="server" /> 
            <span itemprop="author">  
              <sf:PersonProfileView runat="server" /> 
            </span>
          |  <a href="#comments" class="comments">Comments <span class="bubble"><span class="point"></span><span class="bubble-icon rounded3">000</span></span></a>
        </div>
        <sf:ContentBrowseAndEditToolbar ID="BrowseAndEditToolbar" runat="server" Mode="Edit,Delete,Unpublish"></sf:ContentBrowseAndEditToolbar>
        <sf:FieldListView ID="PostContent" runat="server" 
            Text="{0}" Properties="Content" 
            WrapperTagName="div" WrapperTagCssClass="sfpostContent tPostContent mb40"
        />

        <asp:PlaceHolder ID="socialOptionsContainer" runat="server">
  
    </asp:PlaceHolder>
        <sf:ContentView 
             id="commentsListView" 
             ControlDefinitionName="BlogsCommentsFrontend"
             DetailViewName="CommentsMasterView" 
             ContentViewDisplayMode="Master"
             LayoutTemplatePath="~/ExternalTmpl/Comments/CommentsMasterView.ascx"
             runat="server" />
        <sf:ContentView 
             id="commentsDetailsView" 
             ControlDefinitionName="BlogsCommentsFrontend" 
             DetailViewName="CommentsDetailsView"
             ContentViewDisplayMode="Detail"
            LayoutTemplatePath="~/ExternalTmpl/Comments/CommentsDetailsView.ascx"
             runat="server" />
    </itemtemplate>
</telerik:RadListView>

Finally, we have to take care for the comments. Mind that we use external template for them in order to suit it for our needs:
<%@ Control Language="C#" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI.ContentUI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="sf" Namespace="Telerik.Sitefinity.Web.UI" Assembly="Telerik.Sitefinity" %>
<%@ Register TagPrefix="telerik" Namespace="Telerik.Web.UI" Assembly="Telerik.Web.UI" %>

<telerik:RadListView ID="commentsList" ItemPlaceholderID="ItemsContainer" runat="server" EnableEmbeddedSkins="false" EnableEmbeddedBaseStylesheet="false">
    <LayoutTemplate>
        <section>
            <h2 id="comments" class="sfcommentsTitle">
                <asp:Literal ID="comments" runat="server" />
            </h2>
            <div class="tCommentListing">
                <asp:PlaceHolder ID="ItemsContainer" runat="server" />
            </div>
        </section>
    </LayoutTemplate>
    <ItemTemplate>
        <article itemscope itemtype="http://schema.org/UserComments" class="sfcommentDetails">
            <div class="tCommentAuthor">
                <sf:SitefinityHyperLink ID="website" Text='<%# Eval("AuthorName") %>' NavigateUrl='<%# Eval("Website") %>' runat="server" Target="_blank" />
                <span itemprop="creator" id="authorName" runat="server">
                    <%# Eval("AuthorName") %>
                </span>
                <time class="tCommentDate" itemprop="commentTime" datetime="<%#((DateTime)Eval("DateCreated")).ToLocalTime().ToString("dd MMM") %>">
                    <sf:FieldListView ID="dateCreated" runat="server" Format="{DateCreated.ToLocal():dd MMM}" />
                </time>
            </div>
            <div class="tCommentText" itemprop="commentText">
                <asp:Literal ID="content" Text='<%# Eval("Content") %>' runat="server" />
            </div>
        </article>
    </ItemTemplate>
</telerik:RadListView>

That's all you need. One tiny remark: I couldn't find a way to add itemscope attribute only to the body tag without adding value for it. From what I found in the internet, it's perfectly ok to replace itemscope with itemscope="itemscope" and that what I did. I hope you find this helpful.