|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
BackgroundThis 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 ObjectivesThe 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:
Solution DesignI'll first go through design implications for the objectives discussed above and then move into class design etc. later. Client-Side ValidationFortunately, 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 SupportThere'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 SupportThere 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 Configuration outside of the ASP.NET tagAgain, 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. ExtensibilityFortunately, 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 Solution ArchitectureBroadly speaking, there are two user interface controls to be built and can be implemented in two classes:
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
CardTypesListBox Infrastructure Classes
Shared Infrastructure Classes
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 ImplementationBroadly, 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 The key method to override from the 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 Time now to look at how the various parts of the validator work. EvaluateIsValid ImplementationThe first part of the implementation looks to see whether a If the developer has used a If the developer isn't using a Assuming the number passes the valid card type check, the number is processed using Luhn's formula by calling the type's 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 Luhn's Formula ImplementationFirst 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 FormulaThe 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:
C# ImplementationThe 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 ChecksDuring the The Regular ExpressionsFor 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 CardTypeCollection ct =
(CardTypeCollection)ConfigurationSettings.GetConfig(
"Etier.CreditCard"
);
This loads a <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 CreditCardValidatorSectionHandler ImplementationTo create a configuration section handler, a type must provide an implementation of the When Below is the implementation for the 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 (" Client Side ValidationAdding 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 Below is a snippet of the 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 There are two client-side functions 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 The code below shows how the JavaScript is generated when the 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 If no card types exists, the 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 SupportI 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 To associate a control with a designer, you use the [
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 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:
This is implemented as follows: [
TypeConverter (typeof(CardTypesListBoxConverter))
]
public string CardTypesListBox
{
The The key method for the 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 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 SummaryIn 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 Assuming other validator controls on the web form also validate successfully, the 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 ImplementationAs 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 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 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 controlThe 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.
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 ConclusionThis 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.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||