How to integrate ReCaptcha in Orchard

Tags: orchard, asp.net, google, captcha, antibot

Recently we had the problem getting lots of spam by our new website that we rebuilt using the Orchard-Framework.
So we had to find a way to fight against Spam-bots. First we tried the Orchard.Captcha Module that could be found using the Gallery. After the installation and activation of this Module the Settings of our Website could not be loaded.
The reason was an bad designed database-entry that causes a NULL-Value inserted into a non-Nullable column in the SQL Database. Also there was a compile error, because there was a reference to an Assembly that is not used. After we made this project compileable, we found no documentation on how to use this Module. So we decided to try another way.
After a short search we found out, that Microsoft already has a Captcha support in their Microsoft.Web.Helpers Library. Since Orchard is built using MVC and @Razor, it should be possible to use and integrate it into the Orchard Project. In the sample below you can read how to integrate the Captcha of the Microsoft.Web.Helpers library into the Orchard.Comments Module.

1. Create a ReCaptcha Private/Public Key pair

Goto http://www.google.com/recaptcha sign in – or create an account and create a private/public key pair. You need this information later to validate the input of the Captcha-Value. ReCaptcha is a service hostet at Google and its free for use to fight against Spam.

2. Add a Reference in the Orchard.Framework project to Microsoft.Web.Helpers and Microsoft.Web.Pages

First, you need to download the Sources of Orchard, so that you can modify and debug it using Visual Studio.
To get it working, you need to add a reference to the Orchard.Framework project.
Open the project, right click References and click “Manage Nuget Packages”.

image

Picture 1: Add Package Reference using NuGet

In the NuGet Package Manager Online Library search for “web helpers”.
Select “ASP.NET Web Helpers Library” and install it.

image

Picture 2: Search and install the Microsoft ASP.NET Web Helpers Library

This Reference is required, because later when a Module using this library is injected in Orchard, the interceptors requires a reference to that dependent libraries. Also add a Reference to System.Web.Pages.

3. Add a Reference to the “ASP.NET Web Helpers Library” in the Modules where you want to use Captcha

In this sample we show you how to modify the Comments-Module to add the Captcha on the Cpmments-Form.
Open the Project and add the References as you did for the Orchard.Framework Project.
Under Views you will find the “Parts.Comments.cshtml” file. Modify the file like below:

@using Orchard.Comments.Models;
@using Orchard.Comments;
@using Orchard.Security;
@using Orchard.Utility.Extensions;
@using Microsoft.Web.Helpers;
 
@{
    var contextExists = TempData["CreateCommentContext.Name"] != null;
    var name = Convert.ToString(TempData["CreateCommentContext.Name"]);
    var commentText = Convert.ToString(TempData["CreateCommentContext.CommentText"]);
    var email = Convert.ToString(TempData["CreateCommentContext.Email"]);
    var siteName = Convert.ToString(TempData["CreateCommentContext.SiteName"]);
}
@{
    ReCaptcha.PublicKey = "enter-your-public-recatchpa-key-here";  
}
@if (Model.ContentPart.Comments.Count > 0) {
<div id="comments">
    <h2 class="comment-count">@T.Plural("1 Comment", "{0} Comments", (int)Model.ContentPart.Comments.Count)</h2>
    @{Html.RenderPartial("ListOfComments", (IEnumerable<Orchard.Comments.Models.CommentPart>)Model.ContentPart.Comments);}
</div>
}
 
@if (Model.ContentPart.CommentsActive == false) {
    if (Model.ContentPart.Comments.Count > 0) {
        <div id="comments">
            <p class="comment-disabled">@T("Comments have been disabled for this content.")</p>
        </div>
    }
}
else if (WorkContext.CurrentUser == null && !AuthorizedFor(Permissions.AddComment)) {
<h2 id="add-comment">@T("Add a Comment")</h2>
<p class="info message">@T("You must {0} to comment.", Html.ActionLink(T("log on").ToString(), "LogOn", new { Controller = "Account", Area = "Orchard.Users", ReturnUrl = string.Format("{0}#addacomment", Context.Request.RawUrl) }))</p>
} else {
using (Html.BeginForm("Create", "Comment", new { area = "Orchard.Comments" }, FormMethod.Post, new { @class = "comment-form" })) { 
    @Html.ValidationSummary() 
    if (WorkContext.CurrentUser == null) {
 
    <fieldset class="who">
        <legend id="add-comment">@T("Add a Comment")</legend> 
    <ol>
        <li>
            <label for="Name">@T("Name")</label>
            <input id="Name" class="text" name="Name" type="text" value="@(contextExists ? name : String.Empty)" />
        </li>
        <li>
            <label for="Email">@T("Email")</label>
            <input id="Email" class="text" name="Email" type="text" value="@(contextExists ? email : String.Empty)"/>
        </li>
        <li>
            <label for="SiteName">@T("Url")</label>
            <input id="SiteName" class="text" name="SiteName" type="text" value="@(contextExists ? siteName : String.Empty)"/>
        </li>
        <li>
            @ReCaptcha.GetHtml(theme: "red")
        </li>
     </ol>
    </fieldset>
    } else {
        @Html.Hidden("Name", WorkContext.CurrentUser.UserName ?? "")
        @Html.Hidden("Email", WorkContext.CurrentUser.Email ?? "")
    }
 
    <h2 id="commenter">@if (WorkContext.CurrentUser != null) { @T("Hi, {0}!", Html.Encode(WorkContext.CurrentUser.UserName))}</h2>
    <fieldset class="what">
    <ol>
        <li>
            <label for="comment-text">@T("Comment")</label>
            <textarea id="comment-text" rows="10" cols="30" name="CommentText">@(contextExists ? commentText : String.Empty)</textarea>
        </li>
        <li>
            <button class="primaryAction" type="submit">@T("Submit Comment")</button>
            @Html.Hidden("CommentedOn", (int)Model.ContentPart.ContentItem.Id) 
            @Html.Hidden("ReturnUrl", Context.Request.ToUrlString()) 
            @Html.AntiForgeryTokenOrchard() 
        </li>
    </ol>
    </fieldset>
    }
} 

Listing 1: The modified “Parts.Comments.cshtml” file of the Orchard.Comments Module

Next you need to modify the Controller of the View and integrate the validation as you can see in the CommentController.cs file below:

[HttpPost, ValidateInput(false)]
public ActionResult Create(string returnUrl) {
    if (!Services.Authorizer.Authorize(Permissions.AddComment, T("Couldn't add comment")))
        return this.RedirectLocal(returnUrl, "~/");
            
 
 
    var viewModel = new CommentsCreateViewModel();
 
    TryUpdateModel(viewModel);
            
    var context = new CreateCommentContext {
        Author = viewModel.Name,
        CommentText = viewModel.CommentText,
        Email = viewModel.Email,
        SiteName = viewModel.SiteName,
        CommentedOn = viewModel.CommentedOn
    };
 
    if (!ReCaptcha.Validate(privateKey: "insert-your-private-recatchpa-key-here")) {
        _notifier.Error(T("Please enter correct Values exactly as you can see in the Picture!"));
 
        TempData["CreateCommentContext.Name"] = context.Author;
        TempData["CreateCommentContext.CommentText"] = context.CommentText;
        TempData["CreateCommentContext.Email"] = context.Email;
        TempData["CreateCommentContext.SiteName"] = context.SiteName;
 
        return this.RedirectLocal(returnUrl, "~/");
    }
 
    if (ModelState.IsValid) {
        if (!String.IsNullOrEmpty(context.SiteName) && !context.SiteName.StartsWith("http://") && !context.SiteName.StartsWith("https://")) {
            context.SiteName = "http://" + context.SiteName;
        }
 
        CommentPart commentPart = _commentService.CreateComment(context, Services.WorkContext.CurrentSite.As<CommentSettingsPart>().Record.ModerateComments);
 
        if (commentPart.Record.Status == CommentStatus.Pending) {
            // if the user who submitted the comment has the right to moderate, don't make this comment moderated
            if (Services.Authorizer.Authorize(Permissions.ManageComments)) {
                commentPart.Record.Status = CommentStatus.Approved;
            }
            else {
                Services.Notifier.Information(T("Your comment will appear after the site administrator approves it."));
            }
        }
    }
    else {
        foreach (var error in ModelState.Values.SelectMany(m => m.Errors).Select( e=> e.ErrorMessage)) {
            _notifier.Error(T(error));
        }
    }
 
    if(!ModelState.IsValid) {
        TempData["CreateCommentContext.Name"] = context.Author;
        TempData["CreateCommentContext.CommentText"] = context.CommentText;
        TempData["CreateCommentContext.Email"] = context.Email;
        TempData["CreateCommentContext.SiteName"] = context.SiteName;
    }
 
    return this.RedirectLocal(returnUrl, "~/");
}

Listing 2: The modified “CommentController.cs” file of the Orchard.Comments Module (Create-Method)

How does it look like? See the Comments below this BLOG-Entry.

That’s all. So easy is fighting spam!
--[ Helmut ]--

Add a Comment