ASP.NET MVC CAPTCHA C# Code Sample

The ASP.NET MVC Captcha sample project shows how to use the BotDetect CAPTCHA MvcCaptcha control in ASP.NET MVC web applications.

Starting with the default ASP.NET MVC sample project, the sample includes all code required to add CAPTCHA validation to the Account controller Register action.

The sample remembers when the CAPTCHA is successfully solved within a single registration, and doesn't display it again if there are errors with other form values (the username, for example).

You can get a new CAPTCHA using the Register action link in the top right corner of the page, and see the explanation in the Account controller code.

Download the BotDetect ASP.NET CAPTCHA Component and run this sample

ASP.NET MVC 3.0 / Visual Studio 2010 / .NET 4.0

By default, the .NET 4.0 C# version of the ASP.NET MVC 3.0 Captcha sample project is installed at:
C:\Program Files\Lanapsoft\BotDetect 3 CAPTCHA Component\Asp.Net\v4.0\WebApp\AspNetMVC30CaptchaSample\CSharp

You can also run it from the BotDetect Start Menu:
Programs > Lanapsoft > BotDetect 3 CAPTCHA Component > ASP.NET > DotNET 4.0 Web Applications > Run

Models\AccountModels.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
using System.Web.Security;

namespace AspNetMvc30CaptchaSampleCSharp.Models
{
    public class ChangePasswordModel
    {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2}
          characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = "The new password and 
          confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }

    public class LogOnModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }

    public class RegisterModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email address")]
        public string Email { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2}
          characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and 
          confirmation password do not match.")]
        public string ConfirmPassword { get; set; }

        // add Captcha code as a field in the protected action Model
        [Required(ErrorMessage = "Retyping the characters from the 
          picture is required.")]
        [Display(Name = "Please retype the characters from the 
          picture")]
        public string CaptchaCode { get; set; }
    }
}

Since we want to include Captcha validation in the Register action, we add a string field representing the Captcha code to the related RegisterModel class declaration.

The [Required] attribute specifies that the Captcha code input field cannot be left blank (and which error message to display if it is), while the [Display] attribute declares the text description used to prompt users to type the Captcha code.

Views\Account\Register.cshtml

@model AspNetMvc30CaptchaSampleCSharp.Models.RegisterModel

@using BotDetect.Web.UI.Mvc;

@{
    ViewBag.Title = "Register";
}

@* add BotDetect header includes *@
@section BotDetectStyles {
    <link href="@BotDetect.Web.CaptchaUrls.LayoutStyleSheetUrl" 
    rel="stylesheet" type="text/css" />
    <link href="@BotDetect.Web.CaptchaUrls.
    SoundPackageWarningStyleUrl" rel="stylesheet" type="text/css" />
}

<h2>Create a New Account</h2>
<p>
    Use the form below to create a new account. 
</p>
<p>
    Passwords are required to be a minimum of @Membership.
    MinRequiredPasswordLength characters in length.
</p>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" 
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.
js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    <p>
        @Html.ValidationSummary(true, "Account creation was 
        unsuccessful. Please correct the errors and try again.")
    </p>
    <div>
        <fieldset id="RegisterFields">
            <legend>Account Information</legend>

            <div class="editor-label">
                @Html.LabelFor(m => m.UserName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.UserName)
                @Html.ValidationMessageFor(m => m.UserName)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.Email)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.Email)
                @Html.ValidationMessageFor(m => m.Email)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.Password)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.Password)
                @Html.ValidationMessageFor(m => m.Password)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.ConfirmPassword)
            </div>
            <div class="editor-field">
                @Html.PasswordFor(m => m.ConfirmPassword)
                @Html.ValidationMessageFor(m => m.ConfirmPassword)
            </div>

            @* add Captcha validation controls to the protected action 
              View *@
            @{ MvcCaptcha registrationCaptcha = CaptchaHelper.
              GetRegistrationCaptcha(); }
            @if (!registrationCaptcha.IsSolved) {
              <div class="editor-label">
                @Html.LabelFor(m => m.CaptchaCode)
                @Html.Captcha(registrationCaptcha)
              </div>
              <div class="editor-field">
                @Html.TextBoxFor(m => m.CaptchaCode)
                @Html.ValidationMessageFor(m => m.CaptchaCode)
              </div>
            }

            <p style="width: 210px; text-align: right;">
                <input type="submit" value="Register" />
            </p>
        </fieldset>
    </div>
}

The BotDetectStyles section includes required BotDetect stylesheets in the generated page header.

To keep View code simple, we delegate MvcCaptcha instance creation to a helper class, and use it along with Model fields to generate the required markup using straightforward HtmlHelper calls.

Since we want to avoid re-displaying the Captcha challenge to users after they solve it, we only call the markup generation code if the IsSolved property is not set by a previous successful Captcha validation.

Views\Shared\_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" 
      type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" 
      type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" 
      type="text/javascript"></script>
    @* allow BotDetect stylesheet includes in selected Views *@
    @RenderSection("BotDetectStyles", required: false)
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>BotDetect CAPTCHA ASP.NET MVC 3.0 Sample</h1>
            </div>
            <div id="logindisplay">
                @Html.Partial("_LogOnPartial")
            </div>
            <nav>
                <ul id="menu">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                </ul>
            </nav>
        </header>
        <section id="main">
            @RenderBody()
        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

To allow easy inclusion of BotDetect stylesheets into Views which require them, the BotDetectStyles section was declared in the layout template header.

Controllers\AccountController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using AspNetMvc30CaptchaSampleCSharp.Models;

using BotDetect.Web.UI.Mvc;

namespace AspNetMvc30CaptchaSampleCSharp.Controllers
{
    public class AccountController : Controller
    {

        //
        // GET: /Account/LogOn

        public ActionResult LogOn()
        {
            return View();
        }

        //
        // POST: /Account/LogOn

        [HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if (Membership.ValidateUser(model.UserName, model.
                Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, 
                    model.RememberMe);
                    if (Url.IsLocalUrl(returnUrl) && returnUrl.Length >
                    1 && returnUrl.StartsWith("/")
                        && !returnUrl.StartsWith("//") && !returnUrl.
                        StartsWith("/\\"))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "The user name or 
                    password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        //
        // GET: /Account/LogOff

        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();

            return RedirectToAction("Index", "Home");
        }

        //
        // GET: /Account/Register

        public ActionResult Register()
        {
            // when the View is accessed directly and not posted, we 
            // clear any remembered CAPTCHA solving state. 
            // The users only have to solve the CAPTCHA once within a 
            // single registration, but if they reload the Register 
            // page later, it is shown again. 
            // Otherwise, they could register an unlimited number of 
            // accounts within a single Session after solving the 
            // CAPTCHA only once.
            MvcCaptcha.ResetCaptcha("RegistrationCaptcha");

            return View();
        }

        //
        // POST: /Account/Register

        [HttpPost]
        [CaptchaValidation("CaptchaCode", "RegistrationCaptcha", "Your 
          input doesn't match displayed characters.")]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus;
                Membership.CreateUser(model.UserName, model.Password, 
                model.Email, null, null, true, null, out createStatus);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, 
                    false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("", ErrorCodeToString(
                    createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        //
        // GET: /Account/ChangePassword

        [Authorize]
        public ActionResult ChangePassword()
        {
            return View();
        }

        //
        // POST: /Account/ChangePassword

        [Authorize]
        [HttpPost]
        public ActionResult ChangePassword(ChangePasswordModel model)
        {
            if (ModelState.IsValid)
            {

                // ChangePassword will throw an exception rather
                // than return false in certain failure scenarios.
                bool changePasswordSucceeded;
                try
                {
                    MembershipUser currentUser = Membership.GetUser(
                    User.Identity.Name, true /* userIsOnline */);
                    changePasswordSucceeded = currentUser.
                    ChangePassword(model.OldPassword, model.
                    NewPassword);
                }
                catch (Exception)
                {
                    changePasswordSucceeded = false;
                }

                if (changePasswordSucceeded)
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                else
                {
                    ModelState.AddModelError("", "The current password 
                    is incorrect or the new password is invalid.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        //
        // GET: /Account/ChangePasswordSuccess

        public ActionResult ChangePasswordSuccess()
        {
            return View();
        }

        #region Status Codes
        private static string ErrorCodeToString(MembershipCreateStatus 
        createStatus)
        {
            // See http://go.microsoft.com/fwlink/?LinkID=177550 for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "User name already exists. Please enter a 
                    different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A user name for that e-mail address 
                    already exists. Please enter a different e-mail 
                    address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please 
                    enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. 
                    Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is 
                    invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided 
                    is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please 
                    check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an 
                    error. Please verify your entry and try again. If 
                    the problem persists, please contact your system 
                    administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been 
                    canceled. Please verify your entry and try again. 
                    If the problem persists, please contact your 
                    system administrator.";

                default:
                    return "An unknown error occurred. Please verify 
                    your entry and try again. If the problem persists, 
                    please contact your system administrator.";
            }
        }
        #endregion
    }
}

To add Captcha validation to the Register action Controller code, we perform a single line of setup in the Register action when the user GETs it, and execute the [CaptchaValidation] filter when the user POSTs it.

The filter attribute will automatically add the appropriate ModelState error if the Captcha code input doesn't match the code displayed to the user in the Captcha picture.

CaptchaHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using BotDetect;
using BotDetect.Web;
using BotDetect.Web.UI;
using BotDetect.Web.UI.Mvc;

public class CaptchaHelper
{
    public static MvcCaptcha GetRegistrationCaptcha()
    {
        // create the control instance
        MvcCaptcha registrationCaptcha = new MvcCaptcha(
        "RegistrationCaptcha");
        registrationCaptcha.UserInputClientID = "CaptchaCode";

        // all Captcha properties are set in this event handler
        registrationCaptcha.InitializedCaptchaControl +=
            new EventHandler<InitializedCaptchaControlEventArgs>(
                RegistrationCaptcha_InitializedCaptchaControl);

        // all Captcha settings have to be saved before rendering
        registrationCaptcha.SaveSettings();

        return registrationCaptcha;
    }

    // event handler used for Captcha control property randomization
    static void RegistrationCaptcha_InitializedCaptchaControl(
      object sender, InitializedCaptchaControlEventArgs e)
    {
        if (e.CaptchaId != "RegistrationCaptcha")
        {
            return;
        }

        CaptchaControl registrationCaptcha = sender as CaptchaControl;

        // fixed Captcha settings 
        registrationCaptcha.ImageSize = new System.Drawing.Size(200, 
          50);
        registrationCaptcha.CodeLength = 4;

        // randomized Captcha settings
        registrationCaptcha.ImageStyle = CaptchaRandomization.
          GetRandomImageStyle();
        registrationCaptcha.SoundStyle = CaptchaRandomization.
          GetRandomSoundStyle();
    }
}

Captcha instance creation and property setting is encapsulated in this simple helper class. This separation allows application Views to stay simple regardless of the amount of Captcha customization chosen.

Global.asax.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace AspNetMvc30CaptchaSampleCSharp
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterGlobalFilters(
        GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
        }

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            // BotDetect requests must not be routed
            routes.IgnoreRoute("{*botdetect}", new { botdetect = @"(.*)
              BotDetectCaptcha\.ashx" });

            // Set the Register action as the sample project default, 
            // since it includes the Captcha control and we want to 
            show it
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Account", action = "Register", id 
                = UrlParameter.Optional } // Parameter defaults
            );

        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
        }
    }
}

Since all BotDetect requests are handled by the BotDetect HttpHandler, ASP.NET Url Routing must be configured to ignore BotDetect requests, which can be achieved by a single line of code in Global.asax code-behind.

Web.config

<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, 
  please visit
  http://go.microsoft.com/fwlink/?LinkId=152368
  -->

<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated 
         Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;
         User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

  <appSettings>
    <add key="webpages:Version" value="1.0.0.0"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>

  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Mvc, Version=3.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.WebPages, Version=1.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>

    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>

    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.
        Security.SqlMembershipProvider" 
        connectionStringName="ApplicationServices"
        enablePasswordRetrieval="false" 
        enablePasswordReset="true" 
        requiresQuestionAndAnswer="false" 
        requiresUniqueEmail="false"
        maxInvalidPasswordAttempts="5" 
        minRequiredPasswordLength="6" 
        minRequiredNonalphanumericCharacters="0" 
        passwordAttemptWindow="10"
        applicationName="/" />
      </providers>
    </membership>

    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.
        SqlProfileProvider" connectionStringName="ApplicationServices" 
        applicationName="/" />
      </providers>
    </profile>

    <roleManager enabled="false">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.
        SqlRoleProvider" connectionStringName="ApplicationServices" 
        applicationName="/" />
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.
        Security.WindowsTokenRoleProvider" applicationName="/" />
      </providers>
    </roleManager>

  <!-- make sure Session State is enabled -->
  <pages enableSessionState="true">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>

  <!-- configure Session State for BotDetect use -->
  <sessionState mode="InProc" cookieless="AutoDetect" timeout="20" 
    sessionIDManagerType="BotDetect.Web.CustomSessionIdManager, 
      BotDetect"/>

  <httpHandlers>
    <!-- register HttpHandler used for BotDetect Captcha requests -->
    <add verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.
      CaptchaHandler, BotDetect"/>
  </httpHandlers>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true"/>
  <handlers>
    <!-- register HttpHandler used for BotDetect Captcha requests -->
    <remove name="BotDetectCaptchaHandler"/>
    <add name="BotDetectCaptchaHandler" preCondition="integratedMode" 
      verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.
        CaptchaHandler, BotDetect"/>
  </handlers>
  </system.webServer>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" 
        publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.
        0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

The application's web.config file includes the standard BotDetect HttpHandler and Session state configuration elements.

ASP.NET MVC 2.0 / Visual Studio 2008 / .NET 3.5

By default, the .NET 3.5 C# version of the ASP.NET MVC 2.0 Captcha sample project is installed at:
C:\Program Files\Lanapsoft\BotDetect 3 CAPTCHA Component\Asp.Net\v3.5\WebApp\AspNetmvc20CaptchaSample\CSharp

You can also run it from the BotDetect Start Menu:
Programs > Lanapsoft > BotDetect 3 CAPTCHA Component > ASP.NET > DotNET 3.5 Web Applications > Run

Models\AccountModels.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace AspNetMvc20CaptchaSampleCSharp.Models
{
    #region Models
    [PropertiesMustMatch("NewPassword", "ConfirmPassword", 
    ErrorMessage = "The new password and confirmation password do not 
    match.")]
    public class ChangePasswordModel
    {
        [Required]
        [DataType(DataType.Password)]
        [DisplayName("Current password")]
        public string OldPassword { get; set; }

        [Required]
        [ValidatePasswordLength]
        [DataType(DataType.Password)]
        [DisplayName("New password")]
        public string NewPassword { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [DisplayName("Confirm new password")]
        public string ConfirmPassword { get; set; }
    }

    public class LogOnModel
    {
        [Required]
        [DisplayName("User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [DisplayName("Password")]
        public string Password { get; set; }

        [DisplayName("Remember me?")]
        public bool RememberMe { get; set; }
    }

    [PropertiesMustMatch("Password", "ConfirmPassword", ErrorMessage = 
    "The password and confirmation password do not match.")]
    public class RegisterModel
    {
        [Required]
        [DisplayName("User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [DisplayName("Email address")]
        public string Email { get; set; }

        [Required]
        [ValidatePasswordLength]
        [DataType(DataType.Password)]
        [DisplayName("Password")]
        public string Password { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [DisplayName("Confirm password")]
        public string ConfirmPassword { get; set; }

        // add Captcha code as a field in the protected action Model
        [Required(ErrorMessage = "Retyping the characters from the 
          picture is required.")]
        [DisplayName("Please retype the characters from the picture")]
        public string CaptchaCode { get; set; }
    }
    #endregion

    #region Services
    // The FormsAuthentication type is sealed and contains static 
    members, so it is difficult to
    // unit test code that calls its members. The interface and helper 
    class below demonstrate
    // how to create an abstract wrapper around such a type in order 
    to make the AccountController
    // code unit testable.

    public interface IMembershipService
    {
        int MinPasswordLength { get; }

        bool ValidateUser(string userName, string password);
        MembershipCreateStatus CreateUser(string userName, string 
        password, string email);
        bool ChangePassword(string userName, string oldPassword, 
        string newPassword);
    }

    public class AccountMembershipService : IMembershipService
    {
        private readonly MembershipProvider _provider;

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? Membership.Provider;
        }

        public int MinPasswordLength
        {
            get
            {
                return _provider.MinRequiredPasswordLength;
            }
        }

        public bool ValidateUser(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "userName");
            if (String.IsNullOrEmpty(password)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "password");

            return _provider.ValidateUser(userName, password);
        }

        public MembershipCreateStatus CreateUser(string userName, 
        string password, string email)
        {
            if (String.IsNullOrEmpty(userName)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "userName");
            if (String.IsNullOrEmpty(password)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "password");
            if (String.IsNullOrEmpty(email)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "email");

            MembershipCreateStatus status;
            _provider.CreateUser(userName, password, email, null, null,
            true, null, out status);
            return status;
        }

        public bool ChangePassword(string userName, string oldPassword,
        string newPassword)
        {
            if (String.IsNullOrEmpty(userName)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "userName");
            if (String.IsNullOrEmpty(oldPassword)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "oldPassword");
            if (String.IsNullOrEmpty(newPassword)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "newPassword");

            // The underlying ChangePassword() will throw an exception 
            rather
            // than return false in certain failure scenarios.
            try
            {
                MembershipUser currentUser = _provider.GetUser(
                userName, true /* userIsOnline */);
                return currentUser.ChangePassword(oldPassword, 
                newPassword);
            }
            catch (ArgumentException)
            {
                return false;
            }
            catch (MembershipPasswordException)
            {
                return false;
            }
        }
    }

    public interface IFormsAuthenticationService
    {
        void SignIn(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationService : 
    IFormsAuthenticationService
    {
        public void SignIn(string userName, bool 
        createPersistentCookie)
        {
            if (String.IsNullOrEmpty(userName)) throw new 
            ArgumentException("Value cannot be null or empty.", 
            "userName");

            FormsAuthentication.SetAuthCookie(userName, 
            createPersistentCookie);
        }

        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }
    #endregion

    #region Validation
    public static class AccountValidation
    {
        public static string ErrorCodeToString(MembershipCreateStatus 
        createStatus)
        {
            // See http://go.microsoft.com/fwlink/?LinkID=177550 for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Username already exists. Please enter a 
                    different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A username for that e-mail address already 
                    exists. Please enter a different e-mail address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please 
                    enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. 
                    Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is 
                    invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided 
                    is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please 
                    check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an 
                    error. Please verify your entry and try again. If 
                    the problem persists, please contact your system 
                    administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been 
                    canceled. Please verify your entry and try again. 
                    If the problem persists, please contact your 
                    system administrator.";

                default:
                    return "An unknown error occurred. Please verify 
                    your entry and try again. If the problem persists, 
                    please contact your system administrator.";
            }
        }
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, 
    Inherited = true)]
    public sealed class PropertiesMustMatchAttribute : 
    ValidationAttribute
    {
        private const string _defaultErrorMessage = "'{0}' and '{1}' 
        do not match.";
        private readonly object _typeId = new object();

        public PropertiesMustMatchAttribute(string originalProperty, 
        string confirmProperty)
            : base(_defaultErrorMessage)
        {
            OriginalProperty = originalProperty;
            ConfirmProperty = confirmProperty;
        }

        public string ConfirmProperty { get; private set; }
        public string OriginalProperty { get; private set; }

        public override object TypeId
        {
            get
            {
                return _typeId;
            }
        }

        public override string FormatErrorMessage(string name)
        {
            return String.Format(CultureInfo.CurrentUICulture, 
            ErrorMessageString,
                OriginalProperty, ConfirmProperty);
        }

        public override bool IsValid(object value)
        {
            PropertyDescriptorCollection properties = TypeDescriptor.
            GetProperties(value);
            object originalValue = properties.Find(OriginalProperty, 
            true /* ignoreCase */).GetValue(value);
            object confirmValue = properties.Find(ConfirmProperty, 
            true /* ignoreCase */).GetValue(value);
            return Object.Equals(originalValue, confirmValue);
        }
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property,
    AllowMultiple = false, Inherited = true)]
    public sealed class ValidatePasswordLengthAttribute : 
    ValidationAttribute
    {
        private const string _defaultErrorMessage = "'{0}' must be at 
        least {1} characters long.";
        private readonly int _minCharacters = Membership.Provider.
        MinRequiredPasswordLength;

        public ValidatePasswordLengthAttribute()
            : base(_defaultErrorMessage)
        {
        }

        public override string FormatErrorMessage(string name)
        {
            return String.Format(CultureInfo.CurrentUICulture, 
            ErrorMessageString,
                name, _minCharacters);
        }

        public override bool IsValid(object value)
        {
            string valueAsString = value as string;
            return (valueAsString != null && valueAsString.Length >= 
            _minCharacters);
        }
    }
    #endregion

}

Since we want to include Captcha validation in the Register action, we add a string field representing the Captcha code to the related RegisterModel class declaration.

The [Required] attribute specifies that the Captcha code input field cannot be left blank (and which error message to display if it is), while the [Display] attribute declares the text description used to prompt users to type the Captcha code.

Views\Account\Register.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<AspNetMvc20CaptchaSampleCSharp.
Models.RegisterModel>" %>

<asp:Content ID="registerTitle" ContentPlaceHolderID="TitleContent" 
runat="server">
    Register
</asp:Content>

<asp:Content ID="BotDetectStylesheets" ContentPlaceHolderID="includes" 
  runat="server">
    <link href="<%= BotDetect.Web.CaptchaUrls.LayoutStyleSheetUrl %>" 
    rel="stylesheet" type="text/css" />
    <link href="<%= BotDetect.Web.CaptchaUrls.
    SoundPackageWarningStyleUrl %>" rel="stylesheet" type="text/css" />
</asp:Content>

<asp:Content ID="registerContent" ContentPlaceHolderID="MainContent" 
runat="server">
    <h2>Create a New Account</h2>
    <p>
        Use the form below to create a new account. 
    </p>
    <p>
        Passwords are required to be a minimum of <%: ViewData[
        "PasswordLength"] %> characters in length.
    </p>

    <% using (Html.BeginForm()) { %>
        <%: Html.ValidationSummary(true, "Account creation was 
        unsuccessful. Please correct the errors and try again.") %>
        <div>
            <fieldset id="RegisterFields">
                <legend>Account Information</legend>
                
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.UserName) %>
                </div>
                <div class="editor-field">
                    <%: Html.TextBoxFor(m => m.UserName) %>
                    <%: Html.ValidationMessageFor(m => m.UserName) %>
                </div>
                
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Email) %>
                </div>
                <div class="editor-field">
                    <%: Html.TextBoxFor(m => m.Email) %>
                    <%: Html.ValidationMessageFor(m => m.Email) %>
                </div>
                
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.Password) %>
                </div>
                <div class="editor-field">
                    <%: Html.PasswordFor(m => m.Password) %>
                    <%: Html.ValidationMessageFor(m => m.Password) %>
                </div>
                
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.ConfirmPassword) %>
                </div>
                <div class="editor-field">
                    <%: Html.PasswordFor(m => m.ConfirmPassword) %>
                    <%: Html.ValidationMessageFor(m => m.
                    ConfirmPassword) %>
                </div>

                <% // add Captcha validation controls to the protected 
                  action View
                MvcCaptcha registrationCaptcha = CaptchaHelper.
                  GetRegistrationCaptcha();
                if (!registrationCaptcha.IsSolved) { %>
                    <div class="editor-label">
                        <%: Html.Captcha(registrationCaptcha) %>
                        <%: Html.LabelFor(m => m.CaptchaCode) %>
                    </div>
                    <div class="editor-field">
                        <%: Html.TextBoxFor(m => m.CaptchaCode)%>
                        <%: Html.ValidationMessageFor(m => m.
                        CaptchaCode)%>
                    </div>
                <% } %>
                
                <p style="width: 195px; text-align: right;">
                    <input type="submit" value="Register" />
                </p>
            </fieldset>
        </div>
    <% } %>
</asp:Content>

BotDetect stylesheets are included in the generated page Header using the includes content placeholder defined in the Master page.

To keep View code simple, we delegate MvcCaptcha instance creation to a helper class, and use it along with Model fields to generate the required markup using straightforward HtmlHelper calls.

Since we want to avoid re-displaying the Captcha challenge to users after they solve it, we only call the markup generation code if the IsSolved property is not set by a previous successful Captcha validation.

Views\Shared\Site.Master

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.
w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" />
    </title>
    <link href="../../Content/Site.css" rel="stylesheet" 
    type="text/css" />
    <asp:ContentPlaceHolder ID="includes" runat="server" />
</head>

<body>
    <div class="page">

        <div id="header">
            <div id="title">
                <h1>BotDetect CAPTCHA ASP.NET MVC 2.0 Sample</h1>
            </div>
              
            <div id="logindisplay">
                <% Html.RenderPartial("LogOnUserControl"); %>
            </div> 
            
            <div id="menucontainer">
            
                <ul id="menu">              
                    <li><%: Html.ActionLink("Home", "Index", "Home")%>
                    </li>
                    <li><%: Html.ActionLink("About", "About", "Home")%>
                    </li>
                </ul>
            
            </div>
        </div>

        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />

            <div id="footer">
            </div>
        </div>
    </div>
</body>
</html>

To allow easy inclusion of BotDetect stylesheets into Views which require them, the includes content placeholder should be declared in the Master page header.

Controllers\AccountController.cs

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using AspNetMvc20CaptchaSampleCSharp.Models;

using BotDetect.Web.UI.Mvc;

namespace AspNetMvc20CaptchaSampleCSharp.Controllers
{

    [HandleError]
    public class AccountController : Controller
    {

        public IFormsAuthenticationService FormsService { get; set; }
        public IMembershipService MembershipService { get; set; }

        protected override void Initialize(RequestContext 
        requestContext)
        {
            if (FormsService == null) { FormsService = new 
            FormsAuthenticationService(); }
            if (MembershipService == null) { MembershipService = new 
            AccountMembershipService(); }

            base.Initialize(requestContext);
        }

        // **************************************
        // URL: /Account/LogOn
        // **************************************

        public ActionResult LogOn()
        {
            return View();
        }

        [HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if (MembershipService.ValidateUser(model.UserName, 
                model.Password))
                {
                    FormsService.SignIn(model.UserName, model.
                    RememberMe);
                    if (!String.IsNullOrEmpty(returnUrl))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "The user name or 
                    password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        // **************************************
        // URL: /Account/LogOff
        // **************************************

        public ActionResult LogOff()
        {
            FormsService.SignOut();

            return RedirectToAction("Index", "Home");
        }

        // **************************************
        // URL: /Account/Register
        // **************************************

        public ActionResult Register()
        {
            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;

            // when the View is accessed directly and not posted, we 
            // clear any remembered CAPTCHA solving state. 
            // The users only have to solve the CAPTCHA once within a 
            // single registration, but if they reload the Register 
            // page later, it is shown again. 
            // Otherwise, they could register an unlimited number of 
            // accounts within a single Session after solving the  
            // CAPTCHA only once.
            MvcCaptcha.ResetCaptcha("RegistrationCaptcha");

            return View();
        }

        [HttpPost]
        [CaptchaValidation("CaptchaCode", "RegistrationCaptcha", "Your 
          input doesn't match displayed characters.")]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = 
                MembershipService.CreateUser(model.UserName, model.
                Password, model.Email);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsService.SignIn(model.UserName, false /* 
                    createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("", AccountValidation.
                    ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;
            return View(model);
        }

        // **************************************
        // URL: /Account/ChangePassword
        // **************************************

        [Authorize]
        public ActionResult ChangePassword()
        {
            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;
            return View();
        }

        [Authorize]
        [HttpPost]
        public ActionResult ChangePassword(ChangePasswordModel model)
        {
            if (ModelState.IsValid)
            {
                if (MembershipService.ChangePassword(User.Identity.
                Name, model.OldPassword, model.NewPassword))
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                else
                {
                    ModelState.AddModelError("", "The current password 
                    is incorrect or the new password is invalid.");
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;
            return View(model);
        }

        // **************************************
        // URL: /Account/ChangePasswordSuccess
        // **************************************

        public ActionResult ChangePasswordSuccess()
        {
            return View();
        }

    }
}

To add Captcha validation to the Register action Controller code, we perform a single line of setup in the Register action when the user GETs it, and execute the [CaptchaValidation] filter when the user POSTs it.

The filter attribute will automatically add the appropriate ModelState error if the Captcha code input doesn't match the code displayed to the user in the Captcha picture.

CaptchaHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using BotDetect;
using BotDetect.Web;
using BotDetect.Web.UI;
using BotDetect.Web.UI.Mvc;

public class CaptchaHelper
{
    public static MvcCaptcha GetRegistrationCaptcha()
    {
        // create the control instance
        MvcCaptcha registrationCaptcha = new MvcCaptcha(
          "RegistrationCaptcha");
        registrationCaptcha.UserInputClientID = "CaptchaCode";

        // all Captcha properties are set in this event handler
        registrationCaptcha.InitializedCaptchaControl +=
            new EventHandler<InitializedCaptchaControlEventArgs>(
                RegistrationCaptcha_InitializedCaptchaControl);

        // all Captcha settings have to be saved before rendering
        registrationCaptcha.SaveSettings();

        return registrationCaptcha;
    }

    // event handler used for Captcha control property randomization
    public static void RegistrationCaptcha_InitializedCaptchaControl(
      object sender, InitializedCaptchaControlEventArgs e)
    {
        if (e.CaptchaId != "RegistrationCaptcha")
        {
            return;
        }

        CaptchaControl registrationCaptcha = sender as CaptchaControl;

        // fixed Captcha settings 
        registrationCaptcha.ImageSize = new System.Drawing.Size(200, 
          50);
        registrationCaptcha.CodeLength = 4;

        // randomized Captcha settings
        registrationCaptcha.ImageStyle = CaptchaRandomization.
          GetRandomImageStyle();
        registrationCaptcha.SoundStyle = CaptchaRandomization.
          GetRandomSoundStyle();
    }
}

Captcha instance creation and property setting is encapsulated in this simple helper class. This separation allows application Views to stay simple regardless of the amount of Captcha customization chosen.

Global.asax.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace AspNetMvc20CaptchaSampleCSharp
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            // BotDetect requests must not be routed
            routes.IgnoreRoute("{*botdetect}", new { botdetect = @"(.*)
              BotDetectCaptcha\.ashx" });


            // Set the Register action as the sample project default, 
            // since it includes the Captcha control and we want to 
            show it
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Account", action = "Register", id 
                = UrlParameter.Optional } // Parameter defaults
            );

        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterRoutes(RouteTable.Routes);
        }
    }
}

Since all BotDetect requests are handled by the BotDetect HttpHandler, ASP.NET Url Routing must be configured to ignore BotDetect requests, which can be achieved by a single line of code in Global.asax code-behind.

Web.config

<?xml version="1.0"?>

<!--
  For more information on how to configure your ASP.NET application, 
  please visit
  http://go.microsoft.com/fwlink/?LinkId=152368
  -->

<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated 
         Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;
         User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Mvc, Version=2.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>

    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>

    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.
        Security.SqlMembershipProvider" 
        connectionStringName="ApplicationServices"
        enablePasswordRetrieval="false" 
        enablePasswordReset="true" 
        requiresQuestionAndAnswer="false" 
        requiresUniqueEmail="false"
        maxInvalidPasswordAttempts="5" 
        minRequiredPasswordLength="6" 
        minRequiredNonalphanumericCharacters="0" 
        passwordAttemptWindow="10"
        applicationName="/" />
      </providers>
    </membership>

    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.
        SqlProfileProvider" connectionStringName="ApplicationServices" 
        applicationName="/" />
      </providers>
    </profile>

    <roleManager enabled="false">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.
        SqlRoleProvider" connectionStringName="ApplicationServices" 
        applicationName="/" />
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.
        Security.WindowsTokenRoleProvider" applicationName="/" />
      </providers>
    </roleManager>

  <!-- make sure Session State is enabled -->
    <pages enableSessionState="true">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <!-- add BotDetect namespaces for coding convenience -->
        <add namespace="BotDetect"/>
        <add namespace="BotDetect.Web"/>
        <add namespace="BotDetect.Web.UI"/>
        <add namespace="BotDetect.Web.UI.Mvc"/>
      </namespaces>
    </pages>

  <!-- configure Session State for BotDetect use -->
  <sessionState mode="InProc" cookieless="AutoDetect" timeout="20" 
    sessionIDManagerType="BotDetect.Web.CustomSessionIdManager, 
      BotDetect"/>

  <httpHandlers>
    <!-- register HttpHandler used for BotDetect Captcha requests -->
    <add verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.
      CaptchaHandler, BotDetect"/>
  </httpHandlers>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true"/>
  <handlers>
    <!-- register HttpHandler used for BotDetect Captcha requests -->
    <remove name="BotDetectCaptchaHandler"/>
    <add name="BotDetectCaptchaHandler" preCondition="integratedMode" 
      verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.
        CaptchaHandler, BotDetect"/>
  </handlers>
  </system.webServer>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" 
        publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

The application's web.config file includes the standard BotDetect HttpHandler and Session state configuration elements.

ASP.NET MVC 1.0 / Visual Studio 2008 / .NET 3.5

By default, the .NET 3.5 C# version of the ASP.NET MVC 1.0 Captcha sample project is installed at:
C:\Program Files\Lanapsoft\BotDetect 3 CAPTCHA Component\Asp.Net\v3.5\WebApp\AspNetMVC10CaptchaSample\CSharp

You can also run it from the BotDetect Start Menu:
Programs > Lanapsoft > BotDetect 3 CAPTCHA Component > ASP.NET > DotNET 3.5 Web Applications > Run

Views\Account\Register.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="registerTitle" ContentPlaceHolderID="TitleContent" 
runat="server">
    Register
</asp:Content>

<asp:Content ID="BotDetectStylesheets" ContentPlaceHolderID="includes" 
  runat="server">
    <link href="<%= BotDetect.Web.CaptchaUrls.LayoutStyleSheetUrl %>" 
    rel="stylesheet" type="text/css" />
    <link href="<%= BotDetect.Web.CaptchaUrls.
    SoundPackageWarningStyleUrl %>" rel="stylesheet" type="text/css" />
</asp:Content>

<asp:Content ID="registerContent" ContentPlaceHolderID="MainContent" 
runat="server">
    <h2>Create a New Account</h2>
    <p>
        Use the form below to create a new account. 
    </p>
    <p>
        Passwords are required to be a minimum of <%=Html.Encode(
        ViewData["PasswordLength"])%> characters in length.
    </p>
    <p>
        <%= Html.ValidationSummary("Account creation was unsuccessful. 
        Please correct the errors and try again.") %>
    </p>
    <% using (Html.BeginForm()) { %>
        <div>
            <fieldset id="RegisterFields">
                <legend>Account Information</legend>
                <p>
                    <label for="username">Username:</label>
                    <%= Html.TextBox("username") %>
                    <%= Html.ValidationMessage("username") %>
                </p>
                <p>
                    <label for="email">Email:</label>
                    <%= Html.TextBox("email") %>
                    <%= Html.ValidationMessage("email") %>
                </p>
                <p>
                    <label for="password">Password:</label>
                    <%= Html.Password("password") %>
                    <%= Html.ValidationMessage("password") %>
                </p>
                <p>
                    <label for="confirmPassword">Confirm password:<
                    /label>
                    <%= Html.Password("confirmPassword") %>
                    <%= Html.ValidationMessage("confirmPassword") %>
                </p>
                
                <%  MvcCaptcha registrationCaptcha = CaptchaHelper.
                  GetRegistrationCaptcha();
                    if (!registrationCaptcha.IsSolved)
                    { %>
                <%= Html.Captcha(registrationCaptcha) %>
                <p>
                    <label for="captchaCode">Please retype the 
                    characters from the picture:</label>
                    <%= Html.TextBox("captchaCode")%>
                    <%= Html.ValidationMessage("captchaCode")%>
                </p>
                <% } %>
                
                <p>
                    <input type="submit" value="Register" />
                </p>
            </fieldset>
        </div>
    <% } %>
</asp:Content>

BotDetect stylesheets are included in the generated page Header using the includes content placeholder defined in the Master page.

To keep View code simple, we delegate MvcCaptcha instance creation to a helper class, and use it along with Model fields to generate the required markup using straightforward HtmlHelper calls.

Since we want to avoid re-displaying the Captcha challenge to users after they solve it, we only call the markup generation code if the IsSolved property is not set by a previous successful Captcha validation.

Views\Shared\Site.Master

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.
w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" />
    </title>
    <link href="../../Content/Site.css" rel="stylesheet" 
    type="text/css" />
    <asp:ContentPlaceHolder id="includes" runat="server"></asp:
    ContentPlaceHolder>
</head>

<body>
    <div class="page">

        <div id="header">
            <div id="title">
                <h1>BotDetect CAPTCHA ASP.NET MVC 1.0 Sample</h1>
            </div>
              
            <div id="logindisplay">
                <% Html.RenderPartial("LogOnUserControl"); %>
            </div> 
            
            <div id="menucontainer">
            
                <ul id="menu">              
                    <li><%= Html.ActionLink("Home", "Index", "Home")%>
                    </li>
                    <li><%= Html.ActionLink("About", "About", "Home")%>
                    </li>
                </ul>
            
            </div>
        </div>

        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />

            <div id="footer">
            </div>
        </div>
    </div>
</body>
</html>

To allow easy inclusion of BotDetect stylesheets into Views which require them, the includes content placeholder should be declared in the Master page header.

Controllers\AccountController.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;

using BotDetect.Web.UI.Mvc;

namespace AspNetMvc10CaptchaSampleCSharp.Controllers
{

    [HandleError]
    public class AccountController : Controller
    {

        // This constructor is used by the MVC framework to 
        instantiate the controller using
        // the default forms authentication and membership providers.

        public AccountController()
            : this(null, null)
        {
        }

        // This constructor is not used by the MVC framework but is 
        instead provided for ease
        // of unit testing this type. See the comments at the end of 
        this file for more
        // information.
        public AccountController(IFormsAuthentication formsAuth, 
        IMembershipService service)
        {
            FormsAuth = formsAuth ?? new FormsAuthenticationService();
            MembershipService = service ?? new 
            AccountMembershipService();
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        public IMembershipService MembershipService
        {
            get;
            private set;
        }

        public ActionResult LogOn()
        {

            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.
        Design", "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as 
            Controller.Redirect()")]
        public ActionResult LogOn(string userName, string password, 
        bool rememberMe, string returnUrl)
        {

            if (!ValidateLogOn(userName, password))
            {
                return View();
            }

            FormsAuth.SignIn(userName, rememberMe);
            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }

        public ActionResult LogOff()
        {

            FormsAuth.SignOut();

            return RedirectToAction("Index", "Home");
        }

        public ActionResult Register()
        {

            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;

            // when the View is accessed directly and not posted, we 
            // clear any remembered CAPTCHA solving state. 
            // The users only have to solve the CAPTCHA once within a 
            // single registration, but if they reload the Register  
            // page later, it is shown again. 
            // Otherwise, they could register an unlimited number of 
            // accounts within a single Session after solving the 
            // CAPTCHA only  once.
            MvcCaptcha.ResetCaptcha("RegistrationCaptcha");

            return View();
        }


        [AcceptVerbs(HttpVerbs.Post)]
        [CaptchaValidation("captchaCode", "RegistrationCaptcha", "Your 
          input doesn't match displayed characters.")]
        public ActionResult Register(string userName, string email, 
        string password, string confirmPassword)
        {
            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;

            if (ValidateRegistration(userName, email, password, 
            confirmPassword))
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = 
                MembershipService.CreateUser(userName, password, email)
                ;

                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsAuth.SignIn(userName, false /* 
                    createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("_FORM", 
                    ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

        [Authorize]
        public ActionResult ChangePassword()
        {

            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;

            return View();
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.
        Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Exceptions result in password not being 
            changed.")]
        public ActionResult ChangePassword(string currentPassword, 
        string newPassword, string confirmPassword)
        {

            ViewData["PasswordLength"] = MembershipService.
            MinPasswordLength;

            if (!ValidateChangePassword(currentPassword, newPassword, 
            confirmPassword))
            {
                return View();
            }

            try
            {
                if (MembershipService.ChangePassword(User.Identity.
                Name, currentPassword, newPassword))
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                else
                {
                    ModelState.AddModelError("_FORM", "The current 
                    password is incorrect or the new password is 
                    invalid.");
                    return View();
                }
            }
            catch
            {
                ModelState.AddModelError("_FORM", "The current 
                password is incorrect or the new password is invalid.")
                ;
                return View();
            }
        }

        public ActionResult ChangePasswordSuccess()
        {

            return View();
        }

        protected override void OnActionExecuting(
        ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity is 
            WindowsIdentity)
            {
                throw new InvalidOperationException("Windows 
                authentication is not supported.");
            }
        }

        #region Validation Methods

        private bool ValidateChangePassword(string currentPassword, 
        string newPassword, string confirmPassword)
        {
            if (String.IsNullOrEmpty(currentPassword))
            {
                ModelState.AddModelError("currentPassword", "You must 
                specify a current password.");
            }
            if (newPassword == null || newPassword.Length < 
            MembershipService.MinPasswordLength)
            {
                ModelState.AddModelError("newPassword",
                    String.Format(CultureInfo.CurrentCulture,
                         "You must specify a new password of {0} or 
                         more characters.",
                         MembershipService.MinPasswordLength));
            }

            if (!String.Equals(newPassword, confirmPassword, 
            StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "The new password 
                and confirmation password do not match.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateLogOn(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "You must specify 
                a username.");
            }
            if (String.IsNullOrEmpty(password))
            {
                ModelState.AddModelError("password", "You must specify 
                a password.");
            }
            if (!MembershipService.ValidateUser(userName, password))
            {
                ModelState.AddModelError("_FORM", "The username or 
                password provided is incorrect.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateRegistration(string userName, string 
        email, string password, string confirmPassword)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "You must specify 
                a username.");
            }
            if (String.IsNullOrEmpty(email))
            {
                ModelState.AddModelError("email", "You must specify an 
                email address.");
            }
            if (password == null || password.Length < 
            MembershipService.MinPasswordLength)
            {
                ModelState.AddModelError("password",
                    String.Format(CultureInfo.CurrentCulture,
                         "You must specify a password of {0} or more 
                         characters.",
                         MembershipService.MinPasswordLength));
            }
            if (!String.Equals(password, confirmPassword, 
            StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "The new password 
                and confirmation password do not match.");
            }

            return ModelState.IsValid;
        }

        private static string ErrorCodeToString(MembershipCreateStatus 
        createStatus)
        {
            // See http://msdn.microsoft.com/en-us/library/system.web.
            security.membershipcreatestatus.aspx for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Username already exists. Please enter a 
                    different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A username for that e-mail address already 
                    exists. Please enter a different e-mail address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please 
                    enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. 
                    Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is 
                    invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided 
                    is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please 
                    check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an 
                    error. Please verify your entry and try again. If 
                    the problem persists, please contact your system 
                    administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been 
                    canceled. Please verify your entry and try again. 
                    If the problem persists, please contact your 
                    system administrator.";

                default:
                    return "An unknown error occurred. Please verify 
                    your entry and try again. If the problem persists, 
                    please contact your system administrator.";
            }
        }
        #endregion
    }

    // The FormsAuthentication type is sealed and contains static 
    members, so it is difficult to
    // unit test code that calls its members. The interface and helper 
    class below demonstrate
    // how to create an abstract wrapper around such a type in order 
    to make the AccountController
    // code unit testable.

    public interface IFormsAuthentication
    {
        void SignIn(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationService : IFormsAuthentication
    {
        public void SignIn(string userName, bool 
        createPersistentCookie)
        {
            FormsAuthentication.SetAuthCookie(userName, 
            createPersistentCookie);
        }
        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }

    public interface IMembershipService
    {
        int MinPasswordLength { get; }

        bool ValidateUser(string userName, string password);
        MembershipCreateStatus CreateUser(string userName, string 
        password, string email);
        bool ChangePassword(string userName, string oldPassword, 
        string newPassword);
    }

    public class AccountMembershipService : IMembershipService
    {
        private MembershipProvider _provider;

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? Membership.Provider;
        }

        public int MinPasswordLength
        {
            get
            {
                return _provider.MinRequiredPasswordLength;
            }
        }

        public bool ValidateUser(string userName, string password)
        {
            return _provider.ValidateUser(userName, password);
        }

        public MembershipCreateStatus CreateUser(string userName, 
        string password, string email)
        {
            MembershipCreateStatus status;
            _provider.CreateUser(userName, password, email, null, null,
            true, null, out status);
            return status;
        }

        public bool ChangePassword(string userName, string oldPassword,
        string newPassword)
        {
            MembershipUser currentUser = _provider.GetUser(userName, 
            true /* userIsOnline */);
            return currentUser.ChangePassword(oldPassword, newPassword)
            ;
        }
    }
}

To add Captcha validation to the Register action Controller code, we perform a single line of setup in the Register action when the user GETs it, and execute the [CaptchaValidation] filter when the user POSTs it.

The filter attribute will automatically add the appropriate ModelState error if the Captcha code input doesn't match the code displayed to the user in the Captcha picture.

CaptchaHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using BotDetect;
using BotDetect.Web;
using BotDetect.Web.UI;
using BotDetect.Web.UI.Mvc;

public class CaptchaHelper
{
    public static MvcCaptcha GetRegistrationCaptcha()
    {
        // create the control instance
        MvcCaptcha registrationCaptcha = new MvcCaptcha(
          "RegistrationCaptcha");
        registrationCaptcha.UserInputClientID = "captchaCode";

        // all Captcha properties are set in this event handler
        registrationCaptcha.InitializedCaptchaControl +=
            new EventHandler<InitializedCaptchaControlEventArgs>(
                RegistrationCaptcha_InitializedCaptchaControl);

        // all Captcha settings have to be saved before rendering
        registrationCaptcha.SaveSettings();

        return registrationCaptcha;
    }

    // event handler used for Captcha control property randomization
    public static void RegistrationCaptcha_InitializedCaptchaControl(
      object sender, InitializedCaptchaControlEventArgs e)
    {
        if (e.CaptchaId != "RegistrationCaptcha")
        {
            return;
        }

        CaptchaControl registrationCaptcha = sender as CaptchaControl;

        // fixed Captcha settings 
        registrationCaptcha.ImageSize = new System.Drawing.Size(200, 
          50);
        registrationCaptcha.CodeLength = 4;

        // randomized Captcha settings
        registrationCaptcha.ImageStyle = CaptchaRandomization.
          GetRandomImageStyle();
        registrationCaptcha.SoundStyle = CaptchaRandomization.
          GetRandomSoundStyle();
    }
}

Captcha instance creation and property setting is encapsulated in this simple helper class. This separation allows application Views to stay simple regardless of the amount of Captcha customization chosen.

Global.asax.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace AspNetMvc10CaptchaSampleCSharp
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            // BotDetect requests must not be routed
            routes.IgnoreRoute("{*botdetect}", new { botdetect = @"(.*)
              BotDetectCaptcha\.ashx" });


            // Set the Register action as the sample project default, 
            // since it includes the Captcha control and we want to 
            show it
            routes.MapRoute(
               "Default",                                              
               // Route name
               "{controller}/{action}/{id}",                           
               // URL with parameters
               new { controller = "Account", action = "Register", id = 
               "" }  // Parameter defaults
            );
        }

        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);
        }
    }
}

Since all BotDetect requests are handled by the BotDetect HttpHandler, ASP.NET Url Routing must be configured to ignore BotDetect requests, which can be achieved by a single line of code in Global.asax code-behind.

Web.config

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="system.web.extensions" type="System.Web.
    Configuration.SystemWebExtensionsSectionGroup, System.Web.
    Extensions, Version=3.5.0.0, Culture=neutral, 
    PublicKeyToken=31BF3856AD364E35">
      <sectionGroup name="scripting" type="System.Web.Configuration.
      ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, 
      Culture=neutral, PublicKeyToken=31BF3856AD364E35">
        <section name="scriptResourceHandler" type="System.Web.
        Configuration.ScriptingScriptResourceHandlerSection, System.
        Web.Extensions, Version=3.5.0.0, Culture=neutral, 
        PublicKeyToken=31BF3856AD364E35" requirePermission="false" 
        allowDefinition="MachineToApplication"/>
        <sectionGroup name="webServices" type="System.Web.
        Configuration.ScriptingWebServicesSectionGroup, System.Web.
        Extensions, Version=3.5.0.0, Culture=neutral, 
        PublicKeyToken=31BF3856AD364E35">
          <section name="jsonSerialization" type="System.Web.
          Configuration.ScriptingJsonSerializationSection, System.Web.
          Extensions, Version=3.5.0.0, Culture=neutral, 
          PublicKeyToken=31BF3856AD364E35" requirePermission="false" 
          allowDefinition="Everywhere"/>
          <section name="profileService" type="System.Web.
          Configuration.ScriptingProfileServiceSection, System.Web.
          Extensions, Version=3.5.0.0, Culture=neutral, 
          PublicKeyToken=31BF3856AD364E35" requirePermission="false" 
          allowDefinition="MachineToApplication"/>
          <section name="authenticationService" type="System.Web.
          Configuration.ScriptingAuthenticationServiceSection, System.
          Web.Extensions, Version=3.5.0.0, Culture=neutral, 
          PublicKeyToken=31BF3856AD364E35" requirePermission="false" 
          allowDefinition="MachineToApplication"/>
          <section name="roleService" type="System.Web.Configuration.
          ScriptingRoleServiceSection, System.Web.Extensions, 
          Version=3.5.0.0, Culture=neutral, 
          PublicKeyToken=31BF3856AD364E35" requirePermission="false" 
          allowDefinition="MachineToApplication"/>
        </sectionGroup>
      </sectionGroup>
    </sectionGroup>
  </configSections>
  <connectionStrings>
    <add name="ApplicationServices" connectionString="data source=.
    \SQLEXPRESS;Integrated Security=SSPI;
    AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" 
    providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <system.web>
    <compilation debug="true">
      <assemblies>
        <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, 
        PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Extensions, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Abstractions, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Mvc, Version=1.0.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Xml.Linq, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Data.Linq, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
      </assemblies>
    </compilation>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>
    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.
        Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, 
        Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
        connectionStringName="ApplicationServices" 
        enablePasswordRetrieval="false" enablePasswordReset="true" 
        requiresQuestionAndAnswer="false" requiresUniqueEmail="false" 
        passwordFormat="Hashed" maxInvalidPasswordAttempts="5" 
        minRequiredPasswordLength="6" 
        minRequiredNonalphanumericCharacters="0" 
        passwordAttemptWindow="10" 
        passwordStrengthRegularExpression="" applicationName="/" />
      </providers>
    </membership>
    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.
        SqlProfileProvider, System.Web, Version=2.0.0.0, 
        Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
        connectionStringName="ApplicationServices" applicationName="/" 
        />
      </providers>
    </profile>
    <roleManager enabled="false">
      <providers>
        <clear/>
        <add connectionStringName="ApplicationServices" 
        applicationName="/" name="AspNetSqlRoleProvider" type="System.
        Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, 
        Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
        <add applicationName="/" name="AspNetWindowsTokenRoleProvider" 
        type="System.Web.Security.WindowsTokenRoleProvider, System.Web,
        Version=2.0.0.0, Culture=neutral, 
        PublicKeyToken=b03f5f7f11d50a3a" />
      </providers>
    </roleManager>
    <!-- make sure Session State is enabled -->
    <pages enableSessionState="true">
      <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" 
        assembly="System.Web.Extensions, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add tagPrefix="asp" namespace="System.Web.UI.WebControls" 
        assembly="System.Web.Extensions, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      </controls>
      <namespaces>
        <add namespace="System.Web.Mvc"/>
        <add namespace="System.Web.Mvc.Ajax"/>
        <add namespace="System.Web.Mvc.Html"/>
        <add namespace="System.Web.Routing"/>
        <add namespace="System.Linq"/>
        <add namespace="System.Collections.Generic"/>
        <!-- add BotDetect namespaces for coding convenience -->
        <add namespace="BotDetect"/>
        <add namespace="BotDetect.Web"/>
        <add namespace="BotDetect.Web.UI"/>
        <add namespace="BotDetect.Web.UI.Mvc"/>
      </namespaces>
    </pages>
    <!-- configure Session State for BotDetect use -->
    <sessionState mode="InProc" cookieless="AutoDetect" timeout="20" 
      sessionIDManagerType="BotDetect.Web.CustomSessionIdManager, 
        BotDetect"/>
    <httpHandlers>
      <remove verb="*" path="*.asmx"/>
      <add verb="*" path="*.asmx" validate="false" type="System.Web.
      Script.Services.ScriptHandlerFactory, System.Web.Extensions, 
      Version=3.5.0.0, Culture=neutral, 
      PublicKeyToken=31BF3856AD364E35"/>
      <add verb="*" path="*_AppService.axd" validate="false" 
      type="System.Web.Script.Services.ScriptHandlerFactory, System.
      Web.Extensions, Version=3.5.0.0, Culture=neutral, 
      PublicKeyToken=31BF3856AD364E35"/>
      <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.
      Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.
      5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" 
      validate="false"/>
      <add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.
      MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral,
      PublicKeyToken=31BF3856AD364E35"/>
      <!-- register HttpHandler used for BotDetect Captcha requests -->
      <add verb="GET" path="BotDetectCaptcha.ashx" type="BotDetect.Web.
        CaptchaHandler, BotDetect"/>
    </httpHandlers>
    <httpModules>
      <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, 
      System.Web.Extensions, Version=3.5.0.0, Culture=neutral, 
      PublicKeyToken=31BF3856AD364E35"/>
      <add name="UrlRoutingModule" type="System.Web.Routing.
      UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, 
      Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    </httpModules>
  </system.web>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp" extension=".cs" 
      warningLevel="4" type="Microsoft.CSharp.CSharpCodeProvider, 
      System, Version=2.0.0.0, Culture=neutral, 
      PublicKeyToken=b77a5c561934e089">
        <providerOption name="CompilerVersion" value="v3.5"/>
        <providerOption name="WarnAsError" value="false"/>
      </compiler>
      <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" 
      warningLevel="4" type="Microsoft.VisualBasic.VBCodeProvider, 
      System, Version=2.0.0.0, Culture=neutral, 
      PublicKeyToken=b77a5c561934e089">
        <providerOption name="CompilerVersion" value="v3.5"/>
        <providerOption name="OptionInfer" value="true"/>
        <providerOption name="WarnAsError" value="false"/>
      </compiler>
    </compilers>
  </system.codedom>
  <system.web.extensions/>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="ScriptModule"/>
      <remove name="UrlRoutingModule"/>
      <add name="ScriptModule" preCondition="managedHandler" 
      type="System.Web.Handlers.ScriptModule, System.Web.Extensions, 
      Version=3.5.0.0, Culture=neutral, 
      PublicKeyToken=31BF3856AD364E35"/>
      <add name="UrlRoutingModule" type="System.Web.Routing.
      UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, 
      Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
    </modules>
    <handlers>
      <remove name="WebServiceHandlerFactory-Integrated"/>
      <remove name="ScriptHandlerFactory"/>
      <remove name="ScriptHandlerFactoryAppServices"/>
      <remove name="ScriptResource"/>
      <remove name="MvcHttpHandler"/>
      <remove name="UrlRoutingHandler"/>
      <add name="ScriptHandlerFactory" verb="*" path="*.asmx" 
      preCondition="integratedMode" type="System.Web.Script.Services.
      ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, 
      Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="ScriptHandlerFactoryAppServices" verb="*" 
      path="*_AppService.axd" preCondition="integratedMode" 
      type="System.Web.Script.Services.ScriptHandlerFactory, System.
      Web.Extensions, Version=3.5.0.0, Culture=neutral, 
      PublicKeyToken=31BF3856AD364E35"/>
      <add name="ScriptResource" preCondition="integratedMode" 
      verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.
      Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.
      5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      <add name="MvcHttpHandler" preCondition="integratedMode" 
      verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, 
      System.Web.Mvc, Version=1.0.0.0, Culture=neutral, 
      PublicKeyToken=31BF3856AD364E35"/>
      <add name="UrlRoutingHandler" preCondition="integratedMode" 
      verb="*" path="UrlRouting.axd" type="System.Web.
      HttpForbiddenHandler, System.Web, Version=2.0.0.0, 
      Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
      <!-- register HttpHandler used for BotDetect Captcha requests -->
      <remove name="BotDetectCaptchaHandler"/>
      <add name="BotDetectCaptchaHandler" 
        preCondition="integratedMode" verb="GET" path="BotDetectCaptcha.
          ashx" type="BotDetect.Web.CaptchaHandler, BotDetect"/>
    </handlers>
  </system.webServer>
</configuration>

The application's web.config file includes the standard BotDetect HttpHandler and Session state configuration elements.