ASP.NET MVC 2.0 Application Template CAPTCHA VB.NET Code Example

The ASP.NET MVC 2.0 application template Captcha example project shows how to use the BotDetect CAPTCHA MvcCaptcha control in ASP.NET MVC 2.0 web applications.

First Time Here?

Check the BotDetect ASP.NET MVC Captcha Quickstart for key integration steps.

Starting with the default ASP.NET MVC 2.0 Visual Studio 2008 project template, the example includes all code required to add CAPTCHA validation to the Account controller Register action.

The example 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 Generator archive to run this example

→ ASP.NET MVC version:

→ .NET programming language:

  • C#
  • VB.NET

Within this page, the root folder of the extracted archive is referred as the <BDC-DIR>.

This example is in the <BDC-DIR>/lgcy-on-lgcy/examples/t_api-captcha-mvc2-web.security.membership/vbnet/ folder; and contains the following files:

Models\AccountModels.vb

Imports System.ComponentModel 
Imports System.ComponentModel.DataAnnotations 
Imports System.Globalization 
Imports BotDetect.Web.Mvc 

#Region "Models" 
<PropertiesMustMatch("NewPassword", "ConfirmPassword", 
ErrorMessage:="The new password and confirmation password do not match.")> _ 
Public Class ChangePasswordModel 
  Private oldPasswordValue As String 
  Private newPasswordValue As String 
  Private confirmPasswordValue As String 

  <Required()> _ 
  <DataType(DataType.Password)> _ 
  <DisplayName("Current password")> _ 
  Public Property OldPassword() As String 
    Get 
      Return oldPasswordValue 
    End Get 
    Set(ByVal value As String) 
      oldPasswordValue = value 
    End Set 
  End Property 

  <Required()> _ 
  <ValidatePasswordLength()> _ 
  <DataType(DataType.Password)> _ 
  <DisplayName("New password")> _ 
  Public Property NewPassword() As String 
    Get 
      Return newPasswordValue 
    End Get 
    Set(ByVal value As String) 
      newPasswordValue = value 
    End Set 
  End Property 

  <Required()> _ 
  <DataType(DataType.Password)> _ 
  <DisplayName("Confirm new password")> _ 
  Public Property ConfirmPassword() As String 
    Get 
      Return confirmPasswordValue 
    End Get 
    Set(ByVal value As String) 
      confirmPasswordValue = value 
    End Set 
  End Property 
End Class 

Public Class LogOnModel 
  Private userNameValue As String 
  Private passwordValue As String 
  Private rememberMeValue As Boolean 

  <Required()> _ 
  <DisplayName("User name")> _ 
  Public Property UserName() As String 
    Get 
      Return userNameValue 
    End Get 
    Set(ByVal value As String) 
      userNameValue = value 
    End Set 
  End Property 

  <Required()> _ 
  <DataType(DataType.Password)> _ 
  <DisplayName("Password")> _ 
  Public Property Password() As String 
    Get 
      Return passwordValue 
    End Get 
    Set(ByVal value As String) 
      passwordValue = value 
    End Set 
  End Property 

  <DisplayName("Remember me?")> _ 
  Public Property RememberMe() As Boolean 
    Get 
      Return rememberMeValue 
    End Get 
    Set(ByVal value As Boolean) 
      rememberMeValue = value 
    End Set 
  End Property 
End Class 

<PropertiesMustMatch("Password", "ConfirmPassword", 
ErrorMessage:="The password and confirmation password do not match.")> _ 
Public Class RegisterModel 
  Private userNameValue As String 
  Private passwordValue As String 
  Private confirmPasswordValue As String 
  Private emailValue As String 

  <Required()> _ 
  <DisplayName("User name")> _ 
  Public Property UserName() As String 
    Get 
      Return userNameValue 
    End Get 
    Set(ByVal value As String) 
      userNameValue = value 
    End Set 
  End Property 

  <Required()> _ 
  <DataType(DataType.EmailAddress)> _ 
  <DisplayName("Email address")> _ 
  Public Property Email() As String 
    Get 
      Return emailValue 
    End Get 
    Set(ByVal value As String) 
      emailValue = value 
    End Set 
  End Property 

  <Required()> _ 
  <ValidatePasswordLength()> _ 
  <DataType(DataType.Password)> _ 
  <DisplayName("Password")> _ 
  Public Property Password() As String 
    Get 
      Return passwordValue 
    End Get 
    Set(ByVal value As String) 
      passwordValue = value 
    End Set 
  End Property 

  <Required()> _ 
  <DataType(DataType.Password)> _ 
  <DisplayName("Confirm password")> _ 
  Public Property ConfirmPassword() As String 
    Get 
      Return confirmPasswordValue 
    End Get 
    Set(ByVal value As String) 
      confirmPasswordValue = value 
    End Set 
  End Property 

  ' add Captcha code as a field in the protected action Model 
  Private captchaCodeValue As String 

  <Required(ErrorMessage:="Retyping the characters from the picture is required.")> _ 
  <DisplayName("Please retype the characters from the picture")> _ 
  Public Property CaptchaCode() As String 
    Get 
      Return captchaCodeValue 
    End Get 
    Set(ByVal value As String) 
      captchaCodeValue = value 
    End Set 
  End Property 
End Class 
#End Region 

#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 
  ReadOnly Property MinPasswordLength() As Integer 

  Function ValidateUser(ByVal userName As String, ByVal password As String) As 
  Boolean 
  Function CreateUser(ByVal userName As String, ByVal password As String, 
  ByVal email As String) As MembershipCreateStatus 
  Function ChangePassword(ByVal userName As String, ByVal oldPassword As String, 
  ByVal newPassword As String) As Boolean 
End Interface 

Public Class AccountMembershipService 
  Implements IMembershipService 

  Private ReadOnly _provider As MembershipProvider 

  Public Sub New() 
    Me.New(Nothing) 
  End Sub 

  Public Sub New(ByVal provider As MembershipProvider) 
    _provider = If(provider, Membership.Provider) 
  End Sub 

  Public ReadOnly Property MinPasswordLength() As Integer Implements 
  IMembershipService.MinPasswordLength 
    Get 
      Return _provider.MinRequiredPasswordLength 
    End Get 
  End Property 

  Public Function ValidateUser(ByVal userName As String, 
  ByVal password As String) As Boolean Implements IMembershipService.ValidateUser 
    If String.IsNullOrEmpty(userName) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "userName") 
    If String.IsNullOrEmpty(password) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "password") 

    Return _provider.ValidateUser(userName, password) 
  End Function 

  Public Function CreateUser(ByVal userName As String, ByVal password As String, 
  ByVal email As String) As MembershipCreateStatus Implements IMembershipService.CreateUser 
    If String.IsNullOrEmpty(userName) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "userName") 
    If String.IsNullOrEmpty(password) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "password") 
    If String.IsNullOrEmpty(email) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "email") 

    Dim status As MembershipCreateStatus 
    _provider.CreateUser(userName, password, email, Nothing, Nothing, True, 
    Nothing, status) 
    Return status 
  End Function 

  Public Function ChangePassword(ByVal userName As String, 
  ByVal oldPassword As String, ByVal newPassword As String) 
  As Boolean Implements IMembershipService.ChangePassword 
    If String.IsNullOrEmpty(userName) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "username") 
    If String.IsNullOrEmpty(oldPassword) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "oldPassword") 
    If String.IsNullOrEmpty(newPassword) Then 
    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 
      Dim currentUser As MembershipUser = _provider.GetUser(userName, True) 
      Return currentUser.ChangePassword(oldPassword, newPassword) 
    Catch ex As ArgumentException 
      Return False 
    Catch ex As MembershipPasswordException 
      Return False 
    End Try 
  End Function 
End Class 

Public Interface IFormsAuthenticationService 
  Sub SignIn(ByVal userName As String, ByVal createPersistentCookie As Boolean) 
  Sub SignOut() 
End Interface 

Public Class FormsAuthenticationService 
  Implements IFormsAuthenticationService 

  Public Sub SignIn(ByVal userName As String, ByVal createPersistentCookie As 
  Boolean) Implements IFormsAuthenticationService.SignIn 
    If String.IsNullOrEmpty(userName) Then 
    Throw New ArgumentException("Value cannot be null or empty.", "userName") 

    FormsAuthentication.SetAuthCookie(userName, createPersistentCookie) 
  End Sub 

  Public Sub SignOut() Implements IFormsAuthenticationService.SignOut 
    FormsAuthentication.SignOut() 
  End Sub 
End Class 
#End Region 

#Region "Validation" 
Public NotInheritable Class AccountValidation 
  Public Shared Function ErrorCodeToString(ByVal createStatus As MembershipCreateStatus) As String 
    ' See http://go.microsoft.com/fwlink/?LinkID=177550 for 
    ' a full list of status codes. 
    Select Case 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." 

      Case Else 
        Return "An unknown error occurred. Please verify your entry and try  
        again. If the problem persists, please contact your system administrator. 
        " 
    End Select 
  End Function 
End Class 

<AttributeUsage(AttributeTargets.Class, AllowMultiple:=True, Inherited:=False)> 
_ 
Public NotInheritable Class PropertiesMustMatchAttribute Inherits ValidationAttribute 

  Private Const _defaultErrorMessage As String = "'{0}' and '{1}' do not match." 
  Private ReadOnly _confirmProperty As String 
  Private ReadOnly _originalProperty As String 
  Private ReadOnly _typeId As New Object() 

  Public Sub New(ByVal originalProperty As String, ByVal confirmProperty As String) 
    MyBase.New(_defaultErrorMessage) 

    _originalProperty = originalProperty 
    _confirmProperty = confirmProperty 
  End Sub 

  Public ReadOnly Property ConfirmProperty() As String 
    Get 
      Return _confirmProperty 
    End Get 
  End Property 

  Public ReadOnly Property OriginalProperty() As String 
    Get 
      Return _originalProperty 
    End Get 
  End Property 

  Public Overrides ReadOnly Property TypeId() As Object 
    Get 
      Return _typeId 
    End Get 
  End Property 

  Public Overrides Function FormatErrorMessage(ByVal name As String) As String 
    Return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, 
    OriginalProperty, ConfirmProperty) 
  End Function 

  Public Overrides Function IsValid(ByVal value As Object) As Boolean 
    Dim properties As PropertyDescriptorCollection = TypeDescriptor.GetProperties(value) 
    Dim originalValue = properties.Find(OriginalProperty, True).GetValue(value) 
    Dim confirmValue = properties.Find(ConfirmProperty, True).GetValue(value) 
    Return Object.Equals(originalValue, confirmValue) 
  End Function 
End Class 

<AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, 
AllowMultiple:=False, Inherited:=True)> _ 
Public NotInheritable Class ValidatePasswordLengthAttribute 
  Inherits ValidationAttribute 

  Private Const _defaultErrorMessage As String = "'{0}' must be at least {1}  
  characters long." 
  Private ReadOnly _minCharacters As Integer = Membership.Provider. 
  MinRequiredPasswordLength 

  Public Sub New() 
    MyBase.New(_defaultErrorMessage) 
  End Sub 

  Public Overrides Function FormatErrorMessage(ByVal name As String) As String 
    Return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, name, 
    _minCharacters) 
  End Function 

  Public Overrides Function IsValid(ByVal value As Object) As Boolean 
    Dim valueAsString As String = TryCast(value, String) 
    Return (valueAsString IsNot Nothing) AndAlso (valueAsString.Length >= _minCharacters) 
  End Function 
End Class 
#End Region 

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="VB" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage(Of AspNetMvc20CaptchaExampleVBNet.RegisterModel)" %> 

<%@ Import Namespace="BotDetect.Web.Mvc" %> 
<%@ Import Namespace="AspNetMvc20CaptchaExampleVBNet" %> 
<asp:Content ID="registerTitle" ContentPlaceHolderID="TitleContent" 
runat="server"> 
  Register 
</asp:Content> 
<asp:Content ID="BotDetectStylesheets" ContentPlaceHolderID="includes" 
runat="server"> 
  <link href="<%= BotDetect.Web.CaptchaUrls.Absolute.LayoutStyleSheetUrl %>" 
  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("Register", "Account")%> 
  <%= 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(Function(m) m.UserName) %> 
      </div> 
      <div class="editor-field"> 
        <%= Html.TextBoxFor(Function(m) m.UserName) %> 
        <%= Html.ValidationMessageFor(Function(m) m.UserName) %> 
      </div> 
      <div class="editor-label"> 
        <%= Html.LabelFor(Function(m) m.Email) %> 
      </div> 
      <div class="editor-field"> 
        <%= Html.TextBoxFor(Function(m) m.Email) %> 
        <%= Html.ValidationMessageFor(Function(m) m.Email) %> 
      </div> 
      <div class="editor-label"> 
        <%= Html.LabelFor(Function(m) m.Password) %> 
      </div> 
      <div class="editor-field"> 
        <%= Html.PasswordFor(Function(m) m.Password) %> 
        <%= Html.ValidationMessageFor(Function(m) m.Password) %> 
      </div> 
      <div class="editor-label"> 
        <%= Html.LabelFor(Function(m) m.ConfirmPassword) %> 
      </div> 
      <div class="editor-field"> 
        <%= Html.PasswordFor(Function(m) m.ConfirmPassword) %> 
        <%= Html.ValidationMessageFor(Function(m) m.ConfirmPassword) %> 
      </div> 
      <%' add Captcha validation controls to the protected action View 
        Dim registrationCaptcha As MvcCaptcha 
        registrationCaptcha = CaptchaHelper.GetRegistrationCaptcha() 
        If (Not registrationCaptcha.IsSolved) Then%> 
      <div class="editor-label"> 
        <%= Html.Captcha(registrationCaptcha) %> 
        <%= Html.LabelFor(Function(m) m.CaptchaCode)%> 
      </div> 
      <div class="editor-field"> 
        <%= Html.TextBoxFor(Function(m) m.CaptchaCode)%> 
        <%= Html.ValidationMessageFor(Function(m) m.CaptchaCode)%> 
      </div> 
      <% End If%> 
      <p style="width: 195px; text-align: right;"> 
        <input type="submit" value="Register"/> 
      </p> 
    </fieldset> 
  </div> 
  <% End Using%> 
</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="VB" 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 Example</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.vb

Imports System.Diagnostics.CodeAnalysis 
Imports System.Security.Principal 
Imports System.Web.Routing 
Imports BotDetect.Web.Mvc 

<HandleError()> _ 
Public Class AccountController 
  Inherits System.Web.Mvc.Controller 

  Private formsServiceValue As IFormsAuthenticationService 
  Private membershipServiceValue As IMembershipService 

  Public Property FormsService() As IFormsAuthenticationService 
    Get 
      Return formsServiceValue 
    End Get 
    Set(ByVal value As IFormsAuthenticationService) 
      formsServiceValue = value 
    End Set 
  End Property 

  Public Property MembershipService() As IMembershipService 
    Get 
      Return membershipServiceValue 
    End Get 
    Set(ByVal value As IMembershipService) 
      membershipServiceValue = value 
    End Set 
  End Property 

  Protected Overrides Sub Initialize(ByVal requestContext As RequestContext) 
    If FormsService Is Nothing Then FormsService = New 
    FormsAuthenticationService() 
    If MembershipService Is Nothing Then MembershipService = New 
    AccountMembershipService() 

    MyBase.Initialize(requestContext) 
  End Sub 

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

  Public Function LogOn() As ActionResult 
    Return View() 
  End Function 

  <HttpPost()> _ 
  Public Function LogOn(ByVal model As LogOnModel, ByVal returnUrl As String) As 
  ActionResult 
    If ModelState.IsValid Then 
      If MembershipService.ValidateUser(model.UserName, model.Password) Then 
        FormsService.SignIn(model.UserName, model.RememberMe) 
        If Not String.IsNullOrEmpty(returnUrl) Then 
          Return Redirect(returnUrl) 
        Else 
          Return RedirectToAction("Index", "Home") 
        End If 
      Else 
        ModelState.AddModelError("", 
        "The user name or password provided is incorrect.") 
      End If 
    End If 

    ' If we got this far, something failed, redisplay form 
    Return View(model) 
  End Function 

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

  Public Function LogOff() As ActionResult 
    FormsService.SignOut() 

    Return RedirectToAction("Index", "Home") 
  End Function 

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

  Public Function Register() As ActionResult 
    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() 
  End Function 

  <HttpPost()> _ 
  <CaptchaValidationActionFilter("CaptchaCode", "RegistrationCaptcha", 
  "Your input doesn't match displayed characters.")> _ 
  Public Function Register(ByVal model As RegisterModel) As ActionResult 
    If ModelState.IsValid Then 
      ' Attempt to register the user 
      Dim createStatus As MembershipCreateStatus = MembershipService.CreateUser( 
      model.UserName, model.Password, model.Email) 

      If createStatus = MembershipCreateStatus.Success Then 
        FormsService.SignIn(model.UserName, False) 
        Return RedirectToAction("Index", "Home") 
      Else 
        ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus)) 
      End If 
    End If 

    ' If we got this far, something failed, redisplay form 
    ViewData("PasswordLength") = MembershipService.MinPasswordLength 
    Return View(model) 
  End Function 

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

  <Authorize()> _ 
  Public Function ChangePassword() As ActionResult 
    ViewData("PasswordLength") = MembershipService.MinPasswordLength 
    Return View() 
  End Function 

  <Authorize()> _ 
  <HttpPost()> _ 
  Public Function ChangePassword(ByVal model As ChangePasswordModel) As 
  ActionResult 
    If ModelState.IsValid Then 
      If MembershipService.ChangePassword(User.Identity.Name, model.OldPassword, 
      model.NewPassword) Then 
        Return RedirectToAction("ChangePasswordSuccess") 
      Else 
        ModelState.AddModelError("", 
        "The current password is incorrect or the new password is invalid.") 
      End If 
    End If 

    ' If we got this far, something failed, redisplay form 
    ViewData("PasswordLength") = MembershipService.MinPasswordLength 
    Return View(model) 
  End Function 

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

  Public Function ChangePasswordSuccess() As ActionResult 
    Return View() 
  End Function 
End Class 

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 <CaptchaValidationActionFilter> 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.vb

Imports BotDetect 
Imports BotDetect.Web 
Imports BotDetect.Web.UI 
Imports BotDetect.Web.Mvc 

Public Class CaptchaHelper 

  Public Shared Function GetRegistrationCaptcha() As MvcCaptcha 

    ' create the control instance 
    Dim registrationCaptcha As MvcCaptcha = _ 
        New MvcCaptcha("RegistrationCaptcha") 

    registrationCaptcha.UserInputID = "CaptchaCode" 

    ' all Captcha properties are set in this event handler 
    AddHandler WebFormsCaptcha.InitializedWebCaptcha, _ 
        AddressOf RegistrationCaptcha_InitializedWebCaptcha 

    Return registrationCaptcha 

  End Function 

  ' event handler used for Captcha control property randomization 
  Public Shared Sub RegistrationCaptcha_InitializedWebCaptcha( _ 
      ByVal sender As Object, _ 
      ByVal e As BotDetect.InitializedWebCaptchaEventArgs) 

    If (e.CaptchaId <> "RegistrationCaptcha") Then 
      Return 
    End If 

    Dim registrationCaptcha As Captcha = TryCast(sender, Captcha) 

    ' 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() 
  End Sub 

End Class 

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.vb

' Note: For instructions on enabling IIS6 or IIS7 classic mode,  
' visit http://go.microsoft.com/?LinkId=9394802 

Public Class MvcApplication 
  Inherits System.Web.HttpApplication 

  Shared Sub RegisterRoutes(ByVal routes As RouteCollection) 
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}") 

    ' BotDetect requests must not be routed 
    routes.IgnoreRoute("{*botdetect}", 
    New With {.botdetect = "(.*) BotDetectCaptcha\.ashx"}) 

    ' MapRoute takes the following parameters, in order: 
    ' (1) Route name 
    ' (2) URL with parameters 
    ' (3) Parameter defaults 
    routes.MapRoute( _ 
        "Default", _ 
        "{controller}/{action}/{id}", _ 
        New With {.controller = "Account", .action = "Register", . 
        id = UrlParameter.Optional} _ 
    ) 

  End Sub 

  Sub Application_Start() 
    AreaRegistration.RegisterAllAreas() 

    RegisterRoutes(RouteTable.Routes) 
  End Sub 
End Class 

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> 
    <!-- Register the BotDetect configuration section --> 
    <section name="botDetect" requirePermission="false" 
    type="BotDetect.Configuration.BotDetectConfigurationSection, BotDetect"/> 
  </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="false"> 
      <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=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, 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.Collections.Generic"/> 
        <!-- add BotDetect namespaces for coding convenience --> 
        <add namespace="BotDetect"/> 
        <add namespace="BotDetect.Web"/> 
        <add namespace="BotDetect.Web.UI"/> 
        <add namespace="BotDetect.Web.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=2.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=2.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> 
  <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> 
  <botDetect helpLinkEnabled="true" helpLinkMode="image" /> 
</configuration> 

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