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!


Tuesday, January 15, 2013

HOWTO: Add semantic markup to automatically published content in Sitefinity

One of my favorite features in Sitefinity 5 is the "Alternative publishing system". Basically, you get a number of inbound and outbound pipes and you can miss and match them in order to publish content in or out of your CMS via different channels. This is extremely convenient and extensible framework, which can be of great help to accomplish not so common scenarios. Just a few examples of what can be achieved - you can easily expose all your blog posts as a RSS feed, you can take a RSS feed and publish the items in it as blog posts, news items, tweet it, etc. What's even better - you can define your own custom inbound and outbound pipes to extend this system and suit it to your needs (if only there were enough documentation about this).

The Scenario

What we need for one of our projects is to automatically publish content from external blogging frameworks into our Sitefinity project. This is easily accomplish with taking RSS feeds from the external platforms and setting them as inbound pipe and publishing them as blog posts. This works greatly out of the box. The problem comes when the search engines index our automatically published content - since this content will be exact duplicate, we have to decorate it the proper way so our SEO rank doesn't suffer. We have to set the following markup in our page, to designate the original source of this content:
<link rel="canonical" href="[original url]" />

Extending the Blogs module

First, we need a new field to keep the source of a blog post. Every post that is automatically published will have this field filled with the url of the original blog post. The rest will have just an empty field. This is easy to be done through the administration: Go to Blogs module -> Click on a random blog -> "Custom fields for posts" -> Click on "Add a field". Choose appropriate name (let's say CanonicalLink) for the field and set it to a short text type. Done.

After that we need to write some logic that adds the suitable markup when this field is not empty. To achieve this, the easiest way I found is to derive from "Telerik.Sitefinity.Modules.Blogs.Web.UI.Public.DetailPostView" class, override the InitializeControls method and implement the logic I need there.

Finally, we need to set this modified class as a detail view for blog posts - this is done again through the administration: Administration -> Settings -> Advanced -> Blogs -> Controls -> BlogPostsFrontend -> Views -> DetailBlogPostsFrontend. There is a field "ViewType" here and you should set the fully qualified typename of the modified class. Hitting save will modify some config files, so they should be checked outand the app pool user should have permission to modify them.

So far so good, if we have the field filled in - the proper markup will be rendered. We need to fill the field now.

Extending the Alternative publishing system

As I mentioned the backbone of this system are inbound pipes and outbound pipes. These are basically the input and the output of the system. For this task, we need to extend the default outbound pipes for Sitefinity content and make it to fill the custom field we already created. So, we derive from the Telerik.Sitefinity.Publishing.Pipes.ContentOutboundPipe :

As you can see, I've override the SetPropertiesThroughPropertyDescriptor method to set the custom field I need. Using reflection (no other way unfortunately), I look for a property called "Link" in the wrapperObj - this is the object Sitefinity provides to the outbound pipe, so the pipe can produce the Sitefinity content.

There is another static method used to register the pipe. Here comes the bad news - I couldn't find a way to replace the default outbound pipe with the modified one, so I had to add a new one. This might be incovenient for the end user and leads to misunderstanding sometimes (probably hiding the default one from the UI with css is an option). Everything I tried with removing the default one or replacing it - leads to runtime errors. So, here is how to add a new outbound pipe - subscribe for the initialized event that Sitefinity exposes and call the static method that is already provided in the outbound pipe:

Well, that's all - after you build and restart you will get your new outbound pipe as an option in the dialog:
 As I said, you will have to use the Enhanced Sitefinity content pipe instead of the default one - Sitefinity content. It will act as default one in any other way, but will fill the original link in the custom field we added. I hope this will help someone. Special thanks to Boyan Barnev for helping me with this one, I wouldn't do it without his help :)

Monday, January 7, 2013

5 steps that made my blog happy

I started by programming blog two years ago and for the first year and a half it has been quite unsuccessful. I still don’t say it something big, but there is definitely something that changed the trend and help me to increase my visits – I even meet some milestones I though to be quite far away six months ago.
The thing that helped me a lot and bring me visible results (will shows some stats later in the post) in almost no time was the book: Technical Blogging: Turn Your Expertise into a Remarkable Online Presence It’s a great book for everyone that wants to establish some kind of technical blog (programming, photography, whatever you can think of) and has never done it before. Although, the main goal of the book is to help you get money from your blog, which is definitely not my goal, there are still quite a few very useful (and most of them easy to do) tips that you probably haven’t think of. It will give you guidance for all kind of things and decisions you have to make – starting from the name and the domain of your new blog, through your blogging behavior – how often to blog, when to publish your content, what kind of stuff to include. Finally, you will get very good ideas on how to popularize your web space – this is probably the most important part, no matter how good are you in writing and how interesting is your content, if no one reads it – it is in vain. 

1. Choose wisely your domain name

The first thing I did after reading the book (actually while still reading) was to register my own domain and to stop using the default one from blogger. It’s important to do this before invest any time popularizing your blog, otherwise you will be tightly coupled to the blogging platform you choose. Now if I happen to change my blogging platform all credits I have earned in terms of SEO will not suffer. Also, there are many sources that claim that top level domains are better threated than the lower one. Choose wisely, as future changes will inevitably hearth your popularization – you will get lots of advices in the book and tons of them in the internet.

2. Getting the layout work for you

After registering and setting up my new domain, I realized I need to do some changes in the layout of my pages. The author made me think what is the most important thing I want to achieve at this stage with my blog and to arrange the design so it leads my users to this goal. As a total beginner I decided that for the first couple of years my main goal would be to build loyal visitors (as much as I can).
Because of this, I put the RSS subscribe button in the area that is claimed to be most influential – the top right corner. The effect I noticed was a increased activity in my subscribers and readers through this channel. Feedburner can give you very useful statistics about what happens with your rss. I also added list with top 5 of my most read posts, so people can easily access other interesting stuff after reading their article. This action brought me increased “Pages/visit” ratio.
I have missed something else – to present myself. I didn’t have an About me page. Although this seemed not so important at first glance, if I want loyal visitors that follow me – I need to make them believe me, I need to show some credibility and give them the context of my posts. The statistics shows that even though the page is not from the popular ones, there have been 50-60 that are interested in who I am and if they get to this page – they are potential followers.

3. Build your blogging behavior and follow it as strictly as possible

I felt from the beginning that how often I post, at what time of the day, at what topics is very crucial for my success, but I didn’t know how to approach. It turned out that there is no a silver bullet and a single answers – it depends on so many things that experimenting is the best approach I think.
For example, I try to post up to 3 times a month (I just don’t have time for more). I never publish posts on consequent days and noticed that the most convenient time for publishing is Monday, Wednesday or Thursday in the morning (BG time). I usually prepare my post in the weekends when I have spare time and schedule them for a given time slot. Otherwise, no one will read what I published during the weekend and it will get burden
One of the best advices I found in the book is to interact with the community. I’ve been most successful when show opinion on trending topics. Unfortunately, this is very time consuming and I rarely have time to do this, but when I do it – it is always rewarding.

4. Popularization

I guess this was the aspect I sucked most – I did almost nothing, just sharing my content in the some social networks. The first thing I did was to find web sites that allows you to submit your links and add them to a rating system. There are tons of them, some are specialized for example in .net technologies like www.dotnetkicks.com and www.dotnettechy.com. Others are generic like www.reddit.com, www.technorati.com or www.stumbleupon.com I started with these and some other and with the time passing I abandoned those that didn’t proved to be useful for me. Again, no receipts here – experiment and evaluate to find the right way.
After that, I thought that it will be a good idea to promote it on link blogs – such blogs that aggregate interesting links from other blogs. I wrote some letters to the authors of all link blogs that I already followed, informing the about my blog an briefly explaining what I write about and who I am (again some credibility here wouldn’t hurt). Some of them wrote me back, some didn’t. Nevertheless, I started to see my post here and there in the link blogs and this made me really happy. When I see my post published in such blog, I try to analyze what made the authors publish it – after all these people are part of my loyal visitors and if they are happy with what they read, there are probably other people like them. This is a free evaluation of my work which I always try to get the best of.
Another very important thing I already mentioned that supports popularization is participating in the community – write comments to blogs you follow, express opinions in forums, take part in Q/A sites (like www.stackoverflow.com) this will bring you much less people than the search engines and other sources but these people will be much more committed to what you write and the chances are to be more engaged.
All this steps increased my traffic directly – this is easy to check by analyzing the analytics data. But they also increase your performance in the search engines and helped my blog to be much more discoverable. Actually, this indirect benefit brought more people than those that comes directly from the sources above.
The book will give you very valuable information about how search engines works, what’s important for them and how to get the best of your content. Still, be careful not to turn SEO into your primary goal – at least for me the top priority is to provide interesting stuff, so I put there most of the effort.

5. Add shape to your content

Writing content for web is a whole new science so I can only scratch this matter in a paragraph or two but still… Readers to today are literally flooded content, so value their time and make their reading easier. Use paragraphs, bullet points, headings, etc. is a must even if you are doing it only for yourself. Moreover, using the right markup will boost your SEO performance as search engines are getting smarter and smarter in analyzing the semantic of your content not just the text.
Cross linking between my own posts is also something very useful (again this is easily visible in the analytics data). Posts that include meaningful links to other related posts from your blog greatly increase your Pages/Visit ratio which is always good.

Finally, the results…

So, what I achieved after all this was done. I will show you some graphs from different sources that will hopefully convince you can easily make a difference.
ga_stats
I guess, no need to tell that all the worked I described start in the end of April 2012. My current goal is to keep this trend going up and higher. Similar graph I see in the Feedburner analytics, too – which is really important for me, this means that I not only manage to bring more visitors, but manage to keep the attention of some of them and make them my followers.
feedburner_stats
1000 unique visitors a month is something that I set as a first milestone after completing the book and I meet it in 7-8 months will no additional investments for advertisements or whatever else (just 10 euro/year for a domain). I’m going to keep moving and always try to deliver useful content for me and my visitors – this is probably the only silver bullet I have and use. I definitely recommend this book to everyone who is just starting and wants to establish its own blog. It will give you a good foundations to step on and will help you to find the best approach and format for you.