Keith Ratliff

Family, Video Games, D&D

I Have Added reCaptcha to My BlogEngine.Net Blog!

Update: I closed the API hole. Instructions added at the end.

After switching my blog software to use BlogEngine.Net, I noticed an increase in spam attacks to my comments.

The old blog required people to sign in and create an account and while I got the occasional spam in it, it was minimal.

In this one, it was 2 to 3 per day and that bothered me.

Thus, I started to look in to reCaptcha and seeing how to implement it.

reCaptcha actually does a very good thing for developers and wraps its API in to language specific wrappers. They have one for ASP.Net even and it appeared at first glance to be a pretty easy plug and play.

Three days later of continued effort, I have discovered that it's anything but - when it comes to BlogEngine.Net - and not due to reCaptcha's API but rather, due to the fact that it has to be validated at the server and BlogEngine.Net submits comments to posts through AJAX.

In order to implement, the following steps had to be taken:

Convert comment posting to non-Ajax (post back)

Create post-back function that handles all the steps of adding a post.

Add the comment

Nest the comment if needed

Send notifications

This was a pretty incredible amount of effort to figure out and thus, I would like to save others the effort by sharing how to do it.

Enjoy!


Step 1: Download the reCaptcha API and set up an account

You can download the API for .Net here. Save it somewhere you can get back to it. Extract it, you need the extract in the next step.

You can sign up for a free account here. Save your key somewhere safe.

Step 2: Add the reCaptcha library to the BlogEngine.Net web site.

You need to upload the Recaptcha.dll file to the BIN folder of your BlogEngine.Net site. You don't need to copy the PDB file if you are not going to be editing the source code of the reCaptcha library.

If you do plan on editing the reCaptcha library (or looking up code in the case of a failure) then by all means copy the PDB over.

I previously wrote that there was no need to copy the PDB at all and I was surprised it was distributed. I was wrong. The nice folks at reCaptcha (Adrian Godong) pointed out my error. Most specifically, source code can be found here.

If you have no plan to edit the files (or look at them) leave the PDB out.

Step 3: Modify files

This is the biggest step. You have to make changes that let you process the reCaptcha.

I'll go through them all now, one at a time, and explain each edit.

I am starting with BlogEngine.Net version 1.5.0.7 (the latest as of this writing)

First file to modify: /User Controls/CommentView.ascx

This file drives the display of the comments-at-large and that includes the box that you type a new comment in to.

At the top of the file, below the first line (below the line with the @Control directive), add:


<%@ Register TagPrefix="recaptcha" Namespace="Recaptcha" Assembly="Recaptcha" %>

This registers the reCaptcha API control so that we can reference it later.

Since we are going to be working in non-Ajax mode, I added a hidden input to hold the nested comment ID if the user wants to nest their comment (aka "reply to" another comment).

Add the following line


<input type="hidden" id="hiddenReplyID" runat="server" />

place it under the line for "<asp:HiddenField runat="Server" ID="hiddenReplyTo"  />"

I suspect I might have been able to use the "replytoId" but I wasn't 100% sure and went with safe - which was to add my own field.

There is a drop down list for Countries, titled "ddlCountry" - you need to change the view state to save since we will be posting back to the server.


<asp:DropDownList runat="server" ID="ddlCountry" onchange="BlogEngine.setFlag(this.value)" TabIndex="5" EnableViewState="true" ValidationGroup="AddComment" />&nbsp;

The checkbox for "Send me a notification when comments are added" needs to be readable from the post back (not a client-only checkbox) so we need to add the runat tag:


<input type="checkbox" id="cbNotify" runat="server" style="width: auto" tabindex="7" />

Now, wherever you want the reCaptcha control, you need to add it. I placed mine below the checkbox.

To add the control, you add a tag in similar to the following:

      <recaptcha:RecaptchaControl
          ID="recaptcha"
            runat="server"
            PublicKey="REPLACE THIS"            
            PrivateKey="REPLACE THIS"
        />

Make sure to replace the "REPLACE THIS" sections with the keys you saved from Step 2 above.

Since we are going to be posting back when we save, you now need to change the "submit" button to a non-AJAX control and wire it to a function we will describe later called "AddLocalComment".

Here is the new button:


<asp:button id="bnSave" onclick="AddLocalComment" runat="server" tabindex="7" OnClientClick="if(!Page_ClientValidate('AddComment')) return false;" />

Calling the "Page_ClientValidate" will cause the client side validation to "fire" and if it fails, the function will "return false" which causes the browser to ignore that it was clicked (stopping a submit of the form since the fields aren't correct).

Scrolling down a bit you'll find a JavaScript function called "RegisterCommentBox" and in it, a bunch of assignments to variables.

We need to add one for our hidden field we added.

2 new lines, added before the close of the function:

BlogEngine.comments.replyFieldID = '<%= hiddenReplyID.ClientID %>';
BlogEngine.comments.cbNotify = BlogEngine.$("<%=cbNotify.ClientID %>");

That ends the edits for this file, setting it up to be able to handle adding comments in a non-AJAX manner.

Next up, we are going to modify the "blog.js" file in the web application root ("/").

What we are doing is adding handlers to update our hidden field when the nesting and un-nesting occurs.

The "replytoComment" function needs to be changed to the following. This starts on line 81:

    replyToComment: function(id) {

        // set hidden value
        BlogEngine.comments.replyToId.value = id;

        // move comment form into position
        var commentForm = BlogEngine.$('comment-form');
        if (!id || id == '' || id == null || id == '00000000-0000-0000-0000-000000000000') {
            // move to after comment list
            var base = BlogEngine.$("commentlist");
            base.appendChild(commentForm);
            // hide cancel button
            BlogEngine.$('cancelReply').style.display = 'none';
            if (document.getElementById(BlogEngine.comments.replyFieldID)) {
                document.getElementById(BlogEngine.comments.replyFieldID).value = "";
            }
        } else {
            // show cancel
            BlogEngine.$('cancelReply').style.display = '';

            // move to nested position
            var parentComment = BlogEngine.$('id_' + id);
            var replies = BlogEngine.$('replies_' + id);

            // add if necessary
            if (replies == null) {
                replies = document.createElement('div');
                replies.className = 'comment-replies';
                replies.setAttribute('id') = 'replies_' + id;
                parentComment.appendChild(replies);
            }
            replies.style.display = '';
            replies.appendChild(commentForm);
            if (document.getElementById(BlogEngine.comments.replyFieldID)) {
                document.getElementById(BlogEngine.comments.replyFieldID).value = id;
            }
        }

        BlogEngine.comments.nameBox.focus();
    }

 

The parts I added (four lines) are the parts with document.getElementById - I didn't use the "BlogEngine.$" shortcut syntax though I very well could have.

The "appendComment" function also gains a line. Find the comment "// reset reply to" and add right below it:

if (BlogEngine.comments.replyToId) BlogEngine.comments.replyToId.value = '';

The "AddComment" function still references cbNotify by the client ID but cbNotify is now a server control, so the "AddComment" function needs a line changed.

Change:

var notify = BlogEngine.$("cbNotify").checked;

to this:

var notify = BlogEngine.comments.cbNotify ? BlogEngine.comments.cbNotify.checked : false;

One last edit, at the near-bottom of the file.

There is a structure defined for comments, starting with the line "comments: {" (If you've added the updates above and are on my version, that line will be line 506)

At the end of the structure, where it ends with "replyToId: null" we need to add two lines, so change that line to:

replyToId: null,
replyFieldID: null,
cbNotify: null

That's it for this file.

It didn't really change much. We are adding tracking to nesting and unnesting. ;

Our third, last, but most significantly affected file is the code-behind file for the ASCX control we edit before.

That file, specifically, is /User Controls/CommentView.ascx.cs

There are a LOT of changes we are going to have to make here.

The Page_Load function changes significantly. My best advice, rather than go line by line is to change it by copy/paste to this:

 

    protected void Page_Load(object sender, EventArgs e)
    {
        bnSave.Text = Resources.labels.saveComment;

        if (Post == null)
            Response.Redirect(Utils.RelativeWebRoot);

        if(!Page.IsPostBack && !Page.IsCallback)
        {
            if(Page.User.Identity.IsAuthenticated)
            {
                if(Request.QueryString["deletecomment"] != null)
                    DeleteComment();

                if(Request.QueryString["deletecommentandchildren"] != null)
                    DeleteCommentAndChildren();

                if(!string.IsNullOrEmpty(Request.QueryString["approvecomment"]))
                    ApproveComment();

                if(!string.IsNullOrEmpty(Request.QueryString["approveallcomments"]))
                    ApproveAllComments();
            }

            if(BlogSettings.Instance.IsCommentsEnabled)
            {

                if(!Post.IsCommentsEnabled || (BlogSettings.Instance.DaysCommentsAreEnabled > 0 &&
                   Post.DateCreated.AddDays(BlogSettings.Instance.DaysCommentsAreEnabled) < DateTime.Now.Date))
                {
                    phAddComment.Visible = false;
                    lbCommentsDisabled.Visible = true;
                }

                GetCookie();
                hfCaptcha.Value = Guid.NewGuid().ToString();
            }
            else
            {
                phAddComment.Visible = false;
            }
            //InititializeCaptcha();
            BindCountries();
        }

        string theme = BlogSettings.Instance.Theme;
        if(Request.QueryString["theme"] != null)
            theme = Request.QueryString["theme"];

        string path = Utils.RelativeWebRoot + "themes/" + theme + "/CommentView.ascx";
        phComments.Controls.Clear();
        if(NestingSupported)
        {
            // newer, nested comments
            AddNestedComments(path, Post.NestedComments, phComments);
        }
        else
        {
            // old, non nested code

            //Add approved Comments
            foreach(Comment comment in Post.Comments)
            {
                CommentViewBase control = (CommentViewBase) LoadControl(path);
                if(comment.IsApproved || !BlogSettings.Instance.EnableCommentsModeration)
                {
                    control.Comment = comment;
                    control.Post = Post;
                    phComments.Controls.Add(control);
                }
            }

            //Add unapproved comments
            foreach(Comment comment in Post.Comments)
            {
                CommentViewBase control = (CommentViewBase) LoadControl(path);

                if(!comment.IsApproved && Page.User.Identity.IsAuthenticated)
                {
                    control.Comment = comment;
                    control.Post = Post;
                    phComments.Controls.Add(control);
                }
            }

        }

        if(IsPostBack || Page.IsCallback)
        {
            //Add the blog.js back in. Master page only adds it when not post back
            string script = "<script type=\"text/javascript\" src=\"" + ResolveScriptUrl(Utils.RelativeWebRoot + "blog.js") + "\"></script>";
            Page.ClientScript.RegisterStartupScript(GetType(), Utils.RelativeWebRoot + "blog.js".GetHashCode().ToString(), script);
        }

        if(hiddenReplyID.Value.Length > 0)
        {
            Page.ClientScript.RegisterStartupScript(GetType(), "HiddenReplyRefresh".GetHashCode().ToString(), 
            
             string.Format(@"
<script language=""Javascript"" type=""text/javascript"" defer=""defer"">
    registerCommentBox();
    BlogEngine.replyToComment('{0}');
</script>
", hiddenReplyID.Value));
        }
        //Page.Header.Controls.Add(oJSHeader);

        Page.ClientScript.GetCallbackEventReference(this, "arg", null, string.Empty);
    }

 

For reference purposes (so that if you have to change a different version of the BlogEngine) here is the original Page_Load before I touched it. You can use this to "diff" the changes to see what was done.

 

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Post == null)
            Response.Redirect(Utils.RelativeWebRoot);

        if (!Page.IsPostBack && !Page.IsCallback)
        {
            if (Page.User.Identity.IsAuthenticated)
            {
                if (Request.QueryString["deletecomment"] != null)
                    DeleteComment();

                if (Request.QueryString["deletecommentandchildren"] != null)
                    DeleteCommentAndChildren();

                if (!string.IsNullOrEmpty(Request.QueryString["approvecomment"]))
                    ApproveComment();

                if (!string.IsNullOrEmpty(Request.QueryString["approveallcomments"]))
                    ApproveAllComments();
            }

            string theme = BlogSettings.Instance.Theme;
            if (Request.QueryString["theme"] != null)
                theme = Request.QueryString["theme"];

            string path = Utils.RelativeWebRoot + "themes/" + theme + "/CommentView.ascx";

            if (NestingSupported)
            {
                // newer, nested comments
                AddNestedComments(path, Post.NestedComments, phComments);
            }
            else
            {
                // old, non nested code

                //Add approved Comments
                foreach (Comment comment in Post.Comments)
                {
                    CommentViewBase control = (CommentViewBase)LoadControl(path);
                    if (comment.IsApproved || !BlogSettings.Instance.EnableCommentsModeration)
                    {
                        control.Comment = comment;
                        control.Post = Post;
                        phComments.Controls.Add(control);
                    }
                }

                //Add unapproved comments
                foreach (Comment comment in Post.Comments)
                {
                    CommentViewBase control = (CommentViewBase)LoadControl(path);

                    if (!comment.IsApproved && Page.User.Identity.IsAuthenticated)
                    {
                        control.Comment = comment;
                        control.Post = Post;
                        phComments.Controls.Add(control);
                    }
                }

            }


            if (BlogSettings.Instance.IsCommentsEnabled)
            {

                if (!Post.IsCommentsEnabled || (BlogSettings.Instance.DaysCommentsAreEnabled > 0 &&
                   Post.DateCreated.AddDays(BlogSettings.Instance.DaysCommentsAreEnabled) < DateTime.Now.Date))
                {
                    phAddComment.Visible = false;
                    lbCommentsDisabled.Visible = true;
                }

                BindCountries();
                GetCookie();
                hfCaptcha.Value = Guid.NewGuid().ToString();
            }
            else
            {
                phAddComment.Visible = false;
            }
            //InititializeCaptcha();
        }


        Page.ClientScript.GetCallbackEventReference(this, "arg", null, string.Empty);
    }

 

Here are some highlights on my changes.

Since the button in the ASCX file is no longer a client button, I update it's label in the Page_Load instead of through an inline value assignment on the ASCX.

After the theme is applied, I check to see if our new input box has a value. If it does, we will need to re-indent the comment box when the page returns so that the user experience isn't disrupted if their captcha attempt failed. Thus, I manually register a startup script and in it, call the javascript functions for "registerCommentBox" and "replyToComment" in order to get the comment back in to position.

Before we can call them though, the BlogEngine class has to be compiled by the client browser already, but I found an interesting tidbit when I was writing all this code. It only gets sent the first time the page loads. It disappears after post back!! As such, I add it even on post back first. (I placed a comment in the code to this affect.)

When I added the function to re-post the blog.js back to the file, I had to copy a function to the code file we are now modifying. 

Below the Page_Load, add:

    /// <summary>
    /// Resolves the script URL. Copied from BlogBasePage.cs
    /// </summary>
    /// <param name="url">The URL.</param>
    /// <returns></returns>
    public virtual string ResolveScriptUrl(string url)
    {
        return Utils.RelativeWebRoot + "js.axd?path=" + HttpUtility.UrlEncode(url) + "&amp;v=" + BlogSettings.Instance.Version();
    }

This adds the "ResolveScriptUrl" to our page, accessible from the Page_Load function (where we added it in)

Now, for the work horse.

Below the newly added function, add one more. Here it is, in all its glory:

 

    /// <summary>
    /// Adds a comment through code instead of AJAX. Complete with email notifications.
    /// </summary>
    /// <param name="a">Not used</param>
    /// <param name="b">Not used</param>
    protected void AddLocalComment(object a, EventArgs b)
    {
        if(Page.IsValid)
        {
            Comment oComment = new Comment();
            oComment.Author = txtName.Text;
            oComment.Email = txtEmail.Text;
            oComment.Content = txtContent.Text;
            if(ddlCountry.SelectedItem != null)
                oComment.Country = ddlCountry.SelectedItem.Value;
            try
            {
                oComment.Website = new Uri(txtWebsite.Text);
            }
            catch(Exception) { }
            oComment.DateCreated = DateTime.Now;
            oComment.IsApproved = !BlogEngine.Core.BlogSettings.Instance.EnableCommentsModeration;
            if(oComment.IsApproved == null)
                oComment.IsApproved = true;
            oComment.Id = Guid.NewGuid();
            if(Request.ServerVariables["REMOTE_ADDR"] != null)
                oComment.IP = Request.ServerVariables["REMOTE_ADDR"].ToString();
            oComment.Parent = Post;
            if(hiddenReplyID != null)
            {
                //Response.Write("Hidden ID " + hiddenReplyID.Value);
                if(hiddenReplyID.Value != null && hiddenReplyID.Value.Length > 0)
                    try
                    {
                        oComment.Parent = Post;
                        oComment.ParentId = new Guid(hiddenReplyID.Value);
                    }
                    catch(Exception) { }
            }
            Post.AddComment(oComment);

            if(cbNotify.Checked)
            {
                if(Post.NotificationEmails.IndexOf(oComment.Email) == -1) //not already signed up for notifications
                    Post.NotificationEmails.Add(oComment.Email);
            }
            Post.Save();

            //Create a handler function if you want to track failures
            //Utils.EmailFailed += new EventHandler<EventArgs>(RecordFailure);
            System.Net.Mail.MailMessage oMessage = null;
            foreach(string MailTo in Post.NotificationEmails)
            {
                //Post.ApproveComment(oComment); //Sends the email
                oMessage = new System.Net.Mail.MailMessage(
                        BlogSettings.Instance.Email,
                        MailTo,
                        "New Comment by '" + oComment.Author + "' on '" + Post.Title + "'.",
                        string.Format(@"
<b>{1}</b> has entered a comment on the post <a href=""{2}"">{3}</a>

The content of the comment is:

{4}
", Environment.NewLine, oComment.Author, Post.PermaLink + "#id_" + oComment.Id, Post.Title, oComment.Content).Replace(Environment.NewLine, "<br />")
                    );
                oMessage.From = new System.Net.Mail.MailAddress(BlogSettings.Instance.Email, BlogSettings.Instance.Name);
                Utils.SendMailMessageAsync(oMessage);
            }

            if(BlogSettings.Instance.SendMailOnComment)
            {
                oMessage = new System.Net.Mail.MailMessage(
                    BlogSettings.Instance.Email,
                    BlogSettings.Instance.Email,
                    BlogSettings.Instance.EmailSubjectPrefix + ((BlogSettings.Instance.EmailSubjectPrefix == "") ? "" : ": ") +
                    "New Comment by '" + oComment.Author + "' on '" + Post.Title + "'.",
                    string.Format(@"
<b>{1}</b> has entered a comment on the post <a href=""{2}"">{3}</a>

The content of the comment is:

{4}
", Environment.NewLine, oComment.Author, Post.PermaLink + "#id_" + oComment.Id, Post.Title, oComment.Content).Replace(Environment.NewLine, "<br />")
    );
                oMessage.From = new System.Net.Mail.MailAddress(BlogSettings.Instance.Email, BlogSettings.Instance.Name);
                Utils.SendMailMessageAsync(oMessage);
            }
            
            txtContent.Text = "";
        }

        Page_Load(null, null); //Set the page back up
    }

 

There is a lot to digest in what this does, but it's completely new, so you don't have to worry about overwriting another function. It's what our button on the ASCX now calls when you press it.

I have found that when you add a comment in BlogEngine.Net, you have to do EVERYTHING that pertains to the comment. Set all properties, send all notifications, etc. Thus, there's quite a bit of code. You should be able to get the gist of what I'm doing when you read it.

Notice: Scott Marlowe notes in the comments that you can add a way for people's browsers to automagically scroll down to their new comment. Great idea! Check his post out for details.

One final change is in the BindCountries function. Since we made the countries drop down to be view state enabled, it's good practice to clear the drop list before we add items.

At the top of the function, add:

ddlCountry.Items.Clear();

That's it.

Update: One additional step

We need to close the ability to emulate the API to add a comment directly.

In the CommentView.ascx.cs file, find the RaiseCallbackEvent function.

Place the following at the top, but after the variables are filled:

 

if(!isPreview)

{

throw new ApplicationException("This operation is no longer allowed.");

}

 

Upload your files, you should be operating now.

Enjoy.

 

Comments

8/21/2009 6:21:43 PM #

Scott Marlowe

I'm about to run through the steps you've outlined. I started working on integrating recaptcha into my own blogengine.net blog and... whoa, it's a lot of work.

I also want to test out your implementation, thus this comment.

I'll comment back when I'm finished to let you know how it goes.

Scott Marlowe (http://www.itscodingtime.com/) United States |

8/21/2009 8:31:58 PM #

Keith

Cool! I hope you find that it works for you without difficulty. If you have any difficulty, I'll help. Let me know.

Keith United States |

8/21/2009 10:06:49 PM #

Scott Marlowe

I'm done! It's not deployed yet (I want to do some more local testing first), but everything looks good. I'm currently not running the latest BlogEngine.NET (I'm on 1.4.7), so I had to skip a couple of your steps (mostly having to do with the 'reply' feature, which I guess is not supported on 1.4.7). But otherwise everything works great! Thanks a lot for your post. Saved me lots of frustration/time.

I did add one thing...

Page.ClientScript.RegisterStartupScript (GetType (), "navigate", "document.getElementById('addcommentsection').scrollIntoView();", true);

to the bottom of "AddLocalComment" in order to bring the user back down to the comments section. I added a corresponding anchor tag in the ascx file.

Scott Marlowe (http://www.itscodingtime.com/) United States |

8/21/2009 10:53:28 PM #

Keith

That's a really good idea. I'll make a brief mention in the article for people to check out your comment.

Woot.

Keith (http://www.keithratliff.com/) United States |

8/22/2009 12:17:26 AM #

Scott Marlowe

Thanks!

I took it one step further:

1.) If the captcha is not valid, then put the user back at the "add comment" section.
2.) If the captcha is valid and we have a valid comment, put the user at the new comment.

Here's the new code (place after "Page_Load (null, null);" in "AddLocalComment"):

if (!recaptcha.IsValid)
{
  // captcha was incorrect, scroll back down to 'add comment' section
  Page.ClientScript.RegisterStartupScript (GetType (), "navigate", "document.getElementById('addcommentsection').scrollIntoView();", true);
}
else
{
// navigate to new comment (if there is one; there always should be at this point)
  if (guidAnchorId != Guid.Empty)
  {
    Page.ClientScript.RegisterStartupScript (GetType (), "navigate", "document.getElementById('id_" + guidAnchorId + "').scrollIntoView();", true);
  }
}

Scott Marlowe (http://www.itscodingtime.com/) United States |

8/22/2009 12:19:14 AM #

Scott Marlowe

forgot... guidId is a new Guid variable I defined at the top, which gets set when the comment id is generated:

oComment.Id = guidAnchorId = Guid.NewGuid ();

Scott Marlowe (http://www.itscodingtime.com/) United States |

8/22/2009 12:30:04 AM #

Keith

In the AddLocalComment function, I added a line:

oComment.Id = Guid.NewGuid();

After that point, you could just use oComment.Id if it was valid. Then in the Else statement, just scroll the comment box in to view instead (it appears the comment box has the client ID "comment-form" that would work with a document.getElementById)

Keith (http://www.keithratliff.com/) United States |

8/22/2009 1:39:28 AM #

Scott Marlowe

Good point.

Scott Marlowe (http://www.itscodingtime.com/) United States |

8/22/2009 12:23:32 AM #

Keith

I wonder if it might not be more appropriate on successful submit to Response.Redirect to the hash-anchor tag for the comment so that there's no chance of the javascript causing any problem.

Regardless, this is exactly the kind of in-perspective thinking that more Web developers should keep their eyes on!

Bravo!

Keith (http://www.keithratliff.com/) United States |

8/21/2009 10:26:21 PM #

rob

Can't wait to try this out.  I'm sick of the bots.  Thanks for sharing!

rob United States |

8/21/2009 10:56:46 PM #

Keith

You're very welcome. Make sure to come back and let me know how it goes!

Keith (http://www.keithratliff.com/) United States |

8/21/2009 10:40:57 PM #

Scott Marlowe

Huh. Didn't notice that you'd just posted this a week ago. Great timing!

Scott Marlowe (http://www.itscodingtime.com/) United States |

8/21/2009 10:52:08 PM #

Keith

Indeed! I only recently implemented it, but I tested it pretty extensively before writing the article. I'm so glad it worked for you as-is. That's always the worry about writing up something like this - that I might have left out some crucial piece of information by simply overlooking it at write-up time.

Keith (http://www.keithratliff.com/) United States |

9/24/2009 10:50:00 PM #

Keith

I made an additional update to the article in case you wanted to know.

I added:

Update: One additional step

We need to close the ability to emulate the API to add a comment directly.

In the CommentView.ascx.cs file, find the RaiseCallbackEvent function.

Place the following at the top, but after the variables are filled:

if(!isPreview)

{

throw new ApplicationException("This operation is no longer allowed.");

}

Keith United States |

8/24/2009 4:09:35 PM #

吴鹏(AlphaWu)

Try it first.When i implement it,I'll comment again.

吴鹏(AlphaWu) (http://wupeng.cn/) People's Republic of China |

8/24/2009 7:41:11 PM #

吴鹏(AlphaWu)

oh..
Preview occur javascript error.
can't preview.

吴鹏(AlphaWu) (http://wupeng.cn/) People's Republic of China |

8/24/2009 9:09:56 PM #

Keith

OK, If I can help in any way let me know. I'm thinking you may be having issues due to nationalization (possibly?) or due to a different version of the blog software rather than the latest.

Here's wishing you a speedy resolution.

Keith United States |

8/25/2009 6:17:49 AM #

吴鹏(AlphaWu)

In your site,when i click "preview",occur a javascript error.

It's the notify control,when you add "runat=server",client can't find it.
so error occur.

I think if can use ajax recaptcha,will very good.

can you work it out?

thanks.

吴鹏(AlphaWu) (http://wupeng.cn/) People's Republic of China |

8/26/2009 11:59:26 PM #

Keith

Worked out!

There are three changes to the article and I have made the changes - they relate to the fact "cbNotify" was changed from a client side object to a run-at server object.

Sorry for the "miss" on that one.

Keith United States |

8/27/2009 7:31:27 AM #

吴鹏(AlphaWu)

Very Cool.
I use another simply way.Look at this:wupeng.cn/.../Add-Captcha-to-BlogEnginenet.aspx

Add one file and modified two files.
I think the code is clear.Only a little Chinese.

吴鹏(AlphaWu) (http://wupeng.cn/) People's Republic of China |

8/26/2009 11:01:53 AM #

Laptop Drivers

Cool. Thanks, I'll try implement this since I've got loads of spams in my blog engine blogs. Cheers

Laptop Drivers (http://digitalchunk.com/laptop-drivers) United States |

9/10/2009 10:35:43 PM #

plumber mosman

It sounds like you're creating problems yourself by trying to solve this issue instead of looking at why their is a problem in the first place.

plumber mosman (http://www.plumbermosman.com.au/) United Kingdom |

9/10/2009 11:01:35 PM #

Keith

I'm not convinced you understand the nature of the problem; specifically BlogEngine is weak to spam attacks because the included "simple captcha" has been defeated and so bots now use BlogEngine sites to support SEO efforts.

By adding a more secure captcha, I have largely closed this hole. And the immediate drop of people spamming my site confirms.

If you have another solution that addresses SEO spam in BlogEngine let me know. Until a good one is presented, I offer this as a solution.

Keith United States |

9/11/2009 12:15:37 AM #

Scott Marlowe

Couple Keith's solution with Al Nyveldt's Commentor extension (rtur.net/.../Commentor-e28093-new-version.aspx) using Askimet and good luck spammers!

Oh, I started moderating, too, since Al's extension allows you to view and approve comments very easily.

It's unfortunate we even have to go to such lengths, but that's how it is.

Scott Marlowe (http://www.itscodingtime.com/) United States |

9/11/2009 12:16:52 AM #

Scott Marlowe

Oops. that link didn't work, but just search for "Commentor" and it's one of the top entries.

Scott Marlowe (http://www.itscodingtime.com/) United States |

9/11/2009 12:43:11 AM #

Keith

Looks only like the problem was the trailing parenthesis.

Here, I will add the corrected link so that others may find it easily:
rtur.net/.../Commentor-e28093-new-version.aspx

Thanks again, Scott!

Keith United States |

9/15/2009 8:02:15 AM #

Klaus

Hi Keith,
I had the same spam on my website and was looking for some kind of defense. I found your blog post about using reCaptcha with BE.NET. But now I see that these guys got you too, even with reCaptcha in place.

Thanks for your post anyway.

Klaus (http://www.tellingmachine.com/) United States |

9/16/2009 7:20:57 PM #

Keith

Look for an update soon. The API that was used for the AJAX needs to be closed to bring this full circle. It appears that people have figured out how to hook in to the API directly.

Keith United States |

9/26/2009 10:30:08 AM #

Ed Hardy

Thanks, you cleared up some things for me.


Ed Hardy

Ed Hardy (http://www.hardyclothing.net/) United States |

9/26/2009 12:37:56 PM #

Keith

You bet!

Here's hoping that this squares you away!

Keith United States |

9/28/2009 2:48:19 PM #

New Jersey Speed Dating

Re-captcha is perfect. I actually added it to my "tell a friend" page at https://www.njfirstdates.com/tellafriend.aspx

I think you made a great choice in adding this feature to protect yourself.

New Jersey Speed Dating United States |

9/29/2009 5:01:07 PM #

Paul Riley

What a brilliant article, just implemented it on my blog and worked without a fault. Keep up the good work.

Paul Riley (http://blog.paulriley.me.uk/) United Kingdom |

9/29/2009 6:40:46 PM #

Keith

Thanks Paul!

Keith United States |

10/5/2009 2:15:46 AM #

Christopher Galpin

This is some fantastic work Keith, I just implemented it myself per your instructions. With moderated comments (Commentor) on it needs a little feedback, but I will have to see to that. Smile

I looked at reCAPTCHA myself when I did my naive captcha, and saw (and was a bit irritated by) all of the work involved to use standard validation. It's awesome that you tackled and shared it, kudos!

Christopher Galpin (http://codeoptimism.net/) United States |

10/5/2009 2:19:48 AM #

Christopher Galpin

*ahem* fixed my Gravatar for this email

Christopher Galpin (http://codeoptimism.net/) United States |

10/5/2009 6:16:49 AM #

Keith

Thank you very much for the comments and I'm glad it worked out so well!

Keith (http://www.keithratliff.com/) United States |

12/20/2009 5:52:24 PM #

Raden Beletz

yea, What a brilliant article, just implemented it on my blog and worked without a fault. Keep up the good work.

Raden Beletz (http://radenbeletz.com/) United States |

12/31/2009 10:26:35 PM #

Walter

Thank you, I just implemented this solution on a BE 1.5 blog and it’s functioning excellently. The instruction provided were easy to follow, I had already spent about 2-3 hours working on a solution but once I discovered that I would have to wade through all the AJAX to get  RE-Captcha working I was ready to scrap it.. It was defiantly worth Goggling the issue.   The solution was implemented in less time than it had already taken me in my own exploration of the problem  I’m very pleased  Thanks & Ooh Rah

Walter
IT Professional

Walter United States |

1/1/2010 12:06:46 AM #

Keith

Thanks for letting me know it worked for you and I'm glad that I'm able to help people. Oooh rah!

Keith United States |

1/14/2010 3:24:29 AM #

yoga queens

Great post. very easy to do, i was about to pay someone to do this for my site!

yoga queens United States |

1/24/2010 10:10:04 PM #

Mike Ceranski

I got a captcha solution running with Ajax. I followed Alpha's solution and then made a few small modifications to get things working properly on Blogengine 1.5.0.7. The full instructions are here: www.codecapers.com/.../...ts-in-BlogEngineNET.aspx

Mike Ceranski (http://www.codecapers.com/) United States |

1/29/2010 8:40:04 AM #

nowGoogle.com Adalah Multiple Search Engine Popular

Thank you, I just implemented this solution on a BE 1.5 blog and it’s functioning excellently.

nowGoogle.com Adalah Multiple Search Engine Popular (http://www.ericaaz.co.cc/2010/01/nowgooglecom-adalah-multiple-search.html) United States |

2/3/2010 11:55:03 AM #

dstarlight

an interesting feature, I'll try it.

still learning
Ace dstarlight

dstarlight (http://dstarlight.com/) Indonesia |

3/3/2010 8:18:01 PM #

trackback

给BlogEngine.Net添加上验证码

给BlogEngine.Net添加上验证码

神秘园 CaBeta.com (http://cabeta.com/post/2010/02/01/add-the-code-to-blogenginenet.aspx) |

3/6/2010 3:57:34 PM #

Contact Manager Blog

Testing it now! maybe somebody should write an extension or this needs to be integrated in the next release of blogengine.net

Contact Manager Blog (http://blog.officeclip.com/) United States |

3/6/2010 4:54:44 PM #

Samuel Dass

Just found an ajax based recaptcha extension: www.bloodforge.com/.../...r-BlogEngineNET-16.aspx. Will have to try and see.

Samuel Dass (http://blog.officeclip.com/) United States |

3/6/2010 6:26:19 PM #

Keith

Looks pretty good. The IE error he talks about happens due to a page refresh.

Keith United States |

3/24/2010 10:29:59 AM #

seo company

Hello Keith,

We have follow steps as u mentioned for our own blogengine.net blog and yes it works very nicely!
Thank you for sharing good steps..

seo company India |

4/30/2010 8:25:04 AM #

cara menambah tinggi badan

Thus, I started to look in to reCaptcha and seeing how to implement it.

reCaptcha actually does a very good thing for developers and wraps its API in to language specific wrappers. They have one for ASP.Net even and it appeared at first glance to be a pretty easy plug and play.

Three days later of continued effort, I have discovered that it's anything but - when it comes to BlogEngine.Net - and not due to reCaptcha's API but rather, due to the fact that it has to be validated at the server and BlogEngine.Net submits comments to posts through AJAX.

cara menambah tinggi badan (http://peninggi-badan.com/) United States |

5/19/2010 6:41:41 AM #

tinggi badan

Three days later of continued effort, I have discovered that it's anything but - when it comes to BlogEngine.Net - and not due to reCaptcha's API but rather, due to the fact that it has to be validated at the server and BlogEngine.Net submits comments to posts through AJAX.

tinggi badan (http://peninggi-badan.com/) United States |

Comments are closed

About The Authors

Keith - Father of two wonderful children. Second to that, passionate about video games and even loves a bout of good c# programming. Yes, he's strange. See more at this site!

 

Chris - Good friend. Sees things much the same way as Keith, minus a few shades of "weird". Also, more fun to listen to.

Most comments

传奇私服 传奇私服
5 comments
cn People's Republic of China
Coach outlet Coach outlet
1 comments
cn People's Republic of China
Air force one Air force one
1 comments
us United States

Valid XHTML 1.0 Transitional