BotDetect ASP.NET 1.1 CAPTCHA Randomization VB.NET Code Sample (BotDetect v2.0; deprecated)

Please Note

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

An up-to-date equivalent page for the latest BotDetect Captcha release (v3) is BotDetect v3 ASP.NET CAPTCHA Randomization code sample.

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

The BotDetect ASP.NET CAPTCHA randomization sample shows how you can easily randomize various Captcha control parameters, which can significantly improve the CAPTCHA protection security and is the best way to take full advantage of the 50 different CAPTCHA algorithms shipped with BotDetect.

Table of Contents

Sample Project Location

By default, this sample project is installed at
C:\Program Files\Lanapsoft\BotDetect\ASP.NET 1.1\v2.0\Samples\VBNetBotDetect2RandomDemo\.

You can also run it from the Start Menu:
Programs > Lanapsoft > BotDetect > ASP.NET 1.1 > v2.0 > Samples > VB.NET BotDetect CAPTCHA Randomization Sample.

Default.aspx

Full Source Code Listing

<%@ Page Language="vb" AutoEventWireup="false"
  Codebehind="Default.aspx.vb" Inherits="VBNetBotDetectDemo._Default" 
%>

<%@ Register Assembly="Lanap.BotDetect" Namespace="Lanap.BotDetect" 
  TagPrefix="BotDetect" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>BotDetect Demo</title>
    <link type='text/css' rel='Stylesheet' href="StyleSheet.css" />
</head>
<body>
    <form id="form1" runat="server">
    <fieldset id="Preview">
        <legend>
            <span id="PreviewLegend">CAPTCHA Preview</span>
        </legend>
        <div id="PromptDiv">
            <span id="Prompt">Type the characters you see in 
              the picture</span>
        </div>
        <div id="CaptchaDiv">
            <BotDetect:Captcha ID="SampleCaptcha" runat="server" />
        </div>
        <div id="ValidationDiv">
            <asp:TextBox ID="CodeTextBox" runat="server">
            </asp:TextBox>
            <asp:Button ID="ValidateButton" runat="server" />
            <asp:Label ID="MessageCorrectLabel" runat="server">
            </asp:Label>
            <asp:Label ID="MessageIncorrectLabel" runat="server">
            </asp:Label>
        </div>
    </fieldset>
    <div id="Note">
        <span>NOTE: the Trial version will use "LANAP" instead of a 
          random code in 50% of renderings.</span>
    </div>
    </form>
</body>
</html>

Explanation

Lines required to add the BotDetect CAPTCHA control to the ASP.NET form are bold. To use the <BotDetect:Captcha> control, we must first register the Lanap.BotDetect.dll assembly using the <%@Register %> directive.

The form also contains an <asp:TextBox> for the user input, an <asp:Button> to submit the page, and a pair of <asp:Label> controls which are used to show the CAPTCHA validation result. The rest of the file is either generated by Visual Studio 2005 by default, or just defines the layout and visual presentation of the page.

There is no difference between the .aspx file of this sample and the basic one, since all CAPTCHA randomization is performed in codebehind.

Default.aspx.vb

Full Source Code Listing

Public Class _Default
    Inherits System.Web.UI.Page

    #Region " Web Form Designer Generated Code "

    'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub _
        InitializeComponent()
    End Sub
    
    Protected WithEvents MessageCorrectLabel As _
        System.Web.UI.WebControls.Label
				
    Protected WithEvents MessageIncorrectLabel As _
        System.Web.UI.WebControls.Label
				
    Protected WithEvents SampleCaptcha As _
        Lanap.BotDetect.Captcha
				
    Protected WithEvents CodeTextBox As _
        System.Web.UI.WebControls.TextBox
				
    Protected WithEvents ValidateButton As _
        System.Web.UI.WebControls.Button

    'NOTE: The following placeholder declaration is required by 
    'the Web Form Designer. Do not delete or move it.
    Private designerPlaceholderDeclaration As System.Object

    Private Sub Page_Init(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Init
        'CODEGEN: This method call is required by the Web Form 
        'Designer. Do not modify it using the code editor.
        InitializeComponent()
    End Sub

    #End Region
		
    Protected Sub Page_Load(ByVal sender As System.Object, 
        ByVal e As System.EventArgs) Handles MyBase.Init
				
        'register CAPTCHA-specific event handler
        AddHandler SampleCaptcha.PreDrawCaptchaImage, 
           AddressOf SampleCaptcha_PreDrawCaptchaImage
    End Sub

    Protected Sub Page_PreRender(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.PreRender

        ' initial page setup
        If (Not IsPostBack) Then

            'set control text
            ValidateButton.Text = "Validate"
            MessageCorrectLabel.Text = "Correct!"
            MessageIncorrectLabel.Text = "Incorrect!"

            'these messages are shown only after validation
            MessageCorrectLabel.Visible = False
            MessageIncorrectLabel.Visible = False
        End If
				
        ' clear user input on Reload button clicks
        Dim scriptTemplate As String
        scriptTemplate = "<script type='text/javascript'>" & _
          "function LBD_ClearUserInput() {{" & _
          "  var LBD_textBox = document.getElementById('{0}');" & _
          "  if(LBD_textBox) {{" & _
          "    LBD_textBox.value = '';" & _
          "  }}" & _
          "}}" & _
          "LBD_RegisterHandler('PreReloadCaptchaImage', _
            LBD_ClearUserInput);</script>"

        Dim script As String
        script = String.Format(scriptTemplate, CodeTextBox.ClientID)
        If (Not Page.ClientScript.IsStartupScriptRegistered( _
            "CaptchaReloadClearInput")) Then
          Page.ClientScript.RegisterStartupScript(Me.GetType(), _ 
            "CaptchaReloadClearInput", script, True)
        End If
				
        ' automatically lowercase user input							
        CodeTextBox.Attributes.Add("onkeyup", _
            "this.value = this.value.toLowerCase();")

        If (IsPostBack) Then
            'validate the input code, and show the 
            'appropriate message 
            Dim code As String = CodeTextBox.Text.Trim().ToUpper()

            If (SampleCaptcha.Validate(code)) Then
                MessageCorrectLabel.Visible = True
                MessageIncorrectLabel.Visible = False
            Else
                MessageCorrectLabel.Visible = False
                MessageIncorrectLabel.Visible = True
            End If

            'clear previous user code input
            CodeTextBox.Text = ""
        End If

    End Sub

    ' all CAPTCHA randomization should be performed in this event 
    ' handler instead of Page_Load or Page_PreRender, because this 
    ' event is also fired for direct CAPTCHA image requests (which 
    ' skip the page events since the page is never loaded), for 
    ' example when clicking the Reload CAPTCHA button repeatedly
    Protected Sub SampleCaptcha_PreDrawCaptchaImage( _ 
        ByVal sender As System.Object, ByVal e As System.EventArgs)
        
        Dim captcha As ICaptcha = sender
        If (captcha.CaptchaId <> SampleCaptcha.CaptchaId) Then
          Return
        End If

        ' randomize code generation properties
        captcha.CodeType = RandomizationHelper.GetRandomCodeType()
        captcha.CodeLength = _ 
            RandomizationHelper.GetRandomCodeLength(4, 6)

        ' randomize text style
        Dim styles As TextStyleEnum() = { _
            TextStyleEnum.Lego, TextStyleEnum.MeltingHeat, _
            TextStyleEnum.Ghostly, TextStyleEnum.FingerPrints, _
            TextStyleEnum.Graffiti2, TextStyleEnum.Bullets2, _
            TextStyleEnum.CaughtInTheNet2, TextStyleEnum.Collage, _ 
            TextStyleEnum.Chalkboard
        }

        captcha.TextStyle = _ 
            RandomizationHelper.GetRandomTextStyle(styles)

    End Sub

End Class

Explanation

In the Page_Init phase of the ASP.NET page life-cycle, we register a special event handler to be executed before every CAPTCHA image is generated, in which we perform the CAPTCHA randomization. Since CAPTCHA images are generated and sent to the client in a Http request separate from the one loading the ASP.NET page (when this code gets executed), we add this event handler to ensure the CAPTCHA is randomized every time it is drawn, and not only once per page load.

This is important because the CAPTCHA image requests don't have to be tied to the number of page loads - most notably, when using the Reload CAPTCHA button, and when bots are accessing the CAPTCHA image directly, it's possible that many CAPTCHA images will be generated after only a single page load (and the related Page_PreRender execution).

In the Captcha_PreDrawCaptchaImage handler, the Captcha control instance is available as the sender parameter. To make the randomization as simple as possible, we use the RandomizationHelper class, which allows us to get a random value of a given parameter from all available values (as the CAPTCHA CodeType), or from a given range of values (as the CAPTCHA CodeLength), or from the given set of values (as the CAPTCHA TextStyle).

You can also randomize other CAPTCHA properties in a similar manner, but the CAPTCHA drawing algorithm and the CAPTCHA code length are the ones that improve the CAPTCHA security the most when randomized. Every individual CAPTCHA algorithm can be theoretically and eventually be broken (given enough effort), but if the bot also has to recognize the algorithm used for every image, the task becomes an order of magnitudes harder. Also, several popular CAPTCHA algorithms have been broken because they used a fixed number of characters in their images – "find 5 characters in this image" is inherently a much easier task to automate than "find an unknown number of characters in this image".

RandomizationHelper.vb

Full Source Code Listing

Imports Microsoft.VisualBasic
Imports System.Drawing
Imports Lanap.BotDetect

Public NotInheritable Class RandomizationHelper

    Private Sub New()
        'constructor omitted, static methods only
    End Sub

    ' a single global generator is used for all random numbers
    Private Shared ReadOnly _rand As Random = New Random()

    Public Const DefaultCodeType As CodeTypeEnum = _
        CodeTypeEnum.AlphaNumeric

    Public Shared Function GetRandomCodeType( _
        ByVal ParamArray usedValues As CodeTypeEnum()) As CodeTypeEnum

        Dim codeType As CodeTypeEnum = DefaultCodeType

        If (0 = usedValues.Length) Then
            Dim max As Integer = 
                System.Enum.GetValues(GetType(CodeTypeEnum)).Length
            codeType = CType(_rand.Next(max), CodeTypeEnum)
        ElseIf (1 = usedValues.Length) Then
            codeType = usedValues(0)
        Else
            Dim max As Integer = usedValues.Length
            Dim index As Integer = _rand.Next(max)
            codeType = usedValues(index)
        End If

        Return codeType

    End Function

    Public Const DefaultCodeLength As Integer = 5
    Public Const MinCodeLength As Integer = 1
    Public Const MaxCodeLength As Integer = 15

    Public Shared Function GetRandomCodeLength( _
        Optional ByVal min As Integer = 0, Optional ByVal max As _
            Integer = 0) Integer

        If ((max > MaxCodeLength) OrElse (max < MinCodeLength)) Then
            max = MaxCodeLength
        End If

        If ((min < MinCodeLength) OrElse (min > max)) Then
            min = MinCodeLength
        End If

        Return _rand.Next(min, max + 1)

    End Function

    Public Shared ReadOnly DefaultImageFormat As ImageFormatEnum = _
        ImageFormatEnum.Jpeg

    Public Shared Function GetRandomImageFormat( _
        ByVal ParamArray usedValues As ImageFormatEnum()) As _ 
            ImageFormatEnum

        Dim imageFormat As ImageFormatEnum = DefaultImageFormat

        If (0 = usedValues.Length) Then
            Dim max As Integer = System.Enum.GetValues( _
                GetType(ImageFormatEnum)).Length
            imageFormat = CType(_rand.Next(max), ImageFormatEnum)
        ElseIf (1 = usedValues.Length) Then
            imageFormat = usedValues(0)
        Else
            Dim max As Integer = usedValues.Length
            Dim index As Integer = _rand.Next(max)
            imageFormat = usedValues(index)
        End If

        Return imageFormat

    End Function

    Public Shared ReadOnly DefaultImageSize As Size = New Size(250, 50)
    Public Shared ReadOnly MinImageSize As Size = New Size(50, 40)
    Public Shared ReadOnly MaxImageSize As Size = New Size(500, 200)

    Public Shared Function GetRandomImageSize() As Size
        Return GetRandomImageSize(New Size(0, 0), New Size(0, 0))
    End Function

    Public Shared Function GetRandomImageSize(ByVal maxSize As Size) _
        As Size
        
        Return GetRandomImageSize(New Size(0, 0), maxSize)
    End Function

    Public Shared Function GetRandomImageSize(ByVal minSize As Size, _
        ByVal maxSize As Size) As Size
				
        'determine width
        If ((maxSize.Width > MaxImageSize.Width) OrElse _
            (maxSize.Width < MinImageSize.Width)) Then
            maxSize.Width = MaxImageSize.Width
        End If

        If ((minSize.Width < MinImageSize.Width) OrElse _
            (minSize.Width > maxSize.Width)) Then
            minSize.Width = MinImageSize.Width
        End If
				
        Dim width As Integer = _
            _rand.Next(minSize.Width, maxSize.Width + 1)

        'determine height
        If ((maxSize.Height > MaxImageSize.Height) OrElse _
           (maxSize.Height < MinImageSize.Height)) Then
            maxSize.Height = MaxImageSize.Height
        End If

        If ((minSize.Height < MinImageSize.Height) OrElse _
            (minSize.Height > maxSize.Height)) Then
            minSize.Height = MinImageSize.Height
        End If
				
        Dim height As Integer = _
            _rand.Next(minSize.Height, maxSize.Height + 1)

        'the result
        Return New Size(width, height)
    End Function

    Public Const DefaultTextStyle As TextStyleEnum = _
        TextStyleEnum.Chalkboard

    Public Shared Function GetRandomTextStyle( _
        ByVal ParamArray usedValues As TextStyleEnum()) As _
        TextStyleEnum

        Dim textStyle As TextStyleEnum = DefaultTextStyle

        If (0 = usedValues.Length) Then
            Dim max As Integer = _ 
                System.Enum.GetValues(GetType(TextStyleEnum)).Length
            textStyle = CType(_rand.Next(max), TextStyleEnum)
        ElseIf (1 = usedValues.Length) Then
            textStyle = usedValues(0)
        Else
            Dim max As Integer = usedValues.Length
            Dim index As Integer = _rand.Next(max)
            textStyle = usedValues(index)
        End If

        Return textStyle

    End Function

End Class

Explanation

RandomizationHelper is a small utility class which you can use in your projects to simplify various CAPTCHA parameter randomization.

For each CAPTCHA parameter which can take a numeric or enumerated value, this class provides a static method returning a random value. Each parameter has a default value, and numeric parameters also have predefined minimum and maximum values.

All randomization methods take a variable number of parameters, either via multiple method overloads or via the params keyword.

Methods returning numeric values (for example, CodeLength) behave in the following manner:

  • If no parameters are given, the random value is selected between the predefined minimum and maximum values.
  • If a single value is given as a parameter, the random value is selected between the predefined minimum and the given value as the maximum. Note that it is not possible to use a maximum greater than the predefined one, only a lesser one.
  • If two values are given as parameters, the random value is selected between the first value as the minimum and the second value as the maximum. Note that it is not possible to use a maximum greater than the predefined one, or a minimum lesser than the predefined one.

Methods returning enumerated values (for example, CodeType) behave in the following manner:

  • If no parameters are given, the random value is selected from all possible enumeration values.
  • If a single value is given as the parameter, it is returned as the result - be careful not to pass a single value to these methods, since there will be no randomization. And if you want to use a single value, just assign it to the appropriate parameter directly.
  • If a set (i.e. an array) of values is given as the parameter, the random value is selected from within that set.

Of course, you could also use such randomization code directly in your projects, but encapsulating it in the RandomizationHelper class is made available for convenience and better code readability.

Web.config

Full Source Code Listing

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
  <system.web>
  
  <httpHandlers>
    <add verb="*" path="LanapCaptcha.aspx" 
      type="Lanap.BotDetect.CaptchaHandler, Lanap.BotDetect"/>
  </httpHandlers>

  <!-- DYNAMIC DEBUG COMPILATION
    Set compilation debug="true" to enable ASPX debugging. Otherwise, 
    setting this value to false will improve runtime performance of 
    this application. Set compilation debug="true" to insert debugging 
    symbols (.pdb information) into the compiled page. Because this 
    creates a larger file that executes more slowly, you should set 
    this value to true only when debugging and to false at all other 
    times. For more information, refer to the documentation about 
    debugging ASP.NET files.
  -->
  <compilation 
    defaultLanguage="vb"
    debug="false"
  />

  <!-- CUSTOM ERROR MESSAGES
    Set customErrors mode="On" or "RemoteOnly" to enable custom error 
    messages, "Off" to disable. 
		
    Add <error> tags for each of the errors you want to handle.

    "On" Always display custom (friendly) messages.
		
    "Off" Always display detailed ASP.NET error information.
		
    "RemoteOnly" Display custom (friendly) messages only to users not 
      running on the local Web server. This setting is recommended for 
      security purposes, so that you do not display application detail 
      information to remote clients.
  -->
  <customErrors 
    mode="RemoteOnly" 
  /> 

  <!-- AUTHENTICATION 
    This section sets the authentication policies of the application. 
    Possible modes are "Windows", "Forms", "Passport" and "None".

    "None" No authentication is performed. 
		
    "Windows" IIS performs authentication (Basic, Digest, or 
    Integrated Windows) according to its settings for the 
    application. Anonymous access must be disabled in IIS. 
		
    "Forms" You provide a custom form (Web page) for users to 
    enter their credentials, and then you authenticate them 
    in your application. A user credential token is stored 
    in a cookie.
		
    "Passport" Authentication is performed via a centralized 
    authentication service provided by Microsoft that offers 
    a single logon and core profile services for member sites.
  -->
  <authentication mode="Windows" /> 

  <!-- AUTHORIZATION 
    This section sets the authorization policies of the 
    application. You can allow or deny access to application 
    resources by user or role. Wildcards: "*" mean everyone, 
    "?" means anonymous (unauthenticated) users.
  -->

  <authorization>
    <allow users="*" /> <!-- Allow all users -->
    <!-- 
    <allow users="[comma separated list of users]"
      roles="[comma separated list of roles]"/>
    <deny users="[comma separated list of users]"
      roles="[comma separated list of roles]"/>
    -->
  </authorization>

  <!-- APPLICATION-LEVEL TRACE LOGGING
    Application-level tracing enables trace log output for 
    every page within an application. 
    Set trace enabled="true" to enable application trace 
    logging. If pageOutput="true", the trace information 
    will be displayed at the bottom of each page. Otherwise, 
    you can view the application trace log by browsing the 
    "trace.axd" page from your web application root. 
  -->
  <trace
    enabled="false"
    requestLimit="10"
    pageOutput="false"
    traceMode="SortByTime"
    localOnly="true"
  />

  <!-- SESSION STATE SETTINGS
    By default ASP.NET uses cookies to identify which requests 
    belong to a particular session. If cookies are not available, 
    a session can be tracked by adding a session identifier to the 
    URL. To disable cookies, set sessionState cookieless="true".
  -->
  <sessionState 
    mode="InProc"
    stateConnectionString="tcpip=127.0.0.1:42424"
    sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"
    cookieless="false" 
    timeout="20" 
  />

  <!-- GLOBALIZATION
    This section sets the globalization settings of the application. 
  -->
  <globalization 
    requestEncoding="utf-8" 
    responseEncoding="utf-8" 
  />
   
 </system.web>

</configuration>

Explanation

Lines necessary for BotDetect CAPTCHA to function properly have been bold, other lines are all standard values generated by Visual Studio 2003 by default. There are no special settings related to CAPTCHA randomization in the web.config file.

The <httpHandlers> element registers the path used for CAPTCHA image and sound requests for processing by Lanap.BotDetect.dll code, while the <sessionState> element declares the persistence mechanism used by BotDetect to keep the CAPTCHA codes and settings for each user.

You can use different Session State settings and modes depending on your application's needs, but you will have to ensure Session State persistence is available for BotDetect to work. Special care should be taken if you are running multiple load-balanced servers, as explained in this FAQ item.


Please Note

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

An up-to-date equivalent page for the latest BotDetect Captcha release (v3) is BotDetect v3 ASP.NET CAPTCHA Randomization code sample.

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