Blogengine.NET Recaptcha 0.91 Installation Instructions

by filip 20. February 2010 20:27

NOTE: THESE INSTALLATION INSTRUCTIONS HAVE BEEN UPDATED: To get the latest version, follow the instructions located at:

First, the linked ZIP file contains the updated files for installing the Recaptcha control.


  • Version 0.92 - The recaptcha will no longer get focus after it has been loaded. Install procedure is unchanged from version 0.91.
  • Version 0.91 - Changed the way that Recaptcha is verified.  It should no longer be possible to bypass the recaptcha by manually executing JavaScript on the page.
  • Version 0.9 - Initial Version

Quick Installation

First, back up your files in case you need to revert for any reason. If you have not modified any of the BlogEngine files, you can extract the ZIP linked to above and place all of the files into their proper locations ( the unzipped files should contain a folder structure ). There is no need to recompile.

You may need to force a refresh of your browser on the site, since there is a JavaScript file updated, and most browsers like to cache these files.

Manual Installation

Below is a complete summary of what needs to be altered to make the Recaptcha control work.  You’ll need to do this for any files manually modified in your BlogEngine.NET instance.


This file was not included in the BlogEngine.NET installation, and should be placed in the ‘/App_Code/Controls/’ folder.


There are three functions in this file that need to be updated to the following:

onCommentError: function(error, context) {
        error = error || "Unknown error occurred.";
        var iDelimiterPos = error.indexOf("|");
        if (iDelimiterPos > 0) {
            error = error.substr(0, iDelimiterPos);
            // Remove numbers from end of error message.
            while (error.length > 0 && error.substr(error.length - 1, 1).match(/\d/)) {
                error = error.substr(0, error.length - 1);
        if( document.getElementById('recaptcha_response_field') )
        alert("Sorry, the following error occurred while processing your comment:\n\n" + error);
addComment: function(preview) {
        var isPreview = preview == true;
        if (!isPreview) {
            this.$("status").innerHTML = BlogEngine.i18n.savingTheComment;
        var author = BlogEngine.comments.nameBox.value;
        var email = BlogEngine.comments.emailBox.value;
        var website = BlogEngine.comments.websiteBox.value;
        var country = BlogEngine.comments.countryDropDown ? BlogEngine.comments.countryDropDown.value : "";
        var content = BlogEngine.comments.contentBox.value;
        var notify = BlogEngine.$("cbNotify").checked;
        var captcha = BlogEngine.comments.captchaField.value;
        var replyToId = BlogEngine.comments.replyToId ? BlogEngine.comments.replyToId.value : "";
        var recaptchaResponseField = document.getElementById('recaptcha_response_field');        
        var recaptchaResponse = recaptchaResponseField ? recaptchaResponseField.value : "";
        var recaptchaChallengeField = document.getElementById('recaptcha_challenge_field');
        var recaptchaChallenge = recaptchaChallengeField ? recaptchaChallengeField.value : "";
        var avatarInput = BlogEngine.$("avatarImgSrc");
        var avatar = (avatarInput && avatarInput.value) ? avatarInput.value : "";
        var callback = isPreview ? BlogEngine.endShowPreview : BlogEngine.appendComment;
        var argument = author + "-|-" + email + "-|-" + website + "-|-" + country + "-|-" + content + "-|-" + notify + "-|-" + isPreview + "-|-" + captcha + "-|-" + replyToId + "-|-" + avatar + "-|-" + recaptchaResponse + "-|-" + recaptchaChallenge;
        WebForm_DoCallback(BlogEngine.comments.controlId, argument, callback, 'comment', BlogEngine.onCommentError, false);
        if (!isPreview && typeof (OnComment) != "undefined")
            OnComment(author, email, website, country, content);
appendComment: function(args, context) {
        if (context == "comment") {
            if( document.getElementById('recaptcha_response_field') )
            if( args == "RecaptchaIncorrect" )
               if( document.getElementById("spnCaptchaIncorrect") ) document.getElementById("spnCaptchaIncorrect").style.display = "";
                if( document.getElementById("spnCaptchaIncorrect") ) document.getElementById("spnCaptchaIncorrect").style.display = "none";
                var commentList = BlogEngine.$("commentlist");
                if (commentList.innerHTML.length < 10)
                    commentList.innerHTML = "<h1 id='comment'>" + BlogEngine.i18n.comments + "</h1>"
                // add comment html to the right place
                var id = BlogEngine.comments.replyToId ? BlogEngine.comments.replyToId.value : '';
                if (id != '') {
                    var replies = BlogEngine.$('replies_' + id);
                    replies.innerHTML += args;
                } else {
                    commentList.innerHTML += args;
           = 'block';
                // reset form values
                BlogEngine.comments.contentBox.value = "";
                BlogEngine.comments.contentBox = BlogEngine.$(;
                BlogEngine.$("status").className = "success";
                if (!BlogEngine.comments.moderation)
                    BlogEngine.$("status").innerHTML = BlogEngine.i18n.commentWasSaved;
                    BlogEngine.$("status").innerHTML = BlogEngine.i18n.commentWaitingModeration;
                // move form back to bottom
                var commentForm = BlogEngine.$('comment-form');
                // reset reply to
                if (BlogEngine.comments.replyToId) BlogEngine.comments.replyToId.value = '';
                if (BlogEngine.$('cancelReply')) BlogEngine.$('cancelReply').style.display = 'none';
        BlogEngine.$("btnSaveAjax").disabled = false;

/User controls/CommentView.ascx.cs

One method in this file needs to be updated:

/// <summary>
    /// Processes a callback event that targets a control.
    /// </summary>
    /// <param name="eventArgument">A string that represents an event argument to pass to the event handler.</param>
    public void RaiseCallbackEvent(string eventArgument)
        if (!BlogSettings.Instance.IsCommentsEnabled)
        string[] args = eventArgument.Split(new string[] { "-|-" }, StringSplitOptions.None);
        string author = args[0];
        string email = args[1];
        string website = args[2];
        string country = args[3];
        string content = args[4];
        bool notify = bool.Parse(args[5]);
        bool isPreview = bool.Parse(args[6]);
        string sentCaptcha = args[7];
        //If there is no "reply to" comment, args[8] is empty
        Guid replyToCommentID = String.IsNullOrEmpty(args[8]) ? Guid.Empty : new Guid(args[8]);
        string avatar = args[9];
        string recaptchaResponse = args[10];
        string recaptchaChallenge = args[11];
        if (!isPreview && recaptcha.RecaptchaEnabled && recaptcha.RecaptchaNecessary)
            if (!recaptcha.ValidateAsync(recaptchaResponse, recaptchaChallenge))
                _Callback = "RecaptchaIncorrect";
        string storedCaptcha = hfCaptcha.Value;
        if (sentCaptcha != storedCaptcha)
        Comment comment = new Comment();
        comment.Id = Guid.NewGuid();
        comment.ParentId = replyToCommentID;
        comment.Author = Server.HtmlEncode(author);
        comment.Email = email;
        comment.Content = Server.HtmlEncode(content);
        comment.IP = Request.UserHostAddress;
        comment.Country = country;
        comment.DateCreated = DateTime.Now;
        comment.Parent = Post;
        comment.IsApproved = !BlogSettings.Instance.EnableCommentsModeration;
        comment.Avatar = avatar.Trim();
        if (Page.User.Identity.IsAuthenticated)
            comment.IsApproved = true;
        if (website.Trim().Length > 0)
            if (!website.ToLowerInvariant().Contains("://"))
                website = "http://" + website;
            Uri url;
            if (Uri.TryCreate(website, UriKind.Absolute, out url))
                comment.Website = url;
        if (!isPreview)
            if (notify && !Post.NotificationEmails.Contains(email))
            else if (!notify && Post.NotificationEmails.Contains(email))
            SetCookie(author, email, website, country);
        string path = Utils.RelativeWebRoot + "themes/" + BlogSettings.Instance.Theme + "/CommentView.ascx";
        CommentViewBase control = (CommentViewBase)LoadControl(path);
        control.Comment = comment;
        control.Post = Post;
        using (StringWriter sw = new StringWriter())
            control.RenderControl(new HtmlTextWriter(sw));
            _Callback = sw.ToString();

/User controls/CommentView.ascx

Finally, the following line needs to be added to this file, at the location you would like the Recaptcha control to appear. Remember to update your TabIndexes for proper tab button navigation.

<blog:RecaptchaControl ID="recaptcha" runat="server" TabIndex="8" />

Recaptcha Settings

You can edit the Recaptcha settings via the Extension Manager. You probably should enter in your own Public and Private keys, as the provided keys are for this site ( they are global, so they will work on other sites ).

Recaptcha Theming

There are 4 built-in themes that you can change via the ExtensionManager. You can also create your own theme by adding it in the following way to the Recaptcha control:

<blog:RecaptchaControl ID="recaptcha" runat="server" TabIndex="8" Theme="MyCustomTheme" />

For detailed instructions on what your theme needs to look like, visit the Recaptcha site.

Tags: , , , , , ,

Web Development

Async Recaptcha for BlogEngine.NET 1.6

by filip 20. February 2010 02:03

UPDATE: The latest installation instructions and files are available by following this link:

The instructions on this page are no longer valid for the latest version.

I really am getting sick of the spam that is appearing on this blog, so yesterday I decided to implement Recaptcha for BlogEngine.NET. I had three main goals:

  1. Easy to implement – as any control, it needs to be pretty trivial to add it to an existing blog instance
  2. Configurable via the Extensions panel – I really like what BlogEngine has done with their ability to edit extensions, so I wanted the control to be configurable in this way
  3. It had to be asynchronous – when a user posts a comment, I don’t want the whole page to refresh

The final solution is pretty close to what I had in mind.  The extension is a single file that needs to be inserted into the ‘/App_Code/Controls’ folder.  In addition, two lines of code need to be added/modified in the ‘/User controls/CommentView.ascx’ file.

Just a quick note to anyone who may want to implement this on your own.  I have noticed that once in a while, when using IE, I get an error that BlogEngine is not defined and the error points to the following file: ‘/js.axd?path=%2fadmin%2fwidget.js&v=’. The changes involved shouldn’t have any effect on this, but I am not 100% sure if this is due to my code or not. If it is, hopefully I’ll figure it out in the next day or so, as I’m way to tired right now. [ this should now be fixed in the 0.95 version ]

To implement the Recaptcha control, the following steps need to be taken:

  1. Download, extract the file in it, and place the file in the /App_Code/Controls folder.
  2. Open ‘/User controls/CommentView.ascx’ in the editor of your choice and put in the following line at the spot where you would like your control to appear.  On this blog, the code is right above the submit button when posting comments.

    <blog:RecaptchaControl ID="recaptcha" runat="server" TabIndex="8" /><br />

    In the example above, I also put in an extra line break for some extra white space.  I have also put in a TabIndex ( and changed the TabIndex of the submit button ).
  3. The onClick event on the actual submit button needs to change. If you have not modified the code at all, the submit button should look like this:

    <input type="button" id="btnSaveAjax" value="<%=Resources.labels.saveComment %>" onclick="return BlogEngine.validateAndSubmitCommentForm()" tabindex="7" />

    For the recaptcha code to work, the submit button needs to change to the following:

    <input type="button" id="btnSaveAjax" value="<%=Resources.labels.saveComment %>" onclick="return validateWithRecaptcha()" tabindex="9" />

That’s it.  No recompile is needed, and the Recaptcha control should be functional.  If you go to your extensions section, you’ll notice that the Recaptcha control is there and available for editing.  You can ( and probably should ) create your own account on Recaptcha.netand put in a Public and Private key that corresponds to your site. The keys in there are for my site, but they are “global” keys which should work from any domain. You can also disable the control altogether, change it so it does not appear for logged in users, and change the theme.

The control also allows you to create a custom theme.  To do so, you need to specify the name of the theme directly in the tag in the CommentView.ascx file mentioned above. So, for example:

<blog:RecaptchaControl ID="recaptcha" runat="server" TabIndex="8" Theme=”MyCustomTheme” />

You’ll find the rest of the info about what you need to do to skin the Recaptcha on their site.

As always, let me know if you’d like to see anything added to the control, or if you experience any bugs with it.  I’ll be looking into the bug mentioned above this weekend to determine if the control is actually causing this issue mentioned above.  I have a feeling I know how to fix it even if it isn’t, but I’ll just have to run some tests…

Tags: , , , , , ,

Web Development

Disabling Comments for Blacklisted IP addresses

by filip 18. February 2010 16:28

I was reading through the discussions on Codeplex for Blogengine.NET, and a user (ALBsharah) had an interesting idea for controlling spam: if we can already determine that a user is blacklisted based on the IP, do not even give them the ability to post new comments.  The thread is available here.

Making this change to BE is relatively simple.  The first change was actually creating a function which would return whether the user is blacklisted.  Comment moderation methods seem to be located in the file in the BlogEngine.Core project. All of this code was actually already written in another method ( ModeratedByRule ), I just needed to extract that code out of that method, and modify that method to use the new code – I hate having code that does the same thing in two different places.  So, where before the function looked like the following:

   1: static bool ModeratedByRule(Comment comment)
   2:         {
   3:             // trust authenticated users
   4:             if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
   5:             {
   6:                 comment.IsApproved = true;
   7:                 comment.ModeratedBy = "Rule:authenticated";
   8:                 return true;
   9:             }
  11:             int blackCnt = 0;
  12:             int whiteCnt = 0;
  14:             // check if this user already has approved or
  15:             // rejected comments and belongs to white/black list
  16:             foreach (Post p in Post.Posts)
  17:             {
  18:                 foreach (Comment c in p.Comments)
  19:                 {
  20:                     if (c.Email.ToLowerInvariant() == comment.Email.ToLowerInvariant()
  21:                         || c.IP == comment.IP)
  22:                     {
  23:                         if (c.IsApproved)
  24:                             whiteCnt++;
  25:                         else
  26:                             blackCnt++;
  27:                     }
  28:                 }
  29:             }
  31:             // user is in the white list - approve comment
  32:             if (whiteCnt >= BlogSettings.Instance.CommentWhiteListCount)
  33:             {
  34:                 comment.IsApproved = true;
  35:                 comment.ModeratedBy = "Rule:white list";
  36:                 return true;
  37:             }
  39:             // user is in the black list - reject comment
  40:             if (blackCnt >= BlogSettings.Instance.CommentBlackListCount)
  41:             {
  42:                 comment.IsApproved = false;
  43:                 comment.ModeratedBy = "Rule:black list";
  44:                 return true;
  45:             }
  46:             return false;
  47:         }

It now looks like this:

   1: /// <summary>
   2:         /// Checks if the IP or email is blacklisted
   3:         /// </summary>
   4:         /// <param name="IP">The IP of the user</param>
   5:         /// <param name="Email">The Email of the user</param>
   6:         /// <returns>true if blacklisted, false if whitelisted, or null if undetermined</returns>
   7:         public static Nullable<bool> IsBlacklisted(string IP, string Email)
   8:         {
   9:             int blackCnt = 0;
  10:             int whiteCnt = 0;
  12:             // check if this user already has approved or
  13:             // rejected comments and belongs to white/black list
  14:             foreach (Post p in Post.Posts)
  15:             {
  16:                 foreach (Comment c in p.Comments)
  17:                 {
  18:                     if (
  19:                         ( Email != String.Empty && c.Email.ToLowerInvariant() == Email.ToLowerInvariant())
  20:                         || c.IP == IP
  21:                         )
  22:                     {
  23:                         if (c.IsApproved)
  24:                             whiteCnt++;
  25:                         else
  26:                             blackCnt++;
  27:                     }
  28:                 }
  29:             }
  31:             // user is in the white list - approve comment
  32:             if (whiteCnt >= BlogSettings.Instance.CommentWhiteListCount)
  33:             {
  34:                 return false;
  35:             }
  37:             // user is in the black list - reject comment
  38:             if (blackCnt >= BlogSettings.Instance.CommentBlackListCount)
  39:             {
  40:                 return true;
  41:             }
  43:             return null;
  44:         }
  46:         static bool ModeratedByRule(Comment comment)
  47:         {
  48:             // trust authenticated users
  49:             if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
  50:             {
  51:                 comment.IsApproved = true;
  52:                 comment.ModeratedBy = "Rule:authenticated";
  53:                 return true;
  54:             }
  56:             Nullable<bool> isBlacklisted = IsBlacklisted(comment.IP, comment.Email);
  58:             // user is in the white list - approve comment
  59:             if (isBlacklisted == false)
  60:             {
  61:                 comment.IsApproved = true;
  62:                 comment.ModeratedBy = "Rule:white list";
  63:                 return true;
  64:             }
  66:             // user is in the black list - reject comment
  67:             if (isBlacklisted == true)
  68:             {
  69:                 comment.IsApproved = false;
  70:                 comment.ModeratedBy = "Rule:black list";
  71:                 return true;
  72:             }
  73:             return false;
  74:         }

Next, a very simple change needed to be made to the CommentView.ascx.cs file, which actually handles the viewing of the comment section.

The following line was modified ( in /User controls/CommentView.ascx.cs):

   1: if (BlogSettings.Instance.IsCommentsEnabled)

To this:

   1: if (
   2:                 BlogSettings.Instance.IsCommentsEnabled && 
   3:                 BlogEngine.Core.CommentHandlers.IsBlacklisted(Request.UserHostAddress, String.Empty) != true
   4:                 )

Both of the updated files are available for download by following this link.  Hopefully I didn’t mess anything up ( this was all about 5 min of code, thanks to BenAmada for his tips ).

Tags: , , , ,

Web Development

Extract Formatted Text From Excel Cell With C# (Rich Text Format)

by filip 16. September 2009 23:17

I was writing an application that needed to convert text in a cell in an Excel workbook to HTML. It is fairly trivial to get formatting for the entire cell, but each individual character in the cell could have different formatting itself, so I needed something more specific than cell-level formatting info.

At first, I started using the Excel.Range.get_Characters( pos, len ) method to get info out of the cell.  The code would loop through all characters, get them one by one, and check the formatting.  For example:

   1: Microsoft.Office.Interop.Excel.Range Range = (Microsoft.Office.Interop.Excel.Range)Cell;
   2: int TextLength = Range.Text.ToString().Length;
   3: for (int CharCount = 1; CharCount <= TextLength; CharCount++)
   4: {
   5:     Microsoft.Office.Interop.Excel.Characters charToTest = Range.get_Characters(CharCount, 1);
   6:     bool IsBold = (bool)charToTest.Font.Bold;
   7:     bool IsItalic = (bool)charToTest.Font.Italic;
   8:     // other formatting tests here
   9: }

However, that method proved to be incredibly slow for cells that have more than just a few characters.  For cells that have 1000+ characters, it would take several minutes to run the test across all characters. I kept playing around with different ways to speed up the whole process, but it just became apparent that making the call to Excel to get all of this information was not going to be acceptable.

Finally, I think I’ve found the solution. It is possible to copy the text from a cell to the clipboard, and then use the Clipboard class to retrieve the formatted text, and parse it with C#. I ended up using the System.Windows.DataFormats.Rtf format to extract the data from the clipboard in the following way:

string rtf = (string)System.Windows.Clipboard.GetData(System.Windows.DataFormats.Rtf);

Then, I create a System.Windows.Forms.RichTextBox, and use that to parse the data. The following is a sample of the solution, and it is reasonably quick.

   1: Microsoft.Office.Interop.Excel.Range Range = (Microsoft.Office.Interop.Excel.Range)Cell;
   2: Range.Copy(System.Reflection.Missing.Value);
   4: string rtf = (string)System.Windows.Clipboard.GetData(System.Windows.DataFormats.Rtf);
   5: System.Windows.Forms.RichTextBox rtb = new System.Windows.Forms.RichTextBox();
   6: rtb.Rtf = rtf;
   8: int CharCount = rtb.Text.Length;
  10: for (int CharNum = 0; CharNum < CharCount; CharNum++)
  11: {
  12:    rtb.Select(CharNum, 1);
  13:    System.Drawing.Font Font = rtb.SelectionFont;
  14:    bool IsCharBold = Font.Bold;
  15:    bool IsCharUnderline = Font.Underline;
  16:    bool IsCharItalic = Font.Italic;
  18:    // other code here

I was also asked about getting the color in the comments. To get the color, you can use:

System.Drawing.Color color = rtb.SelectionColor;

There are also other properties of rtb dealing with selection, such as SelectionAlignment, SelectionBackColor, etc. See the RichTextBox class for more info.

Tags: , , ,

Windows Development

Something is seriously wrong with our airlines

by filip 13. May 2009 23:17

Today I was reminded exactly of why I hate flying.  A couple months ago, I purchased a ticket to fly to Krakow, Poland.  I live in Cincinnati, and it is not possible to have a direct flight into Poland.  The best you can do from CVG is a one-stop flight.  I really wanted as few stops as possible, so I paid $1360 for a ticket with only one stop ( Chicago ). I could have saved myself $600 on the ticket and bought a multi-stop ticket for around $700, but I just wanted the trip to be as quick as possible.

Well, about 8 hours ago I show up at the airport, do the check-in, go through security.  However, the departure time never really approaches, because every 20 minutes or so the flight gets more and more delayed.  I had about a 100 minute window to switch planes in Chicago, and pretty soon it becomes apparent that even if I get on the plane to Chicago, I will miss my flight to Poland.

The “manager” at the American Eagle ( the regional partner to the Polish airlines partner – American Airlines ) that is handling the flight to Chicago is not very helpful.  He keeps repeating that it is not their problem, since all these delays are due to weather and not some mechanical failure ( as I’m talking to the manager, I can see through the terminal window how Delta flights are departing and landing – something he claims is not possible to do right now ).

At this point, I’m told that I can either fly to Chicago and I’m “on my own” ( whatever that means ), or I can try to reschedule my flight tomorrow. Since the thought of sleeping in the airport terminal in O’hare is not very appealing, I decide to switch my flight to the next day – what else can I really do? I end up with a flight to Chicago, then a flight into Warsaw, and the ( 6 hours later! ) a flight to Krakow. I’m now arriving a day late, on a trip that will take 13 hours more than my planned trip.

I realize that certain weather conditions can wreck havoc on flight schedules. But I’m not buying any of this BS that the airlines have absolutely no control over this aspect of flying. I’ve flown many times in the past, and it is standard practice to have a flight arrive, people leave the plane, then the new flyers board the plane, and have the plane take off in like 45 minutes. Basically, very little – if no time at all – is factored in for delays due to weather. The airlines could easily fix this problem by allowing more time in between arriving and departing flights. But that would hurt their bottom line, and they prefer to shift all the blame to the weather ( thus escaping any liability ) instead of actually admitting that their scheduling is messed up.

I really think we need a new system that requires airlines to allow for more time between arrival and departure. Don’t make it mandatory, but if the airline decides that they want to continue their current practice, require them to compensate the passengers $20 per hour that their flight is delayed, and something like $100 per added stop to their travel plans. If airlines couldn’t simply blame everything on the weather, I’m sure they would very quickly arrive at some solution that would improve the status quo.



Bloodforge Band

Looking for the Bloodforge band site? Click here.

About Filip Stanek

Death Note Pic I'm a developer at ACG in Cincinnati, OH. I like ASP.NET, Flash, and other web technologies, & enjoy playing chess, video games, etc.

Currently playing:


Month List