Friday, January 25, 2013

Measuring the success of you blog with Sitefinity

No matter what we do, we always try to be successful. Whether we are selling goods, writing content for the fun of it, raising funds for charity - every day we struggle to be better than yesterday (or at least that's what we should do). And no matter what success means to us - most of the cases it is measurable. Number of items sold, total unique visitors for a month, total views of your youtube video.

When it comes to blogs, measuring success might mean a lot of things - counting the comment if your goal is to raise discussions, new leads in your pipeline if your goal is to sell your product, social networks activities if you want to change the world. But most often - it's a combination of a lot of metrics. It's quite easy to check how many these metric one by one for each post, but ultimately you would like to have this information for all blog posts at one place.

The bad news is that Sitefinity doesn't give you a lot of reporting out of the box. But the good news is that its super easy to be done using the Sitefinity API and some other APIs. To show you how easy it is - I have prepared a very simple report for a Sitefinity website, that shows some metric for blog posts - this can very easily be converted to all kind of content - news, custom modules, just name it.

For the presentation, I will use a simple grid and two datepickers for start and end date of the report:
Start time:
<telerik:RadDatePicker ID="rdpStartTime" runat="server">
</telerik:RadDatePicker>
End time:
<telerik:RadDatePicker ID="rdpEndTime" runat="server" />
<telerik:RadButton runat="server" ID="btnGenerateReport" Text="Generate report" />
<br />
<telerik:RadGrid runat="server" ID="rgCommentActivities" AllowSorting="true">
    <MasterTableView CommandItemDisplay="Top">
        <CommandItemSettings ShowExportToExcelButton="true" ShowAddNewRecordButton="false"/>
    </MasterTableView>
    <ExportSettings>
        <Excel Format="Biff" />        
    </ExportSettings>
</telerik:RadGrid>
Nothing, extraordinary here. I will use the built in capabilities of the telerik grid to export to excel - this way I will be able to create all kind of charts later.

And now to populate the data:
 
private void dataBindGrid()
{
    DateTime startTime = rdpStartTime.SelectedDate ?? DateTime.MinValue;
    DateTime endTime = rdpEndTime.SelectedDate ?? DateTime.MaxValue;
    endTime = endTime.AddDays(1);

    UserManager userManager = UserManager.GetManager();

    BlogsManager blogsManager = BlogsManager.GetManager();
    IList<CommentActivityViewModel> activities = new List<CommentActivityViewModel>();

    foreach (BlogPost blogPost in blogsManager.GetBlogPosts().Where(bp => bp.Parent != null && bp.PublicationDate >= startTime && bp.PublicationDate <= endTime && bp.Status == Telerik.Sitefinity.GenericContent.Model.ContentLifecycleStatus.Live))
    {
        CommentActivityViewModel activityReport = new CommentActivityViewModel()
        {

        };
        activityReport.CommentsCount = blogsManager.GetComments().Where(c => c.CommentedItemID == blogPost.Id && c.CommentStatus == Telerik.Sitefinity.GenericContent.Model.CommentStatus.Published).ToList().Count();
        activityReport.BlogPostUrl = blogPost.ItemDefaultUrl;
        activityReport.Blog = blogPost.Parent.Title;
        activityReport.PublishDate = blogPost.PublicationDate;

        var userProfile = userManager.GetUser(blogPost.Owner);
        if (userProfile != null)
        {
            activityReport.Author = userProfile.UserName;
        }

        activityReport.TwitterCount = getTwitterCounts(blogPost);
        activityReport.FacebookCount = getFaceBookTotalCounts(blogPost);
        activities.Add(activityReport);
    }

    rgCommentActivities.DataSource = activities;
    rgCommentActivities.DataBind();
}

private int getTwitterCounts(BlogPost blogPost)
{
    string urlToCheck = UrlHelper.GetSocialLinkByBlogPostId(blogPost.Id);
    if (!string.IsNullOrEmpty(urlToCheck))
    {
        WebClient webClient = new WebClient();
        string twitterAPI = string.Format("http://urls.api.twitter.com/1/urls/count.json?url={0}", urlToCheck);
        var jsonResponse = webClient.DownloadString(twitterAPI);
        JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
        TwitterCountReponse jsonObj = jsSerializer.Deserialize<TwitterCountReponse>(jsonResponse);
        return jsonObj.count;
    }
    return 0;
}

private int getFaceBookTotalCounts(BlogPost blogPost)
{
    string urlToCheck = UrlHelper.GetSocialLinkByBlogPostId(blogPost.Id);
    if (!string.IsNullOrEmpty(urlToCheck))
    {
        WebClient webClient = new WebClient();
        string facebookAPI = string.Format("https://graph.facebook.com/fql?q=SELECT url, normalized_url, share_count, like_count, comment_count, total_count, commentsbox_count, comments_fbid, click_count FROM link_stat WHERE url='{0}'", urlToCheck);
        try
        {
            var jsonResponse = webClient.DownloadString(facebookAPI);
            JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
            FacebookCountReponse jsonObj = jsSerializer.Deserialize<FacebookCountReponse>(jsonResponse);
            return jsonObj.data[0].total_count;
        }
        catch (Exception e)
        {
            return 0;
        }
    }

    return 0;
}

private string getSocialLink(BlogPost blogPost)
{
    PageManager pageManager = PageManager.GetManager();
    PageNode pageNode = null;
    if (blogPost.Parent.DefaultPageId.HasValue)
    {
        try
        {
            pageNode = pageManager.GetPageNode(blogPost.Parent.DefaultPageId.Value);
        }
        catch (Exception e)
        {
            return "";
        }
    }

    if (pageNode != null && pageNode.Urls.Any())
    {
        if (pageNode.Urls.Any(u => u.IsDefault))
            return string.Format("blogs.telerik.com{0}{1}", pageNode.Urls.Where(u=>u.IsDefault).First().Url.Replace(".aspx", "").Trim('~'), blogPost.ItemDefaultUrl);
        else
            return string.Format("blogs.telerik.com{0}{1}", pageNode.Urls.First().Url.Replace(".aspx", "").Trim('~'), blogPost.ItemDefaultUrl);
    }

    return "";
}
No need of too much explanations here, but still - using the Sitefinity API we retrieve all blog posts within the date filter, that are published. We retrieve their author in case we have multiple publishers in our CMS. Of course, you can additionally add all kind of filters - by blog, by user, by category, etc. You can make use of all the data in Sitefinity, but most certainly you need the comment counts.

 For every blog post we ask Twitter and Facebook APIs for the count of activities for this blog posts. You can get all kind of information from facebook - comments, likes, shares, in case you need a more detailed report. Something that worth pointing out is the getSocialLink function. We use it to get the url for a blog post from its default page (the one that is set in the blog's settings). And everywhere in the site we populate this url into all the social networks, so that we can have all the social info for one URL only.

You can easily extend this report to show linkedin and even better. You can use Google Analytics API to get the pageviews, bounce rate and all kind of analytics data for these posts - this way you will create a one stop solution for your blog posts activities. After you have created the UserControl that you need - you can use the backend pages to create a new page, put it in the navigation (I created a new top level menu - "Reporting") and make it available for you or your users. Probably, the only downside is that this is slow - you call a third party APIs on every iteration and this report might need hours to end if you have thousands of blog posts - still better than  nothing.

I hope this have been helpful!