A POP3-client Control for the .NET Platform
Tim Kiely, SoftArtisans Developer
Microsoft's .NET framework brings a rich, RAD environment to Windows and Web application development. There are already many sources of information and sample code on .NET programming. MSDN magazine, www.gotdotnet.com, www.c-sharpcorner.com, and many other Web sites. ASP.NET Web controls bring the same experience to Web component development.
Over the past few years, the browser has become a popular container to standardize the user's GUI experience, as well as a means of centralizing application deployment on the server. This has caused applications developers to continually push the boundaries of the browser's functionality. Among its new roles, e-mail access is particularly common. In this article I will present SoftArtisans POP3, a POP3 client Web control. The article explains how I used the .NET framework to build the component and offers tips, cautions, and marginally creative ideas that I encountered along the way.
The code that is included with the download of POP3 contains a .Net assembly, its source code, and two client samples. The component can be used as a non-graphical component,or it can be dropped on an ASP.NET Web form and accessed as a control with a customizable UI. The sample clients demonstrate both of these usages. One uses only the component's POP3 functionality and generates its own UI through a series of .aspx pages. The second uses SAPOP3Control, demonstrating how one line of code and a register directive in a Web Form—
<%@Register TagPrefix="SA" Namespace="SoftArtisans"
<SA:SAPOP3Control HeaderColor=62617 FontSize=10 UsesBorder=true
Log=false runat="server />
gives us a rich graphical control:
SAPOP3Control Object Model
The object model consist of an ASP.NET composite control class(SAPOP3Control) which handles the rendering of the UI, a POP3 class(SAPOP3) which exposes the functionality of the POP3 protocol and contains a collection of message objects(SAPOP3Message) each of which exposes a collection of MIME objects(SAMimeEntity) if the message contains attachments. Both SAPOP3Message and SAMimeEntity derive from CBaseMessagePvt which encapsulates common message handling.
An ASP.NET custom control is simply a class that derives from System.Web.UI.Control. This BCL(Base Class Library) class encapsulates all of the plumbing of a Web app, offering derived classes direct access to the ASP intrinsic objects such as Session and Request. Also, several virtual functions, notably Render(HtmlTextWriter writer) and CreateChildControls(), make it easy to deliver HTML to the client. Render() allows a control to write directly to the Response stream. A composite control, however, overrides CreatChildControls() to add sub controls to the Controls collection of the base class. Web controls, such as System.Web.UI.TextBox or System.Web.UI.Label, need only be declared, initialized, and added to the collection. Then, in the base class' Render() method, RenderControl() is called on each child control. This method writes the control's HTML to the Response stream. Thus, composite controls present a familiar and intuitive programming paradigm in which to generate complex HTML content. Using this model, markup can be added directly through a System.Web.UI.LiteralControl.
protected override void CreateChildControls()
The great benefit, however, comes when we add more complicated UI structures, such as data grids, tables, or templated controls without writing a line of HTML.
protected override void CreateChildControls()
DataGrid dg = new DataGrid();
// initialze data grid
SAPOP3Control's execution logic leverages ASP's intrinsic objects to determine and maintain its state. CreateChildControls() is the first method to be called by the framework under both initial page load and postback(the OnLoad() event handler is called first when the page is loaded, but during a postback, a control enters its ProcessPostData stage, when CreateChildControls() is called, before its Render stage, when OnLoad() is called) We, therefore, use this method as the logical entry point to resolve the control's state and execution path through a call to InitState(). This method checks for three possible states:
- We are not in a postback and the request does not have any parameters.
- In this case, the page is being loaded for the first time, so the user is presented with text boxes and a button to log onto the POP3 server.
- We are not in a postback and the request has parameters.
- In this case, the control has already received the message headers and the user has clicked on a hyperlink, so we check the parameter and react accordingly.
- We are not in a postback and the request does have parameters.
- The user has clicked on a hyperlink, but session has timed out, so the user must log in again.
If we are not in a postback and the incoming request does not contain a query string, we know that the page is being loaded for the first time. Therefore, InitState() returns to CreateChildControls(). This method is the heart of a composite control and, as its name implies, is used to construct the collection of child controls. The principle object of our control's UI is a DataGrid. In order to maintain a consistent UI we want the grid to appear whether or not we have retrieved messages. The code which populates the grid, therefore, has been factored out into a function, BuildGrid(bool bPopulated), so that the data binding logic can be accessed from anywhere in the code. This will be particularly useful if an exception occurs. We can still build an empty grid by calling this method from a catch block (see SAPOP3Control.GetMail()). So, when the page is first loaded, BuildGrid() is passed "false" and it constructs a DataView object with empty strings. CreateChildControls() then adds text boxes and a button to the UI so that the user can enter their credentials to log onto the POP3 server.
When the user clicks the "GetMail" button, a postback is initiated. The event handler for the button's click event is empty. The event is used only to fire a postback. We should now have request parameters, so GetMail() initializes the SAPOP3 object and calls its Start() method to get the user's messages and populate the data grid. Any exceptions populate the control's Error property and optionally displays a literal control with the error message.
The messages data grid contains two hyperlink columns, one to retrieve a message and one to delete a message. When either is clicked, a parameter is added to the new request("id= " or "delete="). This parameter(Page.Request.Params) is interpreted in InitState() and the appropriate method called (ShowMessage() or DeleteMessage()). An equivalent process exists for attachments. Each file attachment is represented in the UI as a BCL HyperLink object with the index of the requested file in its NavigateUrl string.
What does .NET really give us?
.NET Base Class Library
As any good .NET application should, SAPOP3 makes extensive use of the bountiful (I'm still try to avoid the adjective "rich") class library which constitute the framework. Socket communications, string handling, and data binding are only of few tasks made virtually trivial for the applications developer. SAPOP3 communicates with the POP3 server through System.Net.Sockets.Socket. Both synchonous(SAPOP3.MsgReceive()) and asynchronous(SAPOP3.Receive()) reads are made as a syntax exercise. All of the message parsing is done with very readable, intuitive code leveraging the power of System.String. Finally, generating the graphical elements of the controls was greatly simplified through ADO.NET classes, such as System.Web.UI.WebControls.DataGrid, System.Web.UI.WebControls.Table, and System.Data.DataView. The two greatest benefits which ADO.NET brought to this project are a common data binding model and flexible rendering. Once I figured out how to bind to one structure, I could bind to any available structure. Also, the DataGrid and Table controls are extremely customizable and (yes, I'll write it again) generate HTML without me having to write a single line of markup. By turning off the AutoGenerateColumns property and adding Web controls to the Columns collection of the ADO.NET class, I am able to build a graphical object with any number and types of columns.
ASP.NET offers enormous enhancements in UI programming and debugging. We have already seen the functionality available through the BCL Control class. Also, advanced features, such as ToolTips and ImageButtons, are available on Web controls by simple setting a property. ASP.NET composite controls also offer powerful run-time management of UI rendering. For example, after populating the Controls property, instead of relying on the base class's Render() method, a control could override this function, iterate over its collection, and selectively render child controls. While SAPOP3Control does not exploit this technique, I am sure that those more clever than I can put it to very creative use.
Huge improvements have also been made in debugging. The System.Web.TraceContext class, exposed through the Context property to the Control base class, makes monitoring code execution and variable values straightforward and unobtrusive, and thanks to xcopy deployment, dlls are no longer locked in memory. During a debug session, a control's code can be recompiled and the next request will JIT the new dll.
The component also illustrates several features which C# offers the component
developer. These include:
- debugging features, such as the ConditionalAttribute class
- mature error handling through exception handling
- language structures and keywords which precisely fit the .NET environment
Both SAPOP3 and SAPOP3Control take advantage of C#'s advanced conditional compilation features. By decorating the debugging method TRACE(string text) with the attribute "Conditional", the string parameter will be output to either the console or the ASP.NET TraceContext class, depending on preprocessor directives.
internal void TRACE(string text)
With the conditional attribute, if "DEBUG" is not defined, the method calls will not be included in the MSIL code. Also notice that the Visual Studio IDE color-codes conditional sections of code which will not be compiled.
Through the use of exception handling, code becomes more readable because error handling is consolidated. In SAPOP3Control, for example, log-in errors throw a custom exception which bubbles up to the highest level function, CreateChildControls(), where error handling code is organized into a single hierarchy of catch blocks.
. . .
. . .
It is important to remember that multiple catch blocks should always flow from most to least derived. Because a catch clause will match on an exception of a derived type and all .NET exceptions must inherit from System.Exception, the block "catch(Exception e)" will catch all exceptions and execution will never reach any subsequent catch blocks.
Because controls very often expose collections, C# makes control development a bit less painful with features like indexers and the "foreach" statement. SAPOP3 and SAPOP3Message both use indexers and explicit interface implementation (object IEnumerator.Current) to expose a type-safe IEnumerable interface.
Also, C#'s architecture of all classes deriving from Object, makes customizations like string dumping fit naturally into the object model. During debugging, I overrode SAPOP3Message's ToString() function to return the contents of all data members so that these values could be easily written to a file. Also notice that I use System.StringBuilder and its Append() method rather than System.String and its "+=" operator because we want to avoid the overhead of string concatenation which creates a new object on every call.
Language features and keywords new to C#, such as properties and the keywords "internal" and "sealed", make it a powerful tool for development in the .NET framework. SAPOP3 controls it public interface by declaring data members, such as the "m_bConnected" and "m_bHaveMail" flags, with "internal" protection, making them accessible to a containing class within the same assembly but not to external code. Also, C# properties and attributes further fine-tune control of member accessibility. Public properties are available to ASP.NET class declaration, making highly customizable controls easier to write and straightforward to consume. So properties of SAPOP3Controls, such as FontSize,
public int FontSize
m_fuFontSize = value;
can be set in the same statement as the class declaration in the Web form,
<SA:SAPOP3Control FontSize=10 UsesBorders=true Log=true runat="server" />
Also, the .NET "Broswable" attribute modify a property's behavior in the Visual Studio IDE. For instance, the Connected property is read-only, so we implement only the "get" accessor and, since we don't want a client to try to modify the value in the designer's property dialog box, we append the BrowsableAttribute class initialized to "false" which causes the property to be grayed out in the IDE.
public bool Connected
Finally, .NET's type unity makes passing user-defined types and constructs, such as byte arrays, between various components and languages as straightforward as passing primitive types. The SAPOP3 class, for example, returns a byte array through it Attachment property without the COM complexity of a SafeArray.
While the .NET framework exposes a very intuitive programming paradigm, it, like any new technology, includes characteristics that the new user might overlook. While writing this component I encountered a few details which should be passed along to the reader(read, "I did this, don't let it happen to you"). First, when developing Web components it is vital to fully understand the control execution lifecycle. An outline can be found in the SDK docs, ms-help://MS.MSDNVS/cpguide/html_hh2/cpconcontrolexecutionlifecycle.htm. For example, child controls should be added to the Controls collection in CreateChildControls(). Seemingly the method was given its name for a reason. The screen shot below is from a Web form with tracing turned on and traces put in CreateChildControls() and OnPreRender().
This trace was generated during a postback. You will see that CreateChildControls() is call in the ProcessPosData stage, while OnPreRender() is not called until the PreRender stage. Therefore, if child controls with event handlers are added in OnPreRender(), postback events have already been handled (in the PostBackEvent stage), so these controls' handlers will never be executed.
The SAPOP3Control class demonstrates the more straightforward paradigm for Web control development offered by the .NET framework. A few BCL Web controls are initialized and added to the Controls collection. Several enhancements could significantly increase the power and flexibility of the control. SAPOP3Control uses System.Web.UI.WebControls.DataGrid to demonstrate how easy it can be to render complex HTML using Web controls. A more extensible control, however, could be build around a templated class such as the DataList. Such a control would also make it easier to incorporate check boxes into the message grid. This would allow Hotmail-like batch delete event to minimize round trips. Also, using the DataView class's Sort property, the message grid could easily be made sortable. I plan to include these and other improvements, such as fuller design-time customization in a future version. Please write me with any other suggestions.
Go to the SoftArtisans POP3 product page.
Read about SoftArtisans .NET initiative.