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

A Managed C++ Email Validator Control for ASP.NET

0.00/5 (No votes)
3 Jan 2003 3  
An ASP.NET Validator Control implemented in Managed C++ that can be used to verify email addresses by connecting to SMTP servers listed as Mail eXchangers for a domain, includes example of Win32 API Interoperability with C++ .NET. The validation is not RegEx based.

Sample Image - EmailValidator.gif

Introduction

This article is my second ASP.NET Validator Control article, but this time implemented in Managed C++. This was primarily for its interoperability with the Win32 API. The aim is to produce a validator control that will connect to SMTP servers for a domain to establish whether the entered email address is valid.

I will first provide a very high level overview of the solution, explaining how it works by connecting to SMTP servers and issuing commands. After that I will then provide implementation overviews - how the code is intended to work before finally going into the actual code and explaining how it works.

Solution Overview

It takes the entered email address and determines the domain (in the picture above, the domain being webmaster@codeproject.com), this is so that a DNS query can be made to retrieve MX records. MX stands for Mail Exchange and they hold any servers which will accept email for the domain. Quite often there is more than one, this can be used to distribute load but also to provide redundant connections - different servers are often distributed across networks to ensure that should any connection fail email can still be routed. MX records also have a priority associated with them, so that MTA's (Mail Transfer Agents - the bits of software that connect to servers shifting email around, e.g. Sendmail etc.) know which server to connect to first. If the connection fails they can then try others. The validator control will connect to the first server and try to start a normal SMTP connection, if that fails it will then try to connect to the next server and so on. In fact, it will try all records until it reaches a server that will accept email.

Once the connection has been established it proceeds as per the SMTP protocol (see RFC 821 for more details). Here is an extract from the RFC:

  S: MAIL FROM:<Smith@Alpha.ARPA>
R: 250 OK S: RCPT TO:<Jones@Beta.ARPA>
R: 250 OK S: RCPT TO:<Green@Beta.ARPA>
R: 550 No such user here S: RCPT TO:<Brown@Beta.ARPA>
R: 250 OK

Normally, this would continue by issuing further commands to actually send a message. The connection initially informs the server who the email is from, and then gives 3 recipients. The first and final addresses are accepted. However, Green@Beta.ARPA is not accepted. This is essentially what the Validator Control does, connect to an SMTP server as if it were another MTA and tries to send a message to a user.

Provided the server returns an ok response then the validator control can return true and validation succeeds.

It's worth noting that the SMTP protocl does include support for a VRFY command which was intended to verify a user. However, due to misuse this is almost always turned off on SMTP servers. As a result, attempting to send an email is the only way.

Implementation Overview

The solution is implemented entirely in Managed C++. This was the easiest way to use the Win32 API to issue the DNS queries. Firstly, I will provide a quick overview of the classes and their roles.

EmailValidator Class

The EmailValidator class is the BaseValidator derived class that can be put into an ASP.NET web form.

Methods

  • EvaluateIsValid

    Overriden from BaseValidator. Is called when the page is validated. Should return true or false depending on whether validation for the associated control succeeded or failed.

  • ControlPropertiesValid

    Overriden from BaseValidator. Used to determine whether the associated control is valid. The implementation uses Control::FindControl to get a reference to the control, and stores it in the _emailAddressBox private member variable.

Properties

  • LocalServer

    Used during the SMTP connection. The SMTP protocol dictates that the sever should identify itself as part of the HELO command. This allows it to be set from the tag properties.

  • FromEmail

    Used during the SMTP connection. This address is used in the MAIL FROM command.

Query Class

The Query class implements a single method - GetMx - which is used to retrieve MX records for a specified domain name. It takes the domain name in the form of a single String* parameter, and returns the results as an ArrayList populated with MxRecord structures.

SmtpMailer Class

The SmtpMailer class connects to the SMTP servers and issues commands. For every command it issues a return value is checked through the IsOk function. Provided the code returned starts with 1, 2, or 3 then the next command can be issued. If the RCPT TO command is issued and returns an error number, then the validation is said to have failed.

The main method is WillAcceptAddress which will connect to a server (set through the String *smtpServer parameter), issue commands and check them. If the RCPT TO command returns ok then WillAcceptAddress will return true and overall control validation will have succeeded. If it fails and there are other servers listed as MX records for the domain the remaining servers will be checked in order of their priority (lowest number first).

MxRecord Structure

Whilst building the DNS querying code I produced a small C# console application I could use to test results, so the easiest way of transferring the results was through an ArrayList of structures that would hold the SMTP server address and a priority. The priority is used to set the order in which servers should be tried. Often domains have backup servers in case any single machine should fail ensuring that emails are not lost.

The structure also includes support for the IComparable interface to allow sorting within the ArrayList based on the priority.

Solution Implementation

EmailValidator Class

ControlPropertiesValid

The code is fairly self explanatory, it obtains a reference to the Validator's associated control (which ought to be a TextBox). After obtaining this reference it is then cast to a private member variable (_emailAddressBox) to be used in future. If this succeeds then the method returns true. If ControlToValidate points to anything other than a TextBox an exception will be thrown.

// Find the edit box control that contains the email address, and

// store it in a private member variable.

bool EmailValidator::ControlPropertiesValid()
{
    Control* ctrl = Control::FindControl( this->ControlToValidate );     
    _emailAddressBox = __try_cast<TextBox*>(ctrl);

    return true;
}

EvaluateIsValid

The purpose of this method is to perform the validation. Firstly it determines the domain name of the email address through a Regular Expression (References to further details are at the bottom of the article) - by taking anything after the @ symbol, so webmaster@codeproject.com would keep codeproject.com. To me Regular Expressions look damned complicated but after working with .NET for a while now they're proving extremely useful.

After the domain name has been determined its time to issue the DNS query to retrieve MX records (in the form of MxRecord structures) that will be stored in an ArrayList. Once a list of MX records have been returned we then connect to each SMTP server and issue commands. This will be repeated until a server is found that will accept emails for the user, or there are no SMTP servers left to try.

SMTP Connections are handled by the SmtpMailer Class which will be looked at later.

bool EmailValidator::EvaluateIsValid()
{
    String* domainName;
    
    // What's the domain name to look-up?

    // Do a Regex search to find the domain name part.

    Regex* r = new Regex(S"^*@(?<domain>\\S+)");
    if (r->IsMatch( _emailAddressBox->Text ))
        domainName = r->Match( _emailAddressBox->Text )->
            Result("${domain}")->ToString();
    else
        return false;

    // Create an ArrayList of MxRecord structures containing

    // the mail servers to check...

    ArrayList* serverList = Etier::Dns::Query::GetMx( domainName );
    serverList->Sort();

    // Create a SmtpMailer instance, and set the default parameters

    // for the SMTP sessions.

    SmtpMailer *mail = new SmtpMailer( m_sLocalServer, m_sFromEmail );

    // Go through each MxRecord in the serverList to see if the

    // email address will be accepted by any of them.

    int i = 0;
    int nRecordCount = serverList->Count;

    while ( i < nRecordCount )
    {
        MxRecord mx = *dynamic_cast<__box MxRecord*>(serverList->get_Item(i));

        if (mail->WillAcceptAddress( mx.NameExchange, _emailAddressBox->Text ))
            return true;
            
        i++;
    }
    return false;
}

After seeing how the validator control performs validation, its time to look at the other classes used, namely the Query and SmtpMailer.

Query Class

The Query Class is in the Etier::Dns namespace, and uses the Win32 API to obtain a list of MX records. It has a single method - GetMx - which uses the DnsQuery API function. Note that this function is only included in Windows 2000 and later. Like many DNS functions, the DnsQuery function type is implemented in multiple forms to facilitate different encoding methods (taken from MSDN). To convert between the LPCTSTR and System::String types a Util class is included that implements ConvertStringToLPCTSTR. The implementation of this is not essential to the solution so I will not cover it in this article, but was found through a search on Microsoft's public newsgroups.

The DnsQuery function is called, passing (amongst other things) the domain name, query type (DNS_TYPE_MX), and a pointer to a DNS_RECORD pointer - this is used to hold the results of the query.

GetMx Method Implementation

    // GetMx method

    //

    // Returns an ArrayList of MxRecord structs with

    // the MX records.

    static ArrayList* GetMx(String* domainName)
    {
        DNS_STATUS status;
        DNS_RECORD* result = 0;

#ifdef _UNICODE
        status = DnsQuery_W (
            Util::ConvertStringToLPCTSTR(domainName),
            DNS_TYPE_MX,
            DNS_QUERY_STANDARD,
            NULL,
            &result,
            NULL );
#else
        status = DnsQuery_A (
            Util::ConvertStringToLPCTSTR(domainName),
            DNS_TYPE_MX,
            DNS_QUERY_STANDARD,
            NULL,
            &result,
            NULL );
#endif

Provided the DnsQuery function succeeds an ArrayList is created that is used to contain MxRecord types. This list is populated whilst looping through the linked list of records contained in the DNS_RECORD structure. The DNS_RECORD structure includes struct _DnsRecord * pNext - a pointer to the next record. By setting the current DNS_RECORD to pNext its possible to go through every record returned.

I originally believed that by issuing a DNS_TYPE_MX query the result would only contain MX records. However, this gave me exceptions and I had to include a conditional to check that the current result's type (contained in the wType field) was DNS_TYPE_MX. Provided it is, then an MxRecord structure is initialised containing the SMTP server's address, and priority.

It's necessary to use String::Copy to copy the string obtained through the pNameExchange pointer since when GetMx finishes result will be deleted and thus the existing reference would also become invalid and an exception will be thrown.

// Create the ArrayList type that will contain

// the MxRecord structs

ArrayList* aHostList = new ArrayList();
if (SUCCEEDED(status))
{
    // If the call succeeded, go through the results

    // picking out the MX records and creating MxRecord

    // structs to insert into the ArrayList

    if (result!=0)
    {
        // Loop through all the DNS records to find the MX ones.

        // Check that we've not reached a NULL pointer to the next

        // record and that the pNext pointer is not the same as the

        // pointer to the current record.

        while ( (result->pNext!=NULL) && (result->pNext != result))
        {
            if (DNS_TYPE_MX == result->wType)
            {
                MxRecord mx;    // create the empty struct to fill it

                mx.NameExchange = String::Copy(((String*)(LPSTR)result->
                    Data.MX.pNameExchange) );
                mx.nPriority = result->Data.MX.wPreference;
                        
                aHostList->Add( __box(mx) );   // box the __value struct

                                                  //and add it to the ArrayList

            }
                            
            result = result->pNext;    // move to the next DNS record

        }
    }
    // Clean up by freeing up the records

    DnsRecordListFree(result,DnsFreeRecordList);
}
return aHostList;

It's possible that readers of this article will be from an ASP background, primarily VB or C# and may not have an understanding of boxing and unboxing, for their benefit I'll include a quick overview. Boxing is the process of converting a value type to a reference type. ArrayList's Add method holds a pointer to any instance of Object (or Object derived type). Since Object is a reference type its necessary to box our value type (or wrap it) with a reference type. Since it involves creating a new object on the managed heap, C++ requires the explicit use of the __box keyword. This prevents any ambiguity as to whether it still points to the old instance.

Unboxing can then be used to create a value type from a reference type. In the EmailValidator class this is done through the following code:

MxRecord mx = *dynamic_cast<__box MxRecord*>(serverList->get_Item(i));

Its necessary to tell the runtime how the reference class should be interpreted, and this is achieved through de-referencing the pointer to the boxed MxRecord. Its also necessary to tell the runtime how to interpret the reference type (hence the dynamic_cast). Boxing and unboxing is done automatically in C# but C++ makes it absolutely clear another object is created and thus by changing the boxed type its not changing the original value type. Boxing and unboxing is also a fairly intensive operation, so performing it as few times as possible is desireable.

I decided to create my own structure as opposed to using the DNS_RECORD to allow interoperability with other .NET applications to be as painless as possible. For example, its possible to use the same DNS querying code from C# without touching the Win32 API (one of the great things about .NET, and C++ .NET in particular). In fact, I produced a quick C# console application to test the code whilst I was developing it. It also demonstrates one of the greatest things about Visual Studio .NET - the ability to debug across languages!

Time for a quick look at the MxRecord structure.

public __value struct MxRecord : System::IComparable
{
    // IComparable::CompareTo

    int CompareTo( System::Object *obj )
    {
        // Unbox the object to the MxRecord struct

        MxRecord mx = *dynamic_cast<__box MxRecord*>(obj);

        // return the difference between the two priorities

        return this->nPriority - mx.nPriority;
    }

    String *NameExchange;    // holds the address for the exchanger

    int nPriority;            // priority index

};

The structure implements the CompareTo method of the IComparable interface. Since the records will be put in an ArrayList it would be nice to be able to sort them based on their priority. To do this its necessary to include support for the IComparable interface. The CompareTo method compares the current instance with another object of the same type, it should return < 0 if the instance is less than *obj, 0 if it is equal, and > 0 if it is greater than.

Going back to the overall solution, we now have an ArrayList which is populated with MxRecord structures and its time to start connecting to them to see whether they'll accept emails for the user. All SMTP sessions are handled by the SmtpMailer class which is what we will look at next.

SmtpMailer Class

The SmtpMailer class is based on code by Albert Pascual in his CodeProject article "Sending mail in Managed C++ using SMTP".

Firstly, the constructor

    SmtpMailer::SmtpMailer( String *localServer, String *fromEmail )
    {
        m_sLocalServer = localServer;
        m_sFromEmail = fromEmail;
    }

The SmtpMailer instance stores details of the Local Server and From Email addresses to be used throughout the connections, and these are set through the constructor.

WillAcceptAddress

This method actually connects to the specified server, and issues commands as per the SMTP protocol. The code is largely self explanatory and should be easy to follow for anybody without C++ experience.

It uses the .NET Framework's TcpClient to connect to a server, and issues commands through the Server's NetworkStream. Again, to me streams are something I've never used before .NET and they are extremely versatile.

After each SMTP command is issued its result (obtained through the RdStrm->ReadLine call) is checked to determine whether the server effectively returned ok or error. Ok values are determined by checking the first digit of the string returned, if its between 1 and 3 then the server accepted the command and is ready to accept the next one. If an error value is returned then WillAcceptAddress will return false and any connections will be closed. This checking is performed through the IsOk method (which in turn uses Regular Expressions).

The important SMTP command is RCPT TO. This is used to inform the MTA of any recipients, the result of this command is used to determine whether validation should succeed.

// WillAcceptAddress opens an SMTP connection, and then issues a number of

// commands to determine whether the server will accept email for the given

// email address.

bool SmtpMailer::WillAcceptAddress( String *smtpServer, String *emailAddress )
{
    NetworkStream    *pNsEmail;
    StreamReader    *RdStrm;
    String            *Data;
    bool bIsValid = false;
    unsigned char sendbytes __gc[];
    TcpClient *pServer = new TcpClient(smtpServer,25);


    pNsEmail = pServer->GetStream();
    RdStrm = new StreamReader(pServer->GetStream());

    if (!IsOk( RdStrm->ReadLine() ))    // Was the server reply ok?

        return false;

    Data = String::Format("HELO {0}\r\n", m_sLocalServer);

    sendbytes = System::Text::Encoding::ASCII->GetBytes(Data);    
    pNsEmail->Write(sendbytes, 0, sendbytes->get_Length());
    sendbytes = 0;
    Data = 0;
    if (!IsOk( RdStrm->ReadLine() ))
        return false;

    Data = String::Format("MAIL FROM:<{0}>\r\n", m_sFromEmail);
    sendbytes = System::Text::Encoding::ASCII->GetBytes(Data);    
    pNsEmail->Write(sendbytes, 0, sendbytes->get_Length());
    sendbytes = 0;
    Data = 0;
    if (!IsOk( RdStrm->ReadLine() ))
        return false;

    Data = String::Format("RCPT TO:<{0}>\r\n",emailAddress);
    sendbytes = System::Text::Encoding::ASCII->GetBytes(Data);
    pNsEmail->Write(sendbytes, 0, sendbytes->get_Length());
    sendbytes = 0;
    Data = 0;

    // Store the return of WillAcceptAddress in the bIsValid flag

    // thus allowing us to close connections and clean up before returning

    // from the function.

    bIsValid = IsOk( RdStrm->ReadLine() );

    Data = "QUIT\r\n";
    sendbytes = System::Text::Encoding::ASCII->GetBytes(Data);
    pNsEmail->Write(sendbytes, 0, sendbytes->get_Length());
    sendbytes = 0;
    Data = 0;

    pNsEmail->Close();
    RdStrm->Close();
    pServer->Close();

    return bIsValid;
}

WillAcceptAddress is called for each MX record found. For example, performing a DNS query for MX records on codeproject.com yields a single result - mail.codeproject.com, so only a single connection is made. If that server returns an error code during the session WillAcceptAddress will fail and since there's only one server validation as a whole will fail. If more servers existed then they would also be checked.

Example Usage

The downloads includes an ASP.NET Web Application that uses the assembly. However, here is the code you would use to put the tag on a page.

Firstly its necessary to map a namespace in the assembly into the page, such that any controls can be referenced. This is done as follows:

<%@ Register TagPrefix="etier" Namespace="Etier" Assembly="SmtpSend" %>

The validator can then be included as follows

<etier:EmailValidator
  Id="MyValidator"
  Display="none"
  ControlToValidate="Address"
  ErrorMessage="* Invalid"
  RunAt="server"
  EnableClientScript="False"
  LocalServer="oobaloo.co.uk"
  FromEmail="webmaster@oobaloo.co.uk"
/>

The code is essentially the same as that for any validator with the exception of the FromEmail and LocalServer properties which were added.

Conclusion

Once again I am left to admire ASP.NET :) Page Validation is just one of it's features that makes web application development a real joy. I've done a fair bit of MFC development before and its great to be able to use C++ to produce ASP.NET controls (even if the managed extension syntax does make the code look a little kludgy).

Hopefully this has been useful for those in the C++ world to see that you can still use C++ with ASP.NET. It's true that ASP.NET does not include a C++ compiler, and so writing C++ code directly in ASPX files or as part of code-behind won't work. However, it is possible to produce code in assemblies and then pre-compile.

The other aim was to show those with little or no C++ experience (since its assumed most ASP developers have a strong VB bias) why its so great. C++ .NET is unique amongst other .NET languages in its ability to produce unmanaged or managed code (how storage for instances is maintained) but also its strong Interoperability support - one of the main reasons why this solution was implemented in Managed C++ rather than C#. By using MC++ its possible to call the functions directly by including any necessary headers (DnsQuery is from windns.h). If this were to be accomplised in C# using PInvoke I would have had to included DllImport statements galore, as well as including definitions for any structures and methods which can get pretty dull pretty quick.

The code is free for anybody to use and improve, I'm by no means a C++ guru so I'm sure there are bits which could be implemented more efficiently, or designed better. If you do make improvements to the code (or spot glaring errors) then it'd be great to hear from you.

References

Regular Expressions:
.NET Framework Regular Expressions on MSDN

History

  • 15/12/2002 - Thanks to a post by Timothy Glenn Stockstill I've updated the code to include a call to DnsRecordListFree. Sorry that its taken me this long to get the code and archives updated.

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