My Alienware m11x Review

Alienware M11xOn hearing Dell have officially acknowledged there is an issue with all Alienware M11x screen hinges (duh!) regardless of warranty status, I decided to write a belated review on my experience of owning this very laptop.

After having a Dell XPS M1210 since 2007, I thought it was about time I looked for a more substantial piece of kit. I believe I still would have been completely happy with my M1210 to this very day if it wasn’t for the dependence of 64 bit architecture on some key software applications I use.

The thing that really impressed me about the m11x was the portability factor. It really is quite a marvel on how the guys at Dell managed to pack so much high spec goodness in such as small light-weight package. I decided to go all out on my laptop specification. As my Dad has always told me: “Always get the best for the time”, especially since technology progresses so fast nowadays and you want your piece of kit to last as long as it can. Very wise my old man!

So my specification is as follows:

  • Processor: Intel i7 1.5Ghz (overclockable to 2.6Ghz)
  • RAM: 8GB
  • Hard Disk Drive: 320GB
  • 2GB nVidia Geforce GT 540M Graphics Card
  • Flashy lights!!! (as standard)

All this for around the £1000 mark. Not bad considering I paid in excess of over £1200 for my old Dell XPS laptop and I was getting quite a lot for my money compared to other gaming machines on the market. A laptop with this setup doesn’t fail to impress when it comes to Windows Experience ratings:

Alienware M11x Experience Index

The experience rating is based on a non-overclocked processor running at 1.5Ghz. I will be interested to see how the rating fairs when the processor has been overclocked…soon as I figured out how to do this.

The Good, The Bad and The Ugly

The Good

Aside from the form factor, the Alienware M11x manages to pack a real punch when performing any task. From opening Notepad (lol!) to playing the most performance and graphic hungry games. I will admit, I am not much of a gamer but my experiences with playing Dirt 3 has been absolutely phenomenal especially with compared to its console counterparts. The icing on the cake has to be playing games externally through a High Definition TV via HDMI.

The cool customisations and light effects you can make to the keyboard, front and logo lights will satisfy the inner geek. A really nice touch to keep your hardware looking different.

The build quality is similar to what I’d expect from Dell. The good and bad. The matte black soft-touch finish allows the laptop to be somewhat scratch resistant and not much of a finger-print magnet compared to earlier editions of the M11x. All panels and covers are rock solid and all sockets and ports are nice and grippy!

Battery life is greatly increased thanks to the nVidia Optimus switchable graphics, which automatically detects if the application you are running needs full graphics power. I manage to get around 5 hours battery life through general use.

The Bad

Only a minor gripe based on my personal preferences is there is no hard drive status light. My previous Dell laptop had this status light to show me how much my disk drive is working. Useful if your computer is locking up.

It would be nice to have an additional powered USB socket to charge more than one appliance when my Alienware is switched off.

The Synaptics touchpad is as responsive as all the other touchpad’s I’ve used in the past. So no issue there. I am just not too keen on the honeycomb textured surface. The finger doesn’t glide as fluidly as I would have liked.

The Ugly

The well known issue I stated at the beginning of my post – faulty screen hinge. I had known that there have been numerous issues with the hinges even before I made my purchase. You would have thought by the third iteration of the M11x this issue would have been solved by now. Unfortunately, it hasn’t.

The problem I have been experiencing from day one is the keyboard touching the screen when the lid is closed. This has resulted in marks on various points on my screen. Unfortunately, by the time I noticed the damage was done. It seems that the keys on the keyboard are not aligned correctly with the base of the unit.

I will be sure to get both screen and hinges replaced.

Conclusion

Overall, I'm really pleased with the laptop. My only gripe is the hinge issue ruining my screen.

Add MemoryStream File To Kentico Media Library

I needed to be able to pass a file that was stored in a MemoryStream into my Kentico Media Library. In my case, the file was a dynamically generated PDF.

I couldn’t find anything on the web on how I would achieve this. So I decided to have a go creating my own method based on the Media Library API and some very basic examples, as you can see below:

public static string AddFile(MemoryStream file, string fileName, string description, string mediaLibrayName, string folder, string fileExt)
{
    string cleanFileName = MakeValidFileName(fileName).Replace(" ", "-");

    string folderDirectory = HttpContext.Current.Server.MapPath(String.Format("/MyWebsite/media/{0}/{1}/", mediaLibrayName, folder));

    string mediaFilePath = String.Format("{0}{1}.{2}", folderDirectory, cleanFileName, fileExt);

    if (!File.Exists(mediaFilePath))
    {
        #region Create File in Media Library Directory

        //Check if directory exists
        if (!Directory.Exists(folderDirectory))
            Directory.CreateDirectory(folderDirectory);

        file.Position = 0;

        FileStream outStream = new FileStream(mediaFilePath, FileMode.Create, FileAccess.Write);
        file.WriteTo(outStream);
        outStream.Flush();
        outStream.Close();

        #endregion

        #region Add file info to Kentico Media library

        //Media Library Info - takes Media Library Name and Website Name
        MediaLibraryInfo libraryInfo = MediaLibraryInfoProvider.GetMediaLibraryInfo(mediaLibrayName, CMSContext.CurrentSiteName);

        // Get Relative Path to File
        string path = CMS.MediaLibrary.MediaLibraryHelper.EnsurePath(mediaFilePath);

        //Create media file info item
        MediaFileInfo fileInfo = new MediaFileInfo(path, libraryInfo.LibraryID, folder);

        fileInfo.FileTitle = fileName;
        fileInfo.FileDescription = description;

        // Save media file info
        MediaFileInfoProvider.ImportMediaFileInfo(fileInfo);

        #endregion

        return String.Format("/MyWebsite/media/{0}/{1}/{2}.{3}?&ext=.{3}", mediaLibrayName, folder, cleanFileName, fileExt);
    }
    else
    {
        return String.Empty;
    }
}

The method I created is generic enough for you use in your own Kentico site. It provides all the necessary parameters needed  to add an image to a media library of your choice. For example:

AddFile(fileMemStream, “Marketing Issue", “Monthly Marketing Info”, "Private", "Premium Folder", "pdf");

As much as a like using the Kentico CMS platform, I find their API documentation some what lacking in examples on how to use certain methods, especially for a new Kentico developer like myself. I am hoping this is something that will change in the near future.

Google Checkout Error - "Expected Serial Number was not contained in notification acknowledgment”

I’ve been busy lately integrating a payment provider into a site I am working in. After looking at the best payment providers, it came down to either using PayPal or Google Checkout. In the end I decided to use Google’s payment provider (as you can probably tell from my post title).

Before I start this post, I assume you have already created a sandbox Google Checkout account and know your way around setup and integration.

For my own Google Checkout integration, I needed to be able to add new transaction item in my database only if a customer has ordered an item and if their payment is approved. This is where Google’s call-back notification service comes into play.

I decided to use an synchronous notification process based on one the sample code that can be downloaded here: http://code.google.com/p/google-checkout-dotnet-sample-code/downloads/list.

Just using the sample code provided by Google out-the-box caused issues. It seems that the code samples do not correctly reflect version changes to the payment provider. One of the repeating errors I got was: “Expected serial number was not contained in notification acknowledgement”.

Google Checkout ErrorList

You will only receive this error only if you are using API version 2.5 and have “Notification Filtering” enabled in account settings. Unfortunately, notification filtering is something I needed to ensure both Google Checkout and my database transactions were in sync.

The Integration Issue error log showed me that the transaction serial number was not getting populated from my call-back notification page.

Google Checkout Error XML Detail

After a lot of debugging it became apparent that the notification page provided by Google Code sample was wrong! It was missing a key case statement: “authorization-amount-notification”. Once this was added no more errors occurred.

If you are using the “notification_extended.aspx”, “notification.aspx” or “notification_handshake.aspx” code samples. Be sure to make the following change:

<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="GCheckout" %>
<%@ Import Namespace="GCheckout.AutoGen" %>
<%@ Import Namespace="GCheckout.Util" %>
<script runat="server" language="c#">

  public string SerialNumber = "";

  void Page_Load(Object sender, EventArgs e) {
    // Extract the XML from the request.
    Stream RequestStream = Request.InputStream;
    StreamReader RequestStreamReader = new StreamReader(RequestStream);
    string RequestXml = RequestStreamReader.ReadToEnd();
    RequestStream.Close();
    // Act on the XML.
    switch (EncodeHelper.GetTopElement(RequestXml)) {
      case "new-order-notification":
        NewOrderNotification N1 = (NewOrderNotification) EncodeHelper.Deserialize(RequestXml, typeof(NewOrderNotification));
        SerialNumber = N1.serialnumber;
        string OrderNumber1 = N1.googleordernumber;
        string ShipToName = N1.buyershippingaddress.contactname;
        string ShipToAddress1 = N1.buyershippingaddress.address1;
        string ShipToAddress2 = N1.buyershippingaddress.address2;
        string ShipToCity = N1.buyershippingaddress.city;
        string ShipToState = N1.buyershippingaddress.region;
        string ShipToZip = N1.buyershippingaddress.postalcode;
        foreach (Item ThisItem in N1.shoppingcart.items) {
          string Name = ThisItem.itemname;
          int Quantity = ThisItem.quantity;
          decimal Price = ThisItem.unitprice.Value;
        }
        break;
      case "risk-information-notification":
        RiskInformationNotification N2 = (RiskInformationNotification) EncodeHelper.Deserialize(RequestXml, typeof(RiskInformationNotification));
        // This notification tells us that Google has authorized the order and it has passed the fraud check.
        // Use the data below to determine if you want to accept the order, then start processing it.
        SerialNumber = N2.serialnumber;
        string OrderNumber2 = N2.googleordernumber;
        string AVS = N2.riskinformation.avsresponse;
        string CVN = N2.riskinformation.cvnresponse;
        bool SellerProtection = N2.riskinformation.eligibleforprotection;
        break;
      case "order-state-change-notification":
        OrderStateChangeNotification N3 = (OrderStateChangeNotification) EncodeHelper.Deserialize(RequestXml, typeof(OrderStateChangeNotification));
        // The order has changed either financial or fulfillment state in Google's system.
        SerialNumber = N3.serialnumber;
        string OrderNumber3 = N3.googleordernumber;
        string NewFinanceState = N3.newfinancialorderstate.ToString();
        string NewFulfillmentState = N3.newfulfillmentorderstate.ToString();
        string PrevFinanceState = N3.previousfinancialorderstate.ToString();
        string PrevFulfillmentState = N3.previousfulfillmentorderstate.ToString();
        break;
      case "charge-amount-notification":
        ChargeAmountNotification N4 = (ChargeAmountNotification) EncodeHelper.Deserialize(RequestXml, typeof(ChargeAmountNotification));
        // Google has successfully charged the customer's credit card.
        SerialNumber = N4.serialnumber;
        string OrderNumber4 = N4.googleordernumber;
        decimal ChargedAmount = N4.latestchargeamount.Value;
        break;
      case "refund-amount-notification":
        RefundAmountNotification N5 = (RefundAmountNotification) EncodeHelper.Deserialize(RequestXml, typeof(RefundAmountNotification));
        // Google has successfully refunded the customer's credit card.
        SerialNumber = N5.serialnumber;
        string OrderNumber5 = N5.googleordernumber;
        decimal RefundedAmount = N5.latestrefundamount.Value;
        break;
      case "chargeback-amount-notification":
        ChargebackAmountNotification N6 = (ChargebackAmountNotification) EncodeHelper.Deserialize(RequestXml, typeof(ChargebackAmountNotification));
        // A customer initiated a chargeback with his credit card company to get her money back.
        SerialNumber = N6.serialnumber;
        string OrderNumber6 = N6.googleordernumber;
        decimal ChargebackAmount = N6.latestchargebackamount.Value;
        break;
    // Edit: Additional case statement for "authorization-amount-notification"
      case "authorization-amount-notification":
        AuthorizationAmountNotification N7 = (AuthorizationAmountNotification)EncodeHelper.Deserialize(RequestXml, typeof(AuthorizationAmountNotification));
        // Information about a successful reauthorization for an order
        SerialNumber = N7.serialnumber;
        string OrderNumber7 = N7.googleordernumber;
        break;
      default:
        break;
    }
  }
</script>
<notification-acknowledgment xmlns="http://checkout.google.com/schema/2" serial-number="<%=SerialNumber %>" />

Once I have got to grips on using Google Checkout I will add another blog post containing my full call-back solution.

Calculate Time Duration in Seconds, Minutes, Hours & Weeks

For a news site I am currently working on, I needed to display the last time a news article was last published. I wanted to be able to show the duration based on respective major time format. For example, if an article was displayed a couple hours ago, I would want it to to display “2 hours” not “120 minutes”.

More importantly, if an article hadn’t been published to the site more than a week, I don’t want the exact time duration to be displayed. I would prefer the following message: “more than a week ago”. This way, if the site administrator gets really lazy the website viewer will not know the exact time period the site was last updated.

Code:

public class TimePassed
{
    public static string GetPassedTime(DateTime since)
    {
        TimeSpan ts = DateTime.Now.Subtract(since);

        if (ts.Days <= 7)
        {
            switch (ts.Days)
            {
                case 0:
                    switch (ts.Hours)
                    {
                        case 0:
                            switch (ts.Minutes)
                            {
                                case 0:
                                    return String.Format("{0} seconds ago", ts.Seconds);
                                case 1:
                                    return "1 minute ago";
                                default:
                                    return String.Format("{0} minutes ago", ts.Minutes);
                            }
                        case 1:
                            return "1 hour ago";
                        default:
                            return String.Format("{0} hours ago", ts.Hours);
                    }
                case 1:
                    return "yesterday";
                default:
                    return String.Format("{0} days ago", ts.Days);
            }
        }
        else
        {
            return "more than a week ago";
        }
    }
}

Get CheckBoxList Values Using jQuery

To be able to retrieve values from a ASP.NET CheckBoxList control or a group of HTML checkboxes, use the following jQuery:

$(document).ready(function () {
    var checkboxValues = [];

    $('#<%=MyCheckBoxList.ClientID %> input[type=checkbox]').click(function () {
        $('input[type=checkbox]:checked').each(function () {
            checkboxValues.push(this.value);
        });        
    });
    
    var values = checkboxValues.toString(); //Output Format: 1,2,3
});

If you do use this code snippet on a CheckBoxList, take a look that this article on how to create a custom CheckBoxList control with a value attribute.

ASP.NET CheckBoxList Control With Value Attribute

ASP.NET server controls is a great way to quickly build a page with dynamic functionality. Even though we do not have much of direct control over the way these controls are rendered, they do a pretty good job and its not very often I get annoyed with them.

Until now.

Generally, I find myself using the .Attributes.Add() method when needing to add additional attributes to certain server controls. No problem! In this case, I wanted to add a “value” attribute that will contain the record ID for that checkbox. I can then use this value within my JavaScript. I would have thought a value attribute would already be there. Its perfectly valid HTML mark-up:

<form>
    <input type="checkbox" name="vehicle" value="Volvo" />
    <input type="checkbox" name="vehicle" value="Volkswagen" />
</form> 

For some reason, when I tried to add my custom attributes after my CheckBoxList was databound (as shown below), the attribute was simply ignored.

NewsCheckList.Items[0].Attributes["value"] = "1";
NewsCheckList.Items[1].Attributes["value"] = "2";
NewsCheckList.Items[2].Attributes["value"] = "3";

So I decided the best way forward would be to create a custom CheckBoxList control that would contain a value attribute. I based my code from an old (but very useful) article that can be found here.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.UI;
using System.Collections;
using System.ComponentModel;

namespace Site.WebControls
{
    [DefaultProperty("Text"),
    ToolboxData("<{0}:CheckBoxValueList runat=server></{0}:CheckBoxValueList>")]
    public class CheckBoxValueList : CheckBoxList
    {
        protected override void Render(HtmlTextWriter writer)
        {
            StringBuilder sb = new StringBuilder();
            TextWriter tw = new StringWriter(sb);
            
            HtmlTextWriter originalStream = new HtmlTextWriter(tw);
            base.Render(originalStream);
            string renderedText = sb.ToString();

            int start = 0;
            int labelStart = 0;
            int end = renderedText.Length;

            for (int i = 0; i < this.Items.Count; i++)
            {
                StringBuilder itemAttributeBuilder = new StringBuilder();

                end = renderedText.Length;
                start = renderedText.IndexOf("<input", start, end - start);
                labelStart = renderedText.IndexOf("<label", start, end - start);

                this.Items[i].Attributes.Render(new HtmlTextWriter(new StringWriter(itemAttributeBuilder)));

                renderedText = renderedText.Insert(labelStart + 7, itemAttributeBuilder.ToString() + " ");
                renderedText = renderedText.Insert(start + 7, String.Format("{0} value=\"{1}\" ", itemAttributeBuilder.ToString(), this.Items[i].Value));
                start = renderedText.IndexOf("/>", start, renderedText.Length - start);
            }
            
            writer.Write(renderedText);
        }
    }
}

Has Facebook Redefined Friendship?

Definition Of FriendI was a late bloomer when when it came to joining the social networking giant that is Facebook (around late 2007). The only reason I can remember for ever joining the site was just because all people around me were submitting their profiles like crazy. Not wanting to miss out on this new trend, I decided to “pop” my social networking cherry and take the plunge!

Looking back on my first experience on Facebook I was amazed at how easily I could connect with friends and people I used to know from a past life (school, work etc). Within a few months my Facebook profile spread through the social networking vine in quick haste and found myself receiving friend requests. But it became ever so prevalent that the people who requested me to add them as a friend weren’t people I would necessarily call a friend. I knew of them and that is where my connection ends. So in some ways Facebook has redefined the term “friend”.

Facebook has broken down the friendship barriers considerably. Its made it really easy. Too easy in fact. It was only a couple days ago when my sister said: “Look! I got more friends than you!”. In all honesty I wasn’t really bothered…ok maybe a little. This is where personal feelings come into play.

One of the feelings I will call: “Facebook guilt”. Facebook guilt is when you receive a friend request and don’t act on it. You simply ignore it hoping they would just forget or even worse…remove their request altogether. Hoping by not accepting their friend request you haven’t made an enemy or caused emotional discourse.

Then there is “Facebook rejection”. An example of this is the following conversation I had with with a work mate of mine a few years back:

Anonymous friend: I’ve sent you a Facebook friend request. Me: Ok cool. Anonymous friend: How come you haven’t approved it yet? Me: Mate, I’ve been on holiday for over a week and haven’t checked Facebook yet. I’ll do it today. Anonymous friend: Thanks Surinder!

I was surprised that he took not responding to his friend request as a personal hit.

Social Networking sites have created a trend that makes us more interested in the number of people in our social circles rather than the relationships we have with them.

So where do I stand in the social networking medium? My Facebook activity has drastically declined over the years. Currently, I have 114 friends with majority of them I know quite well and only a handful of them I haven’t really met. Nowadays, I have become more of a tweeter. I just feel that Twitter has met my social needs over Facebook. Its just more flexible and open. If someone likes you they follow you, if not they don’t.

EaseUS Todo Backup Disk Clone Tool…It’s Good!

Earlier today, I decided to upgrade my laptop’s hard drive to a larger capacity disk. As we all know, the most straight-forward method of carrying this out is by cloning the existing drive onto your new disk of choice. Originally, I was planning on purchasing “Acronis True Image” since this is something I’ve used it in the past and makes cloning any disk a cinch!

I decided to look for some freely available cloning software available online, instead of having to pay £30 for software I won’t be using that often. Yes I am that tight! :-) In all honesty, I wasn’t expecting to find anything substantial but I was surprised to find a great piece of cloning software called “EaseUS Todo Backup” free edition. EaseUS Todo Backup software not only had the ability to clone a disk but also had the following useful features:

  • Backup – on selected files, partitions or your entire computer
  • Recovery
  • Scheduled backup plan

I am happy to report that I managed to clone the whole of my disk drive successfully within 2.5 hours (based on 126GB of data). Whoever said nothing in life is free!

You can download EaseUS Todo Backup here.

BlogEngine Disqus Comment Count Fix

For those of you that have decided to opt out of using BlogEngine’s default commenting system and instead, use Disqus platform will probably encounter a minor issue. The minor issue being the fact that the comment count displayed in within post view doesn’t actually work.

I needed to make a few modifications to the way Disqus is used within BlogEngine. I have to say that I wasn’t exactly impressed with the way Disqus was setup within the JavaScript code (maybe over-exaggerating). You’ll see what I mean from the Disqus JavaScript code found in “post.aspx” and “page.aspx”:

<script type="text/javascript">
    var disqus_url = '<%= Post.PermaLink %>';
    var disqus_developer = '<%= BlogEngine.Core.BlogSettings.Instance.DisqusDevMode ? 1 : 0 %>';
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = 'http://<%=BlogEngine.Core.BlogSettings.Instance.DisqusWebsiteName %>.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>

I believe that additional Disqus variable’s should be used, such as:

  • disqus_title - Tells the Disqus service the title of the current page.
  • disqus_identifier -  If this variable is undefined, the page's URL will be used. The URL can be unreliable, such as when renaming an article slug or changing domains, so its recommended using your own unique way of identifying a thread.

After you have made this change, your JavaScript should look like this:

<script type="text/javascript">
    var disqus_title = '<%=Post.Title %>';
    var disqus_identifier = '<%= Post.Id.ToString() %>';
    var disqus_url = '<%= Post.AbsoluteLink %>';
    var disqus_developer = '<%= BlogEngine.Core.BlogSettings.Instance.DisqusDevMode ? 1 : 0 %>';
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = 'http://<%=BlogEngine.Core.BlogSettings.Instance.DisqusWebsiteName %>.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>

You may or may not have noticed the “disqus_url” is getting assigned the Post.AbsoluteLink rather than Post.PermaLink. I prefer using the AbsoluteLink since it will output a nice clean friendly URL.

The next thing we need to do is modify the “PostView.ascx” file found in your themes folder. The line we are looking for is where the Disqus comments link is set.

<% if (BlogEngine.Core.BlogSettings.Instance.ModerationType == BlogEngine.Core.BlogSettings.Moderation.Disqus)
{ %>
    <a rel="nofollow" href="<%=Post.PermaLink %>#disqus_thread"><%=Resources.labels.comments %></a>
<%}
    else
{ %>
    <a rel="bookmark" href="<%=Post.PermaLink %>" title="<%=Server.HtmlEncode(Post.Title) %>">Permalink</a> |
    <a rel="nofollow" href="<%=Post.RelativeLink %>#comment"><%=Resources.labels.comments %> (<%=Post.ApprovedComments.Count %>)</a>   
<%} %>

We need to make two changes to the current comments code. Firstly, we need to add an important missing attribute to the anchor tag. The missing tag is the “data-disqus-identifier”. This is recommended practice by the Disqus Developer guide. Secondly, we need to change the “href” attribute to use an AbsoluteLink instead of a PermaLink. The code should now look like this:

<% if (BlogEngine.Core.BlogSettings.Instance.ModerationType == BlogEngine.Core.BlogSettings.Moderation.Disqus)
{ %>
    <a rel="nofollow" href="<%=Post.AbsoluteLink %>#disqus_thread" identifier="<%= Post.Id.ToString() %>"><%=Resources.labels.comments %></a>
<%}
    else
{ %>
    <a rel="nofollow" href="<%=Post.RelativeLink %>#comment"><%=Resources.labels.comments %> (<%=Post.ApprovedComments.Count %>)</a>   
<%} %>

From personal experience, I believe it is best to carry out the “post.aspx” and “page.aspx” code changes before moving onto the Disqus platform. If you currently have comments that need to be exported to Disqus, I found this exporter tool really useful. Be prepared to carry out a few Disqus imports to get things exactly right.

Output Message Board Comments for Individual Pages In Kentico

I needed to implement a message board for users to comment on individual articles stored within my Kentico site. To achieve this, I decided to use a message board. Initially, what I found when I implemented the message board web part to my article template was that submitted comments for individual articles were getting displayed on all other articles.

In my page I am using two Kentico controls: MessageBoardViewer to output the list of comments and MessageBoard for the comments form.

<%@ Register Src="/CMSWebParts/MessageBoards/MessageBoard.ascx" TagName="MessageBoard" TagPrefix="cms" %>
<%@ Register Src="/CMSWebParts/MessageBoards/MessageBoardViewer.ascx" TagName="MessageBoardViewer" TagPrefix="cms" %>

<cms:MessageBoardViewer ID="MessageBoardViewer1" runat="server" Enabled="true" HideControlForZeroRows="false" DisplayOnlyApproved="true" DisplayToRoles="Registered;Paid" ShowForDocumentTypes="NewsSite.News" ZeroRowsText="No Messages in viewer" TransformationName="Community.Transformations.MessageBoard" AlternatingItemTransformationName="Community.Transformations.MessageBoard"></cms:MessageBoardViewer>
<cms:MessageBoard ID="MessageBoard1" BoardModerated="true" runat="server" BoardUseCaptcha="false" BoardAccess="AllUsers" DisplayToRoles="Paid" BoardOpened="true" BoardRequireEmails="false"  BoardEnableSubscriptions="true" ></cms:MessageBoard>

I came across a fix on the (very informative) Kentico forums whereby a user carried out a where condition on the MessageBoardViewer control to retrieve article comments through the “BoardDisplayName” field:

MessageBoardViewer1.WhereCondition = 
    String.Concat("BoardDisplayName = '", 
                    CMSContext.CurrentDocument.GetValue("Title"), " (", 
                    CMSContext.CurrentDocument.DocumentNamePath, 
                  ")'"); 

Some of you may not know, the Board Display Name field is also used in the Message board section within CMS Desk.

Kentico Message Board Admin

Retrieving comments based on the Board Display Name is in my opinion not the best way. As you can see from the title of my document (above) contains single quotes. This would cause an SQL syntax error (which I did experience).

To get around this, it is best to query the MessageBoardViewer control using the “BoardDocumentID” field. So the code will be as follows:

MessageBoardViewer1.WhereCondition = 
    String.Concat("BoardDocumentID = ", 
                    CMSContext.CurrentDocument.DocumentID); 

If anyone knows of a better way of achieving the same thing. Please leave a comment. I am relatively new to Kentico and probably missed a trick!