Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Client/Server-side ASP.NET Credit Card Validation Control

0.00/5 (No votes)
28 Feb 2004 2  
Article discussing new version of ASP.NET Credit Card Validator, including both server and client-side checks and VS.NET designer support.

Screenshot

Background

This article follows on from a previous version of the Credit Card Validation control. It's been a long time since the original was posted, and I've promised this updated version of the article for some time, it feels good to finally get it off my chest.

The control was originally borne out of a different project I was working on - building a native .NET interface to DataCash's payment gateway service - I built a test page and decided it would also be nice to have some simple credit card number validation.

I produced a simple ASP.NET validation control that used Luhn's formula - an algorithm that produces a checksum of all the number's digits. I'll cover the basics of how Luhn's formula can be used later but more detailed information is available from Webopedia.

This was then extended to include some card handling code to enable developers to control which cards should be accepted, for example, some shops may not take American Express because of the additional processing fees, or alternatively they may only accept debit card payments. In the original few versions of this control, the card types were handled through a property that was set at design time in the validator's ASP.NET tag.

I wrote the original article around August 2002. Since then I've had a large number of emails from people suggesting improvements, asking whether something was possible or how they might go about implementing their code around mine.

Before graduating, I still hadn't organized a job and so started looking towards how I might not only keep myself occupied but also how I might start to earn some money. I worked on improving the code, adding features based on what had been suggested etc. There were a number of other commercial solutions available but felt this updated version I had planned and designed would be significantly better, providing more features and require less effort on the part of developers to integrate into ASP.NET applications.

I have now started a company and will be selling .NET components but I've already given a few people that emailed me the updated code, as well as promising this update, so it's not right that I release this as a commercial product. I may well add more features to make it more comprehensive with a view to selling it, in which case people who feel a great sense of appreciation for this free version can buy it ;) However, I do promise not to revoke this or any other articles I've contributed here.

A demonstration on the control will be available shortly, until then the demo can be downloaded and run.

Solution Objectives

The broad goal for the new solution were to provide a more comprehensive product. As I mentioned, I was originally intending on working on this updated code to sell. I had seen a number of other products emerge providing credit card validation but they seemed to provide only very basic validation and required a fair amount of effort on the part of the developer to incorporate the checks into a page. I wanted my product to be a drop-in solution. However, I eventually decided instead to get the majority of the functionality in and working and release the code (and finally this article).

I felt the following were good features I should add to the existing code:

  1. Provide both Client-side and Server-side validation

    This enables more validation to be done before the server round-trip. For users on slow connections this is even more important.

  2. Visual Studio .NET Designer Support

    Although I tend to write pages using a simple HTML editor, providing integration with Visual Studio .NET would make it much quicker for developers using its WYSIWYG environment.

  3. Enable users to choose the card type

    Although the control checks whether card types are valid/invalid, enabling users to select the type from a drop-down would enable an additional level of checking to make sure the number was entered correctly.

  4. Configuration outside of the ASP.NET page

    This was primarily an aim to remove the card types from the ASP.NET tag's properties, allowing any web forms within an application to use the same settings. Should an additional card type become accepted the change should only have to be made in a single place.

  5. Extensibility

    Although there are only a few big card processors (VISA, MasterCard, American Express) within countries there are others, and people had emailed me to ask whether any individual card could be added. I wanted a way that people could update the validation themselves, adding support for a new card without having to re-compile the source code. Plus, going commercial, I could provide any updates to existing customers without requiring them to rebuild the code or install a new DLL.

Solution Design

I'll first go through design implications for the objectives discussed above and then move into class design etc. later.

Client-Side Validation

Fortunately, a guy named Chip Johansen emailed me to say that they had added client-side script support for the card types I used in my previous article. I looked through the code and found it extremely helpful in seeing how checks could be implemented. However, it had the JavaScript code hardcoded for the card-types and would prevent me from providing an extensible solution. I should add that later I also received an email from Peter Lange mentioning he had also added client-side support in a similar way. Both sets of code were helpful in determining how to add support.

I dug around through various JavaScript articles, in particular seeing if there was a way I could enable the same checking code used on the server to be used on the client. I then found that JavaScript includes regular expression support. After writing some basic JavaScript code, I found that I could use the same regular expression matching code on the server and client sides. The only change from the existing code was that more of the card type checking code would be written through regular expressions.

Visual Studio .NET Designer Support

There's not much to cover, adding support is essentially an implementation issue. As it turns out, this took me quite a while to get working, uncovering numerous Visual Studio .NET glitches across the way.

User Selectable Card Type Support

There were a few posts on CodeProject and emails I received from people asking if there was a way to change the validator control's properties at runtime to enable the accepted card types to be modified, allowing the user to select the card type they'd entered from a drop-down.

The obvious solution was to add an additional control to the solution, derived from ListBox. This would self-populate with the list of card types the form would accept. When the user submitted their details, the validation control would inspect this to identify the card type that should have been entered and check it. Additionally, this should work on the client-side also.

Configuration outside of the ASP.NET tag

Again, the logical thing to do was to add support for configuring the control in the application's configuration file (web.config). Effectively, moving the accepted card types property from the ASP.NET control's tag to a separate XML configuration file.

Extensibility

Fortunately, using a system like the one mentioned above lends itself well to extensibility. Creating a new section would allow the XML configuration file to contain all of the accepted types and a regular expression to validate the number's layout. I'd received an email from someone recommending I move the card types into a modifiable collection, defining a new CardType type allowing developers to add custom types. This was the type of architecture I went with, but wanted to move the code for adding types to the XML configuration file.

Solution Architecture

Broadly speaking, there are two user interface controls to be built and can be implemented in two classes:

  1. CreditCardValidator

    This will be responsible for performing the checks against the card number entered, and producing an error message should it be incorrect.

  2. CardTypesListBox

    This will render a drop-down listbox control to the user, enabling them to select the card type they've entered. This is an optional component, validation can be performed without having it on a form.

These will depend on a few other classes that will be necessary to provide the supporting infrastructure, that is enable them to be used as discussed in the design previously. They are:

CreditCardValidator Infrastructure Classes

  1. CreditCardValidatorDesigner

    This is needed to provide Visual Studio .NET design time support. All validation controls have a designer derived from the .NET Framework's BaseValidatorDesigner class.

  2. CreditCardValidatorSectionHandler

    This class is used to enable the validator to be configured through an ASP.NET application's XML configuration file (web.config). As discussed previously, the aim is to enable a collection of card types to be generated based on the contents of the XML.

CardTypesListBox Infrastructure Classes

  1. CardTypesListBoxDesigner

    This enables design-time HTML to be generated for the control when placed on an ASP.NET Web Form.

  2. CardTypesListBoxConverter

    This was identified as a result of building the design-time support. It's necessary for the CreditCardValidator control to know the ID of the list box that displays the valid card types so that it can validate as necessary. Rather than just providing a string property that the developer must enter the name of CardTypeListBox control into, this generates a list of IDs for controls on the form derived from CardTypesListBox.

Shared Infrastructure Classes

  1. CardType

    Represents an individual card type such as VISA. Contains the regular expression to check the type on both the client and server sides, as well as a display name.

  2. CardTypeCollection

    A specialized CollectionBase class, storing a number of CardType instances. Each card type will be entered into the application's XML configuration file, and a collection of this type containing any number of CardType instances will be created by the CreditCardValidatorSectionHandler mentioned above. This will then be accessed by the CreditCardValidator when generating the client-side code, as well as when checking on the server-side.

Below is an abbreviated class diagram for the solution, showing the key classes within the component. The majority of the infrastructure classes are excluded for display reasons but will be discussed later.

Credit Card Validator Implementation

Broadly, the implementation of the Credit Card Validator control has not changed greatly from the previous version so I won't discuss its implementation in every last detail. The source code contains enough comments and it's not the most complicated code to read. As always, if anyone does have questions, feel free to email me and I'll try and help.

All web form validators in ASP.NET are derived from BaseValidator. This provides a simple but powerful model for building web form validation - something that can be extremely tedious to code otherwise. To anyone who hasn't looked at using it yet, the idea behind it is that validator control tags are placed onto a page and assigned a control to validate. During a postback, the developer can call the Page type's IsValid method to check whether the validators on the page all voted that the entered data is correct. Provided IsValid returns true it's ok for the page to then process the form's data.

The key method to override from the BaseValidator class is EvaluateIsValid, this is the method that is called by a page to determine whether the data the validator is given the responsibility of monitoring is correct. It is this method we want to implement our two stage check in. Firstly, whether the number passes the check using Luhn's formula - checking that the various digits of the number are correct, and secondly, whether the number entered is an accepted card type.

The other major part of the implementation is providing JavaScript code to perform the same check that can be done on the server on the client. Client-side script can be output through the Page type's RegisterClientScriptBlock method and the CreditCardValidator control will generate this code. As discussed above, this helps to reduce roundtrips to the server which are extremely beneficial to visitors using slow connections (even more so when the page is using ViewState). However, because user's may have JavaScript turned off or be using a browser that doesn't implement JavaScript, it's extremely important that server-side checks are also done. The fortunate thing about validation controls is that you can centralize the validation in one place for both, providing perfect encapsulation.

Time now to look at how the various parts of the validator work.

EvaluateIsValid Implementation

The first part of the implementation looks to see whether a CardTypesListBox has been set through the CreditCardValidator class' CardTypesListBox property. If so, the FindCardTypesListBox method obtains a reference to the object. In case users have entered the card number with spaces or other separator characters, the string is processed through a regular expression to remove everything except digits.

If the developer has used a CardTypesListBox, it loads the regular expression from the selected item. I'll discuss the implementation of the list box control later, but it works by providing a list of card types with the value set to the regular expression that can be used to test the number. All the EvaluateIsValid method needs to do is retrieve the regular expression from the drop-down.

If the developer isn't using a CardTypesListBox, it checks the card type against the collection of CardType instances that were setup through the XML configuration file through the ValidCardType method call.

Assuming the number passes the valid card type check, the number is processed using Luhn's formula by calling the type's LuhnValid method. Provided this check passes, the number is valid and the validator control returns as so. Below is the code for the method (with comments removed):

protected override bool EvaluateIsValid()
{
    CardTypesListBox tl = FindCardTypesListBox ();

    string cardNumber = Regex.Replace(
        _creditCardNumberTextBox.Text, @"[^0-9]", ""
        );

    if (tl != null)
    {
        string regexp = tl.SelectedItem.Value;
        if ( Regex.IsMatch( cardNumber, regexp ) )
            return LuhnValid ( cardNumber );
        else
            return false;
    }
    else
    {
        if (_cardTypes != null)
        {
            if (ValidCardType (cardNumber))
                return LuhnValid (cardNumber);
            else
                return false;
        }
        else
            return LuhnValid (cardNumber);
    }
}

It's now probably best to take a look at the implementation of Luhn's formula to check the card type. I'll then discuss the card type regular expression checks and then move into talking about the CardTypesListBox control.

Luhn's Formula Implementation

First it's not a bad idea to take a look at the basic card number validation algorithm, I'll then discuss the C# code that actually implements this check.

Luhn's Formula

The formula is well documented around the web, so this is intended just as a reminder or overview of the algorithm. Broadly there are 4 steps:

  1. Double the value of alternating digits

    The first stage is to double every other digit, starting from the penultimate number. For example, if we take the number 1234-5678-1234-5670, we would do as follows:

    7 * 2 = 14
    5 * 2 = 10
    3 * 2 = 6

    And so on until the end.

  2. Add the separate digits of the products from the previous stage
    (1 + 4) + (1 + 0) + (6) + ...

    For the number mentioned in stage 1, this gives us a total of 28.

  3. Add the unaffected digits

    This time we add the digits of the numbers we didn't double during step 1. Again, starting from the right it would look as follows:

    0 + 6 + 4 + 2 + 8 + 6 + 4 + 2 = 32
  4. Add the two results and divide by 10

    Provided the two results when added together and subsequently divided by 10 have no remainder, it is a valid number.

    (Result from Stage 2 + Result from Stage 3) mod 10 = 0 = valid 
    28 + 32 = 60 mod 10 = 0 

C# Implementation

The original validator control contained a rather verbose implementation that wasn't really optimized. I was given the following implementation by Frode N. Rosland:

private static bool ValidateCardNumber(string cardNumber)
{
    int length = cardNumber.Length;
            
    if (length < 13)
        return false;
    int sum = 0;
    int offset = length % 2;
    byte[] digits = new System.Text.ASCIIEncoding().GetBytes(cardNumber);
            
    for (int i = 0; i < length; i++)
    {
        digits[i] -= 48;
        if (((i + offset) % 2) == 0)
            digits[i] *= 2;
        
        sum += (digits[i] > 9) ? digits[i] - 9 : digits[i];
    }
    return ((sum % 10) == 0);
}

It works by first converting each digit from a string into a byte, reducing the number by 48 so that each digit can be processed as a number. Processing as a byte avoids all of the casting overhead previously, as well as optimizes the final sum addition.

Once it has an array of digits to process, it loops through the length. Provided it is an even digit then it doubles the value and is then added to the sum. Instead of performing stage 2, then stage 3 and finally adding the two values, the two stages both add to the same sum.

The sum addition works by determining whether the number was doubled. If it was and is greater than 9, we can calculate the value to add by removing 9 from the byte's value. For example, assume the original digit in the card number was 7 and was then doubled to make 14. 14 represented in hexadecimal is E, if we substitute 9 from 14 (E - 9), we're left with 5. If we were to calculate it by adding the two digits together (1 and 4), we would also get 5. If the number is not greater than 9 there's no need to do any additional calculations, just add the number to the sum.

The final check is done by returning the boolean comparison between the resulting sum modulo 10 and 0 - whether the sum divides by 10 leaving no remainder.

Regular Expression Card Type Checks

During the CreditCardValidator's OnInit event, the card types are loaded from the application's XML configuration file, producing a collection of CardType instances for each card type. These are stored by the CardTypeCollection class (a simple strongly typed collection derived from CollectionBase).

The CardType type has two properties: Name and RegExp. Name is used to provide a human-readable name for the card type, such as MasterCard or VISA. The RegExp property holds the regular expression (in string form) to check the card type is in the correct format.

Regular Expressions

For anyone who isn't familiar with regular expressions they provide a way to process text, such as perform pattern matching, extract or replace data, and many other things. A Google search on regular expressions should give a fairly large amount of material to look through. However, a quick overview with how they relate to card type checking now is no bad thing.

It's possible to produce a pattern matching regular expression to check the validity of a card number against a card type. For instance, the following regular expression checks for the valid format of a VISA credit card number:

^[4] ( [0-9]{15}$ | [0-9]{12}$ )

The first statement (in bold) checks that the number begins with the digit 4. All VISA card numbers start with 4. The next part of the regular expression is grouped by parentheses, with two statements then separated between the pipe symbol (this is equivalent to an OR statement). The first statement (italicized) checks that the last 15 (the $ symbol anchors the search to the end of the string) characters are all digits. In total, the first two commands check for a 16 digit number, starting with 4. The second statement after the OR command checks for 12 digits between 0 and 9, allowing 13 digit numbers including the 4 prefix.

As mentioned earlier, each card type is loaded from the XML configuration file and this is loaded through a call to the CreditCardValidator type's LoadCardTypes method which works as follows:

CardTypeCollection ct =
    (CardTypeCollection)ConfigurationSettings.GetConfig(
        "Etier.CreditCard"
    );

This loads a CardTypeCollection that is built through retrieving the Etier.CreditCard section which looks as follows (note the configSection element is broken on to different lines, this is purely for display reasons):

<configSections>
    <section
        name="Etier.CreditCard"
        type="Etier.CreditCard.CreditCardValidatorSectionHandler,
            CreditCardValidator, Version=0.0.0.1" />
</configSections>

<Etier.CreditCard>
    <cardTypes>
        <cardType name="VISA" regExp="^[4]([0-9]{15}$|[0-9]{12}$)" />
        <cardType name="MasterCard" regExp="^[5][1-5][0-9]{14}$" />
        <cardType name="American Express" regExp="^[34|37][0-9]{14}$" />
        etc...
    </cardTypes>
</Etier.CreditCard>

The first statement creates a new configSection - allowing the developer to handle their own custom application settings. As can be seen above, the section is given a name, the type that implements this section handler, its version and assembly name.

CreditCardValidatorSectionHandler Implementation

To create a configuration section handler, a type must provide an implementation of the IConfigurationSectionHandler interface. The interface specifies a single method - Create. The final parameter is the one of most interest, this returns an XmlNode instance of the section we're loading from the XML configuration file, allowing us to parse the settings.

When GetConfig is called from the CreditCardValidator type, it passes the section name - allowing us to obtain a programmatic representation of the Etier.CreditCard node. This in turn allows us to parse the cardTypes element and each of the cardType child elements, which, in turn gives us the complete card type collection.

Below is the implementation for the CreditCardValidatorSectionHandler type's Create method:

XmlNodeList cardTypes;
CardTypeCollection al = new CardTypeCollection();

cardTypes = section.SelectNodes("cardTypes//cardType");

foreach( XmlNode cardType in cardTypes )
{
    String name = cardType.Attributes.GetNamedItem("name").Value;
    String regExp = cardType.Attributes.GetNamedItem("regExp").Value;

    // Add them to the card types collection

    CardType ct = new CardType(name,regExp);
    al.Add(ct);
}

return al;

An XPath query is performed on the configuration section ("Etier.CreditCard") that allows us to retrieve a list of XmlNode instances for each of the cardType elements. These are then iterated through and the name and regExp attributes are retrieved and used to create new CardType instances. Each CardType instance can then be stored in the CardTypeCollection which is then retrieved by the CreditCardValidator during its OnInit event (as discussed earlier).

Client Side Validation

Adding JavaScript code to perform the same checks as were implemented by the server control was one of the key things I wanted to add after I wrote the first version.

As mentioned towards the opening of this article, I received a couple of emails from people mentioning that they'd added client-side scripting support to the original CodeProject article. I kept a fair amount of this contributed code the same, but I changed the card type checking code to use the same regular expressions as the server-side checks - allowing new card types to be added on both client and server side through the addition of a line in the XML configuration file.

Client-side script is emitted during a call to the type's RegisterClientScript method. This first generates the code, before adding it to the parent Page object through a call to its RegisterClientScriptBlock method.

Below is a snippet of the RegisterClientScript method that generates the JavaScript validation code:

protected void RegisterClientScript()
{
    this.Attributes["evaluationfunction"] = "ccnumber_verify";
    StringBuilder sb_Script = new StringBuilder();

    sb_Script.Append( "<script language="\""javascript\">" );
    sb_Script.Append( "\r" );
    sb_Script.Append( "\r" );
    sb_Script.Append( "function ccnumber_verify() {" );
    sb_Script.Append( "\r" );
    sb_Script.Append( "var strNum = document.all[document.all[\"" );
    sb_Script.Append( this.ID );
    sb_Script.Append( "\"].controltovalidate].value;" );
    sb_Script.Append( "\r" );
    ...
    Page.RegisterClientScriptBlock ("CreditCardValidation", 
                                           sb_Script.ToString());

The first line of the method sets an attribute for the CreditCardValidator control, providing the client-side equivalent of the server-side EvaluateIsValid method. The ccnumber_verify function implements the same functionality as on the server.

There are two client-side functions isNumberValid and isCardTypeCorrect. isNumberValid performs the Luhn check and looks as follows:

function isNumberValid(strNum) {
var nCheck = 0;
var nDigit = 0;
var bEven  = false;

for (n = strNum.length - 1; n >= 0; n--) {
    var cDigit = strNum.charAt (n);
    if (isDigit (cDigit)) {
        var nDigit = parseInt(cDigit, 10);
        if (bEven) {
            if ((nDigit *= 2) > 9) nDigit -= 9;
        }
        nCheck += nDigit;
        bEven = ! bEven;
    }
    else if (cDigit != ' ' && cDigit != '.' && cDigit != '-')
        return false;
    }
    return (nCheck % 10) == 0;
}

The implementation of the isCardTypeCorrect function depends on whether a card types list box is being used or not. If there is a list box then (as with server-side) the card type to check against will be specified. If no list box is being used then the card number should be checked against all card types.

The code below shows how the JavaScript is generated when the CardTypesListBox property has been set:

StringBuilder sCardTypeFunction = new StringBuilder();
sCardTypeFunction.Append( "function isCardTypeCorrect(strNum) {");
sCardTypeFunction.Append( "\r" );
                
// If the listbox is being shown, accept only the selected one.

if (_cardTypesListBox != null)
{
    sCardTypeFunction.Append( "    return strNum.match(" );
    sCardTypeFunction.Append( "document.all[\"" + 
                         _cardTypesListBox + "\"].value" );
    sCardTypeFunction.Append( ")");
    sCardTypeFunction.Append( "\r" );
    sCardTypeFunction.Append( "}" );
                
    sb_Script.Append( sCardTypeFunction.ToString() );
}

Since the card type list box contains the regular expression used to check for a valid card type, it can be retrieved and then executed using the match JavaScript function.

If the developer is not using the drop-down, the following code is executed to generate the client-side code:

sCardTypeFunction.Append( "    if ( " );

foreach( CardType ct in _cardTypes)
{
    sCardTypeFunction.AppendFormat("strNum.match(\"{0}\")", ct.RegExp);
    i++;

    if (i < _cardTypes.Count)
        sCardTypeFunction.Append(" || ");
}

sCardTypeFunction.Append(" ) \r");
sCardTypeFunction.Append("        return true;");
sCardTypeFunction.Append( "\r" );
sCardTypeFunction.Append("    else" );
sCardTypeFunction.Append( "\r" );
sCardTypeFunction.Append("        return false;");
sCardTypeFunction.Append( "\r" );
sCardTypeFunction.Append( "}" );

The card types are iterated to generate a large conditional OR statement checking for all of the regular expressions in the card types collection. Provided one of them is successful, the function returns true indicating a valid card type.

If no card types exists, the isValid card type check is ignored and just Luhn's formula is used.

The final feature is the addition of Visual Studio .NET design-time support, allowing developers to drag and drop the validation control onto a form.

Design Time Support

I wanted to include Visual Studio .NET support for the control to provide a more integrated component for developers that were using the IDE. Below is a screenshot of how it appears on the web form in design view along with the controls added to the IDE's toolbox.

 

When a control is dragged from the toolbox to the editor, the designer loads the assembly and identifies the associated designer type used to generate the HTML to represent the control. It's possible to override a base implementation of the designer and provide custom design time HTML.

Since the validation control doesn't do a great deal, a custom designer isn't especially necessary, but it did seem to cause problems within the IDE during design-time when using the BaseValidatorDesigner directly, so a type was derived instead. This was also needed to enable support for a custom property type.

To associate a control with a designer, you use the DesignerAttribute attribute as follows:

[
Designer(typeof(CreditCardValidatorDesigner))
]
public class CreditCardValidator : BaseValidator

Although this provides everything necessary to enable drag-and-drop placement, there were a few additional properties that were added to the CreditCardValidator above and beyond standard design-time support.

For example, to enable the Credit Card Validator to work alongside a card type drop-down, it's necessary to tell the validator the ID of the list box so that it can be referred to at runtime. Although this could just be a text field requiring the person to enter the name manually, it would be nicer if in the designer it displayed the relevant drop-down boxes. Below is a picture of the custom designer in use within the Visual Studio .NET IDE:

Card Types ListBox Property

This is implemented as follows:

[
TypeConverter (typeof(CardTypesListBoxConverter))
]
public string CardTypesListBox
{

The TypeConverterAttribute attribute is used to tell the Visual Studio .NET designer the type converter it can use to display options to the developer. Since the validator control's CardTypesListBox property is a string, the CardTypesListBoxConverter derives from StringConverter.

The key method for the CardTypesListBoxConverter type is GetStandardValues, which is intended to return a collection of acceptable values for the property. Within the CardTypesListBoxConverter type, this was implemented as:

public override StandardValuesCollection GetStandardValues(
    ITypeDescriptorContext context
)
{
    if (context == null || context.Container == null)
    {
        return null;
    }
    object[] locals = GetControls(context.Container);
    if (locals != null)
    {
        return new StandardValuesCollection(locals);
    }
    else
    {
        return null;
    }
}

The Container property is used to allow the type converter to find more about the environment in which it lives (the web form). The above doesn't do much more than call GetControls which retrieves an ArrayList of the names of the CardTypesListBox controls on the form. Ordinarily there should only be one, but by using the below code it's possible to make sure developers don't mis-spell. The GetControls method is implemented as follows, it loops through the collection of components on the page and when it comes across one of type CardTypesListBox it adds it to the ArrayList that is then sorted and returned.

private object[] GetControls(IContainer container)
{
    ComponentCollection componentCollection = container.Components;
    ArrayList ctrlList = new ArrayList();

    foreach( IComponent comp in componentCollection )
    {
        Control ctrl = (Control)comp;
        if (ctrl is CardTypesListBox)
        {
            if (ctrl.ID != null && ctrl.ID.Length != 0)
            {
              ValidationPropertyAttribute vpa =
                (ValidationPropertyAttribute)TypeDescriptor.GetAttributes(ctrl)
                  [typeof(ValidationPropertyAttribute)];
              if (vpa != null && vpa.Name != null)
                ctrlList.Add(String.Copy(ctrl.ID));
            }
        }
    }
            
    ctrlList.Sort();

    return ctrlList.ToArray();
}

Credit Card Validator Summary

In summary, the Credit Card Validator's first job is to load the card types from an XML configuration file. These can be represented as regular expressions and are loaded from the web application's web.config. By storing them as regular expressions they can be used for both server and client-side validation, but also allows new card types to be added without having to build and deploy a new assembly.

The key method with any validator derived from BaseValidator is EvaluateIsValid, with the credit card validator this first checks that a valid card type has been entered (checking it against the card type they chose in a drop-down if used - this control is discussed next), if it is then it checks the number using Luhn's formula. Provided both checks pass, it is assumed to be a valid number and the control votes for successful validation.

Assuming other validator controls on the web form also validate successfully, the Page.IsValid() call will return true overall indicating that the form contains valid data.

From a client-side point-of-view, the code registers a script block that is rendered by the page, this in turn implements JavaScript equivalent code that checks the number using both Luhn's formula and the custom regular expressions for checking the number's type.

Design-time support is largely taken care of automatically, except for the addition of a type converter to enable the control to list the list box controls that are to display the card types to be accepted. The CardTypesListBox control is the next thing to look at.

CardTypesListBox Implementation

As mentioned when discussing the solution's architecture, the aim of this control is to compliment the validatior, enabling visitors to choose the card number from a drop-down. The implementation is relatively simple compared to the validation control.

Since this is already quite a lengthy article, I won't discuss the direct implementation of the majority of the list box.

Since the application configuration file contains the list of card types, the list box is relatively self-sufficient and uses the same command to load the details from the file as the credit card validator. Like the validator control, this is performed during the OnInit event.

However, to enable the control to be placed into the designer where there is no application configuration settings available yet, it's necessary to determine whether we are in a run-time or design-time environment.

protected override void OnInit(System.EventArgs e)
{
    if (Site != null)
    {
        if (Site.DesignMode)
            // In design mode so just add a quick dummy item.

            Items.Add( new ListItem("CardTypes Here") );
    }
    else
    {
        CardTypeCollection ct = (CardTypeCollection)
            ConfigurationSettings.GetConfig( "Etier.CreditCard" );

As you can see, it's extremely similar to the code used during the credit card validator's OnInit event.

The majority of the work for adding support for the card types listbox is in the validator control. Because the validation uses regular expressions, it's possible to load regular expressions into the list box and then retrieve them directly from there. It's the regular expression for the card type the visitor selected that's actually used.

How to use the control

The links at the top include a demo project that contains a sample of how the control can be used. However, here are the required steps.

  1. Copy the CreditCardValidator assembly into the application's /bin directory.
  2. Edit the web.config configuration file as necessary. Although it's not necessary to list card types, it is advised as it provides an additional level of checking.
  3. Add the ASP.NET control tag prefix declaration to the top of the page as follows:
    <%@ Register TagPrefix="etier" 
      Namespace="Etier.CreditCard" Assembly="CreditCardValidator" %>
  4. Declare the validation control as follows (changing ControlToValidate and other properties where necessary):
    <etier:CreditCardValidator
      Id="MyValidator"
      ControlToValidate="CardNumber"
      ErrorMessage="Please enter a valid credit card number"
      RunAt="server"
      EnableClientScript="True"/>
  5. If using the card types list box, add a list box control to the page as follows:
    <etier:CardTypesListBox id="CardTypesListBox1" 
        runat="server" Width="198px" Rows="1">

    and set the validator control's CardTypesListBox property:

    CardTypesListBox="CardTypesListBox1"
  6. When handling the data during the form's postback, ensure you call IsValid() and only process the form data if it returns true.

To use the control from within Visual Studio .NET, you need to right click on the toolbox, choose "Add/Remove Items ...", and then browse for the compiled assembly. Then double check that the CreditCardValidator and CardTypesListBox components are then listed and checked in the list box. Then you can drag and drop them onto the page. If you want to use the card types list box, it's also necessary to add card types into the application's XML configuration file.

Conclusion

This has turned out to be quite a large article, and has grown significantly beyond what I had originally written in 2002. I've been extremely grateful to hear from those who've used the code and found it useful, and do appreciate all suggestions that people email me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here