ASP.NET MVC 3.0 Application Template CAPTCHA VB.NET Code Sample (BotDetect v3.0; deprecated)

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

First Time Here?

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

Starting with the default ASP.NET MVC 3.0 Visual Studio 2010 project template, 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.

→ ASP.NET MVC version:

→ .NET programming language:

  • C#
  • VB.NET

Installed Location

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

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

Imports System.ComponentModel
Imports System.ComponentModel.DataAnnotations
Imports System.Globalization

Public Class ChangePasswordModel
    Private oldPasswordValue As String
    Private newPasswordValue As String
    Private confirmPasswordValue As String

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

    <Required()> _
    <StringLength(100, ErrorMessage:="The {0} must be at least {2} 
    characters long.", MinimumLength:=6)> _
    <DataType(DataType.Password)> _
    <Display(Name:="New password")> _
    Public Property NewPassword() As String
        Get
            Return newPasswordValue
        End Get
        Set(ByVal value As String)
            newPasswordValue = value
        End Set
    End Property

    <DataType(DataType.Password)> _
    <Display(Name:="Confirm new password")> _
    <Compare("NewPassword", ErrorMessage:="The new password and 
    confirmation password do not match.")> _
    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()> _
    <Display(Name:="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)> _
    <Display(Name:="Password")> _
    Public Property Password() As String
        Get
            Return passwordValue
        End Get
        Set(ByVal value As String)
            passwordValue = value
        End Set
    End Property

    <Display(Name:="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

Public Class RegisterModel
    Private userNameValue As String
    Private passwordValue As String
    Private confirmPasswordValue As String
    Private emailValue As String

    <Required()> _
    <Display(Name:="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)> _
    <Display(Name:="Email address")> _
    Public Property Email() As String
        Get
            Return emailValue
        End Get
        Set(ByVal value As String)
            emailValue = value
        End Set
    End Property

    <Required()> _
    <StringLength(100, ErrorMessage:="The {0} must be at least {2} 
    characters long.", MinimumLength:=6)> _
    <DataType(DataType.Password)> _
    <Display(Name:="Password")> _
    Public Property Password() As String
        Get
            Return passwordValue
        End Get
        Set(ByVal value As String)
            passwordValue = value
        End Set
    End Property

    <DataType(DataType.Password)> _
    <Display(Name:="Confirm password")> _
    <Compare("Password", ErrorMessage:="The password and confirmation 
    password do not match.")> _
    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

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

@ModelType AspNetMvc30CaptchaSampleVBNet.RegisterModel

@Imports BotDetect.Web.UI.Mvc
@Imports AspNetMvc30CaptchaSampleVBNet

@Code
    ViewData("Title") = "Register"
End Code

@* add BotDetect header includes *@
@Section BotDetectStyles 
    <link href="@BotDetect.Web.CaptchaUrls.Absolute.LayoutStyleSheetUrl" 
      rel="stylesheet" type="text/css" />
End Section

<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()
    @Html.ValidationSummary(True, "Account creation was unsuccessful. 
    Please correct the errors and try again.")
    @<div>
        <fieldset>
            <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 *@
            @Code Dim registrationCaptcha As MvcCaptcha
              registrationCaptcha = _ 
                CaptchaHelper.GetRegistrationCaptcha()
            End Code
            @If (Not registrationCaptcha.IsSolved) Then
            @:<div class="editor-label">
                @Html.LabelFor(Function(m) m.CaptchaCode)
                @Html.Captcha(registrationCaptcha)
            @:</div>
            @:<div class="editor-field">
                @Html.TextBoxFor(Function(m) m.CaptchaCode)
                @Html.ValidationMessageFor(Function(m) m.CaptchaCode)
            @:</div>
            End If 

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

Following ASP.NET MVC 3.0 Captcha View instructions, 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.vbhtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewData("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.vb

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

Public Class AccountController
    Inherits System.Web.Mvc.Controller

    '
    ' GET: /Account/LogOn

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

    '
    ' POST: /Account/LogOn

    <HttpPost()> _
    Public Function LogOn(ByVal model As LogOnModel, ByVal returnUrl 
    As String) As ActionResult
        If ModelState.IsValid Then
            If Membership.ValidateUser(model.UserName, model.Password) 
            Then
                FormsAuthentication.SetAuthCookie(model.UserName, 
                model.RememberMe)
                If Url.IsLocalUrl(returnUrl) AndAlso returnUrl.Length >
                1 AndAlso returnUrl.StartsWith("/") _
                   AndAlso Not returnUrl.StartsWith("//") AndAlso Not 
                   returnUrl.StartsWith("/\\") 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

    '
    ' GET: /Account/LogOff

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

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

    '
    ' GET: /Account/Register
    Public Function Register() As ActionResult

        ' 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 
        ' accountswithin a single Session after solving the 
        ' CAPTCHA only once.
        MvcCaptcha.ResetCaptcha("RegistrationCaptcha")

        Return View()
    End Function

    '
    ' POST: /Account/Register

    <HttpPost()> _
    <CaptchaValidation("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
            Membership.CreateUser(model.UserName, model.Password, 
            model.Email, Nothing, Nothing, True, Nothing, createStatus)

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

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

    '
    ' GET: /Account/ChangePassword

    <Authorize()> _
    Public Function ChangePassword() As ActionResult
        Return View()
    End Function

    '
    ' POST: /Account/ChangePassword

    <Authorize()> _
    <HttpPost()> _
    Public Function ChangePassword(ByVal model As ChangePasswordModel) 
    As ActionResult
        If ModelState.IsValid Then
            ' ChangePassword will throw an exception rather
            ' than return false in certain failure scenarios.
            Dim changePasswordSucceeded As Boolean

            Try
                Dim currentUser As MembershipUser = Membership.GetUser(
                User.Identity.Name, True)
                changePasswordSucceeded = currentUser.ChangePassword(
                model.OldPassword, model.NewPassword)
            Catch ex As Exception
                changePasswordSucceeded = False
            End Try

            If changePasswordSucceeded 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
        Return View(model)
    End Function

    '
    ' GET: /Account/ChangePasswordSuccess

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

#Region "Status Code"
    Public 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 "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."

            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 Region

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

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

Public Class CaptchaHelper

    Public Shared Function GetRegistrationCaptcha() As MvcCaptcha

        ' create the control instance
        Dim registrationCaptcha As BotDetect.Web.UI.Mvc.MvcCaptcha = _
            New BotDetect.Web.UI.Mvc.MvcCaptcha("RegistrationCaptcha")

        registrationCaptcha.UserInputClientID = "CaptchaCode"

        ' all Captcha properties are set in this event handler
        AddHandler registrationCaptcha.InitializedCaptchaControl, _
            AddressOf RegistrationCaptcha_InitializedCaptchaControl

        Return registrationCaptcha

    End Function

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

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

        Dim registrationCaptcha As CaptchaControl = TryCast(sender, 
        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()
    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 RegisterGlobalFilters(ByVal filters As 
    GlobalFilterCollection)
        filters.Add(New HandleErrorAttribute())
    End Sub

    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()

        RegisterGlobalFilters(GlobalFilters.Filters)
        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"?>
<!--
  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.


Please Note

The information on this page is out of date and applies to a deprecated version of BotDetect™ CAPTCHA (v3.0).

An up-to-date equivalent page for the latest BotDetect Captcha release (v4) is BotDetect v4 Captcha documentation index.

General information about the major improvements in the current BotDetect release can be found at the What's New in BotDetect v4.0 page.