Note: This is not the complete source code--just the main source file.
You can download the full source (with include files) from our sample code archive by clicking on the diskette icons.

HexillionEmailValidator.cs

// HexillionEmailValidator sample control
// version 1.0.0.0
// 
// Demonstrates how to create a custom email validator control.
// It does client-side validation with a JavaScript regular
// expression and server-side validation with the HexValidEmail
// component.
//
// HexGadgets (components) required:
//   - HexValidEmail COM
// Info: http://www.HexGadgets.com/
// Download: http://www.hexillion.com/download/HexGadgets.exe
//
// History:
// 2003-01-23 1.0.0.0  Created
//
// Copyright 2003 Hexillion Technologies. All rights reserved.
// 
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND/OR
// FITNESS FOR A PARTICULAR PURPOSE.

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Text;
using Hexillion.HexValidEmail.Interop;


namespace Hexillion.Samples.Interop
	{
	/// <summary>
	/// An ASP.NET validator server control that validates email addresses using both
	/// client-side and advanced server-side tests.
	/// </summary>
	/// <remarks> 
	/// <note>This control requires the <see href="http://www.hexillion.com/hg/HexValidEmail/">HexValidEmail</see>
	/// component (COM version), which you can download as part of the 
	/// <see href="http://www.HexGadgets.com/">HexGadgets installation package</see>.
	/// It also needs the HexValidEmail interop assembly, which is included with the
	/// HexillionEmailValidator assembly.</note>
	/// <note>To put the HexillionEmailValidator control in your Visual Studio .NET toolbox,
	/// choose Customize Toolbox from the Tools menu. In the dialog, click on the .NET
	/// Framework Components tab, then click Browse. Use the file dialog to find and select
	/// HexillionEmailValidator.dll. After you close the Customize Toolbox dialog, the
	/// validator control should be in your toolbox.</note>
	/// <para>Use this control to validate email addresses entered into a text field. It does
	/// both client-side validation (if enabled) and server-side validation. The server-side 
	/// validation uses <see href="http://www.hexillion.com/hg/HexValidEmail/">HexValidEmail</see>
	/// to check the syntax, domain, and username.</para>
	/// <para>The only property that you <i>must</i> set is <see cref="BaseValidator.ControlToValidate"/>.
	/// However, we strongly urge you to also set the <see cref="FromDomain"/> and 
	/// <see cref="FromEmail"/> properties to <see href="http://www.hexillion.com/docs/guides/HexValidEmail/concepts/polite_usage.htm">be polite</see>
	/// to mail server administrators.</para>
	/// </remarks>
	/// <include file='Examples.xml' path='documentation/class[@name="HexillionEmailValidator"]/*' />
	[
	DefaultProperty("ControlToValidate"),
	ToolboxData("<{0}:HexillionEmailValidator runat=\"server\"></{0}:HexillionEmailValidator>")
	]
	public class HexillionEmailValidator : BaseValidator
		{
		public HexillionEmailValidator()
			{
			// Create a HexValidEmail.Connection object
			_conn = new Connection();
			_initialError = _conn.Error;
			
			// Error message is client-side message initially
			ErrorMessage = _clientSideMessage;
			}

		/// <summary>
		/// Adds an attribute to the validator tag to hook it in to the client-side form validation.
		/// </summary>
		protected override void AddAttributesToRender( HtmlTextWriter writer ) 
			{
			base.AddAttributesToRender( writer );
			if( RenderUplevel ) 
				writer.AddAttribute("evaluationfunction", "HexillionEmailValidatorValidate");
			}    

		/// <summary>
		/// Places JavaScript code on the page for client-side validation.
		/// </summary>
		protected override void OnPreRender( EventArgs e )
			{
			base.OnPreRender( e );
			
			// Register our client-side script block
			if( RenderUplevel )
				Page.RegisterClientScriptBlock( "HexillionEmailValidator",
				                                string.Format( _clientScript,
			                                                   _regex,
			                                                   ControlToValidate,
			                                                   ID,
			                                                   _clientSideMessage ) );
			}
		
		/// <summary>
		/// Gets or sets the highest level of validation to attempt.
		/// </summary>
		/// <value><b>1</b> for syntax, <b>2</b> for domain (DNS), <b>3</b> for username (SMTP)</value>
		[
		Description("The highest level of validation to attempt: 1 for syntax, 2 for domain (DNS), 3 for username (SMTP)" ),
        DefaultValue(3),
        Category("Behavior")
		]
		public int TryLevel
			{
			get { return (int)_levelTry; }
			set
				{
				if( value < 1 || value > 3 )
					throw new ArgumentException( "TryLevel must have a value from 1 to 3." );
				_levelTry = (HexValidEmailLevel)value;
				}
			}

		/// <summary>Gets or sets the lowest confidence rating required for the validator to pass the input.</summary>
		/// <value>An integer from <b>1</b> to <b>3</b>. This should usually be set to <b>1</b>, the default value.</value>
		/// <remarks>Only a rating of <b>0</b> indicates a bad address. Thus, this property should usually be
		/// set to <b>1</b> to pass all addresses that are not confirmed bad.</remarks>
		[
		Description("The lowest confidence rating required for the validator to pass the input. This should usually be set to 1." ),
        DefaultValue(1),
        Category("Behavior")
		]
		public int RequiredRating
			{
			get { return (int)_levelRequired; }
			set 
				{
				if( value < 1 || value > 3 )
					throw new ArgumentException( "RequiredRating must have a value from 1 to 3." );
				_levelRequired = (HexValidEmailLevel)value;
				}
			}
						
		/// <summary>
		/// Gets or sets text to place after the server-side error message.
		/// </summary>
		/// <remarks>After a postback, the server-side validation determines the error message
		/// to be displayed. This property gives you a chance to place text or HTML at the
		/// end of that message.</remarks>
		[
		Description("Text to place after the server-side error message."),
        DefaultValue(""),
        Category("Appearance")
		]
		public string AppendedMessage
			{
			get { return _appendedMessage; }
			set { _appendedMessage = value; }
			}
		
		/// <summary>
		/// Gets or sets text to place before the server-side error message.
		/// </summary>
		/// <remarks>After a postback, the server-side validation determines the error message
		/// to be displayed. This property gives you a chance to place text or HTML in front of
		/// that message.</remarks>
		[
		Description("Text to place before the server-side error message."),
        DefaultValue(""),
        Category("Appearance")
		]
		public string PrependedMessage
			{
			get { return _prependedMessage; }
			set { _prependedMessage = value; }
			}
			
		/// <summary>
		/// Gets or sets the error message to displayed if the client-side validation
		/// rejects the input.
		/// </summary>
		[
		Description("Error message for client-side validation."),
        DefaultValue("The email address is invalid"),
        Category("Appearance")
		]
		public string ClientSideErrorMessage
			{
			get { return _clientSideMessage; }
			set { _clientSideMessage = value; }
			}
			
		/// <summary>
		/// Gets or sets the regular expression for validating the email address on the client side.
		/// </summary>
		/// <remarks>
		///	Notes about the default regular expression:
		/// <list type="bullet">
		/// <item><description>
		/// It is liberal within legal syntax. If you look at <see href="http://www.rfc-editor.org/rfc/rfc2822.txt">RFC 2822</see>, 
		/// there are a lot more legal characters than you might expect.
		/// This regex allows them in the local part, regardless of whether
		/// they are common. Likewise, the regex passes just about any
		/// legal domain name, regardless of whether it's likely. 
		/// The general philosophy is to let most legal stuff
		/// slide through the client-side validation and let the server-side
		/// validations check for realism.
		/// </description></item>
		/// <item><description>
		/// It is strict when syntax is illegal. Dash characters ("-"), for
		/// example, are not allowed on the ends of the domain labels.
		/// Also, domains cannot contain underscores ("_")... at least not
		/// domains that conform to the old host name rules, which are what
		/// domains in email addresses must follow these days.
		/// </description></item>
		/// <item><description>
		/// It does require at least a second-level domain and conformance
		/// conformance to host name standards (<see href="http://www.rfc-editor.org/rfc/rfc2822.txt">RFC 952</see>
		/// and <see href="http://www.rfc-editor.org/rfc/rfc1123.txt">RFC 1123</see>).
		/// </description></item>
		/// <item><description>
		/// It allows only IPv4 domain literals.
		/// </description></item>
		/// <item><description>
		/// It does not allow display names, quoted literals, comments, or
		/// whitespace. It basically allows a limited version of the
		/// addr-spec form described in RFC 2822.
		/// </description></item>
		/// </list>
		/// </remarks>
		[
		Description("Regular expression for validating the email address on the client side."),
        DefaultValue(_defaultRegex),
        Category("Behavior")
		]
		public string ClientSideRegex
			{
			get { return _regex; }
			set { _regex = value; }
			}

		/// <summary>
		/// Gets or sets the maximum time to wait for DNS validation (milliseconds).
		/// </summary>
		/// <value>An integer from 50 to 60000 representing milliseconds. The default is 4000.</value>
		[
		Description("Maximum time to wait for DNS validation (milliseconds)."),
        DefaultValue(4000),
        Category("Behavior")
		]
		public int DnsTimeout
			{
			get { return _dnsTimeout; }
			set
				{
				if( value < 50 || value > 60000 )
					throw new ArgumentException( "DnsTimeout must have a value from 50 to 60000 (milliseconds). We suggest 4000." );
				_dnsTimeout = value;
				}
			}

		/// <summary>
		/// Gets or sets the maximum time to wait for SMTP validation (milliseconds).
		/// </summary>
		/// <value>An integer from 50 to 120000 representing milliseconds. The default is 10000.</value>
		[
		Description("Maximum time to wait for SMTP validation (milliseconds)."),
        DefaultValue(10000),
        Category("Behavior")
		]
		public int SmtpTimeout
			{
			get { return _smtpTimeout; }
			set
				{
				if( value < 50 || value > 120000 )
					throw new ArgumentException( "DnsTimeout must have a value from 50 to 120000 (milliseconds). We suggest 10000." );
				_smtpTimeout = value;
				}
			}

		/// <summary>
		/// Gets or sets the domain name to use as identification during SMTP validation.
		/// </summary>
		/// <value>A domain name string that will be sent with the SMTP HELO command.
		/// Set this to the domain name of your machine.</value>
		/// <remarks>See <see href="http://www.hexillion.com/docs/guides/HexValidEmail/concepts/polite_usage.htm">
		/// http://www.hexillion.com/docs/guides/HexValidEmail/concepts/polite_usage.htm</see> for more details.</remarks>
		[
		Description("The domain name to use as identification during SMTP validation. Set this to the domain name of your machine." ),
        DefaultValue("hexillion.com"),
        Category("Behavior")
		]
		public string FromDomain
			{
			get { return _conn.FromDomain; }
			set
				{
				if( null == value )
					throw new ArgumentNullException();
				_conn.FromDomain = value;
				}
			}

		/// <summary>
		/// Gets or sets the email address to use as contact information during SMTP validation.
		/// </summary>
		/// <value>An email address string that will be sent with the SMTP MAIL FROM command.
		/// Set this to the email address of a technical contact person at your domain.</value>
		/// <remarks>See <see href="http://www.hexillion.com/docs/guides/HexValidEmail/concepts/polite_usage.htm">
		/// http://www.hexillion.com/docs/guides/HexValidEmail/concepts/polite_usage.htm</see> for more details.</remarks>
		[
		Description("The email address to use as contact information during SMTP validation. Set this to the email address of a technical contact person at your domain." ),
        DefaultValue("HexValidEmail@hexillion.com"),
        Category("Behavior")
		]
		public string FromEmail
			{
			get { return _conn.FromEmail; }
			set
				{
				if( null == value )
					throw new ArgumentNullException();
				_conn.FromEmail = value;
				}
			}
			
		/// <summary>
		/// Validates the referenced email address field.
		/// </summary>
		/// <returns><b>true</b> if the email address passes, <b>false</b> if it fails.</returns>
		protected override bool EvaluateIsValid()
			{
			HexValidEmailLevel rating;
			
			// Set the timeouts
			_conn.Timeouts.Item(HexValidEmailTimeout.hexVeTimeoutDnsTotal).Value = _dnsTimeout;
			_conn.Timeouts.Item(HexValidEmailTimeout.hexVeTimeoutSmtpTotal).Value = _smtpTimeout;
			
			// Do the validation
			rating = (HexValidEmailLevel)_conn.Validate( GetControlValidationValue( ControlToValidate ),
			                                            _levelTry );

			StringBuilder sb = new StringBuilder();
			sb.Append( _prependedMessage );
			sb.Append( GetErrorString( _conn.Error ) );
			sb.Append( _appendedMessage );
			ErrorMessage = sb.ToString();
			
			// Establish whether the address passed
			bool passed = (rating >= _levelRequired);

			// Set visibility of the error message
			Style["visibility"] = passed ? "hidden" : "visible";
			
			return passed;
			}
			
		private string GetErrorString(int error)
			{
			switch( (HexValidEmailErrors)error )
				{
				case HexValidEmailErrors.hexVeErrTimedOut:
					return "Timed out";

				case HexValidEmailErrors.hexVeErrConnectionRefused:
					return "Connection refused";

				case HexValidEmailErrors.hexVeErrConnectionReset:
					return "Connection reset";

				case HexValidEmailErrors.hexVeErrHostUnreachable:
					return "Host unreachable";

				case HexValidEmailErrors.hexVeErrAddressNotAvailable:
					return "Address not available";

				case HexValidEmailErrors.hexVeErrNetworkDown:
					return "Network down";

				case HexValidEmailErrors.hexVeErrNetworkUnreachable:
					return "Network unreachable";

				case HexValidEmailErrors.hexVeErrConnectionAborted:
					return "Connection aborted";
            
				case HexValidEmailErrors.hexVeErrHostNotFound:
					return "Host not found";
            
				case HexValidEmailErrors.hexVeErrTryAgain:
					return "Try again";
            
				case HexValidEmailErrors.hexVeErrNoRecovery:
					return "No recovery";
            
				case HexValidEmailErrors.hexVeErrNoData:
					return "No data";
        
				case HexValidEmailErrors.hexVeErrUnexpected:
					return "Unexpected error";
            
				case HexValidEmailErrors.hexVeErrAddrTooLong:
					return "The email address is too long";
            
				case HexValidEmailErrors.hexVeErrExtraTextPresent:
					return "The email address has extra text that is not allowed";
 
				case HexValidEmailErrors.hexVeErrIllegalChar:
					return "The email address contains an illegal character";
            
				case HexValidEmailErrors.hexVeErrUnbalancedParenthesis:
					return "The email address contains an unbalanced parenthesis";
            
				case HexValidEmailErrors.hexVeErrUnbalancedSquareBracket:
					return "The email address contains an unbalanced square bracket";
            
				case HexValidEmailErrors.hexVeErrUnbalancedAngleBracket:
					return "The email address contains an unbalanced angle bracket";
            
				case HexValidEmailErrors.hexVeErrQuotationMarksNotClosed:
					return "Quotation marks in the email address are not closed";
            
				case HexValidEmailErrors.hexVeErrDomainLiteralPresent:
					return "The email address contains a domain literal, which is not allowed";
            
				case HexValidEmailErrors.hexVeErrMisplacedDomainLiteral:
					return "The email address contains a misplaced domain literal";
            
				case HexValidEmailErrors.hexVeErrMisplacedQuotedString:
					return "The email address contains a misplaced quoted string";
            
				case HexValidEmailErrors.hexVeErrNoLocalPart:
					return "The email address does not include a local part (username)";
            
				case HexValidEmailErrors.hexVeErrNoDomain:
					return "The email address does not include a domain";
            
				case HexValidEmailErrors.hexVeErrInvalidDomain:
					return "The domain in the email address is invalid";
            
				case HexValidEmailErrors.hexVeErrInvalidDomainLiteral:
					return "The email address contains an invalid domain literal";
            
				case HexValidEmailErrors.hexVeErrNoDnsServerConfigured:
					return "No DNS servers configured";
            
				case HexValidEmailErrors.hexVeErrDomainDoesNotExist:
					return "The email address domain does not exist";
            
				case HexValidEmailErrors.hexVeErrNoMxForDomain:
					return "The email address domain does not have a mail exchanger";
            
				case HexValidEmailErrors.hexVeErrCouldNotVerifyRecipient:
					return "Could not verify the local part (username) due to a problem communicating with the mail server";
            
				case HexValidEmailErrors.hexVeErrRecipientRejected:
					return "The domain's mail server rejected the email address";

				case HexValidEmailErrors.hexVeErrNoAddrSpecified:
					return "No email address was specified";
            
				case HexValidEmailErrors.hexVeErrSuccess:
					return "None";
					
				case HexValidEmailErrors.hexVeErrLicenseFileNotFound:
					return "License file not found";
					
				case HexValidEmailErrors.hexVeErrCouldNotOpenLicenseFile:
					return "Could not open license file";
					
				case HexValidEmailErrors.hexVeErrCorruptLicenseFile:
					return "License file is corrupt";
					
				case HexValidEmailErrors.hexVeErrWrongProductLicense:
					return "License is for wrong product";
					
				case HexValidEmailErrors.hexVeErrWrongVersionLicense:
					return "License is for wrong version";
					
				case HexValidEmailErrors.hexVeErrUnlicensedProcessors:
					return "Running on machine with unlicensed processors";

				default:
					return "Unknown error";
				}
    		}
    		
		private HexValidEmailLevel _levelTry = HexValidEmailLevel.hexVeLevelSmtp;
		private HexValidEmailLevel _levelRequired = HexValidEmailLevel.hexVeLevelSyntax;
		private int _dnsTimeout = 4000;
		private int _smtpTimeout = 10000;
		private int _initialError;
		private Connection _conn;
		private string _appendedMessage = String.Empty;
		private string _prependedMessage = String.Empty;
		private string _clientSideMessage = "The email address is invalid";
		private string _regex = _defaultRegex;
		
		// See the ClientSideRegex property for comments about this expression.
		private const string _defaultRegex = @"^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$";
		
		private const string _clientScript = @"
<script language=""JavaScript""><!--
	// Client-side script for the HexillionEmailValidator sample control.
	// http://www.Hexillion.com/samples/#HexillionEmailValidator
	
	function HEVGetElement( id )
		{{
		if( document.all )
			return document.all[id];
		else
			return document.getElementById( id );
		}}

	function HexillionEmailValidatorValidate()
		{{
		var re = /{0}/i;
		if( !re.test( HEVGetElement( '{1}' ).value ) )
			{{
			// Reset to the client-side message in case there's
			// an old server-side message still in place
			HEVGetElement( '{2}' ).firstChild.nodeValue = '{3}';
			return false;
			}}
		return true;
		}}
//--></script>
";		
		}
	}