EMail Interceptor Server Technologies
Architecture:
EMail Interceptor is an Anti-Spam Microsoft .Net C# EMail Server. EMail Interceptor
runs "out of the box", as well as provides an open architecture for developers.
The package includes source code for programmers that wish to customize or integrate our
product into their environment. This version includes the mail server central source code,
and source for the security component is available to licensed users.

TekGuard Email Interceptor System Architecture
Technologies
The following outlines the technologies used to produce TekGuard Email Interceptor
Server:
Windows Services
Email Interceptor can be run as an application or as a Windows Service. A service
application is designed to be long running. As such, it usually polls or monitors
something in the system. The monitoring is set up in the OnStart method. However, OnStart
does not actually do the monitoring. The OnStart method must return to the operating
system once the service's operation has begun. It must not loop forever or block. To set
up a simple polling mechanism, you can use the System.Timers.Timer component. In the
OnStart method, you would set parameters on the component, and then you would set the
Timer.Enabled property to true. The timer then raises events periodically, at which time
the service performs the monitoring.
The basic steps required to create and use a service include:
-
Create a project using the Windows Service application template. This template creates a
class for you that inherits from ServiceBase and writes much of the basic service
code, such as the code to start the service.
-
Write the code for the OnStart and OnStop procedures, and override any
other methods that you want to redefine.
-
Add the necessary installers for your service application. By default, a class
containing two or more installers is added to your application when you click the Add
Installer link: one to install the process, and one for each of the associated
services your project contains.
-
Build your project.
-
Create a setup project to install your service, and then install it.
-
Access the Windows 2000 Services Control Manager and start your service.
TCP/IPSockets Programming
This application provides an asynchronous socket implementation utilizing the
"System.NET.Sockets" namespace. It uses the "TCPListener" and
"TCPClient" classes from the "System.NET.Sockets" namespace.
The "Server" class uses the "TCPListener" class to accept requests
from new clients. Once the client is connected with the server, the server will send it
the appropriate commands, then disconnect and listen for more clients. The
"Client" class uses the "TCPClient" class. It connects to the server
on the specified port and send the clients name to the server upon connection.
EMail Interceptor demonstrates how to create both ends of a TCP/IP socket connection
between applications. COnnected applications may run on the same machine, on machines
connected by a local area network, or even machines communicating across the internet. A
key feature of this method is that it uses threads and sockets in a non-blocking mode. A
server is created that listens for clients to connect. Once a client connects it is added
to a list of active clients. These connections are maintained and tracked internally as
"Sessions".
DNS Lookup
The Internets Domain Name Servers (DNSs) are used to identify
human-readable domain names and map them to IP Addresses. A special type of record called
the MX record is used so that mail servers know where to contact the receiving mail
server. Often, the mail server is not at the same IP address as the domain name, which are
identified by a different record type (an A or CNAME type record).
When you use the .NET DNS class to lookup an IP address you get the domain name record
(an A or CNAME). There is nothing in the documentation that mention MX records or any
other type. In fact, the first line says "simple dns lookup" which would imply
it's not fully featured. This requires special programming in the C# code that finds if a
domain (XYZ.com) has a valid MX record, much like nslookup.
Asynchronous Sockets Programming
Once we have identified the location of the mail server we need to communicate with it.
There are many programs that communicate synchronously; however asynchronous
communications is necessary for any scalable or enterprise level application.
To make a communication method asynchronous we have to declare a delegate. Delegates
are similar to function pointers in C and C++. Since C# is "type safe", the
delegate must have the same signature and return type as the method that should be called
asynchronously.
We begin the process by calling BeginReceive() for the delegate. BeginReceive() has two
arguments in addition to the arguments that are declared with the delegate. With the first
argument, an AsyncCallback delegate can be passed to this method. With the asynchronous
callback, we can define a method with an IAsyncResult argument that will be called when
the method is finished. In this sample, we pass null with both arguments, and put the
returned reference to IAsyncResult into the variable ar:
The method call is started asynchronously. We can now doing something else in the main
thread at the same time:
As soon as we want to be sure that the asynchronous method completed (maybe we need
some result from this method), the reference to the IAsyncResult interface can be used.
With the IAsyncResult interface, we can check if the method is finished by checking the
IsCompleted property. We can wait until the method completes by using the AsyncWaitHandle
property and call WaitOne(). WaitOne() is a blocking call that waits until the method that
belongs to the IAsyncResult is finished:
Asynchronous Client Sockets
In asynchronous programming, the client is built with an asynchronous socket, so
execution of the client application is not suspended while the server returns a response.
The application sends a command to the server and then waits asynchronously for the
response returned by the server.
In order to establish a TCP connection with a server you need to call the connect
Winsock API function encapsulated by the C# sockets library. When the connect function is
called, the transport service on the client machine forms a network packet that contains a
lot of information such as IP addresses of the client and server computers, port number of
the client socket and the port number on the remote system the client is going to
establish the connection to.
The packet also contains a special flag bit that informs the server that this packet is
a request to establish a connection. When the server receives such a packet it looks for
the socket on the server machine which is bound to the port specified in the packet
received from the client. That socket must be in a special state that allows the socket to
accept such requests. This state is called listening. We can turn the socket into this
state using the Listen() function.
But let's go further. The server application that holds the listening socket can find
out that the socket has received a connection request using the Select function. If the
Select function determines the socket is readable, this means that the socket has got
a connection request. If the server application decides to accept that request it calls
the Accept() function which sends to the client the packet that contains a special bit
that informs the client that the request is accepted and the server is waiting for a
confirmation from the client. The client sends one more packet that tells the server that
the client is agree to go on, and then the server creates a new socket that will be
responsible for communication via the recently established connection.
Actually, the Accept() function does three things: sends a packet, receives a packet,
and creates a new socket. This new socket, created with the Accept() function, does have
the same parameters as the listening one except that it is not in a listening state.
Asynchronous Server Sockets
In asynchronous server socket programming, the server is built with an asynchronous
socket, so execution of the server application is not suspended while it waits for a
connection from a client. The application receives a string from the client, and forwards
the message to other software components. Mail protocols typically rely upon a string from
the client which contains an "<EOF>" to signal normal termination of the
message.
In server sockets programming, the sequence of events is reversed from those of client
programming. Otherwise, the functions are symmetrical, i.e., use BeginSend() rather than
BeginReceive(), specify a delegate, wait for data, etc.
SMTP (Simple Mail Transfer Protocol) with .NET
The killer technologies on the Internet are email and http web access. Email on the
Internet was developed using two simple Internet protocols, SMTP and POP3.
The SMTP or Simple Mail Transfer Protocol is described in RFC 821
[http://www.ietf.org/rfc/rfc0821.txt]. The.NET framework already contains an SMTP class in
the System.Web.Mail namespace called SmtpMail. But there annoying limitations. It has two
overloads for the Send() method. One method simply takes four strings the from, the
to, the subject and the body, but that's often inadequate for any useful mailing. Email
Interceptor needs multiple To recipients and CC, BCC recipients, asynchronous methods,
flow control, etc. The other overload takes a MailMessage object as parameter. That is a
useful overload of the method, but not very simple to use; it requires parameters like
BodyEncoding, BodyFormat etc.
Two Parts of an SMTP Server
An SMTP server is broken into two parts. The first part is the SMTP Receiver. This part
is the one that actually acts as a server. The Receiver is listens on port 25, and accepts
connections as soon as they arrive. When a connection (session) terminates successfully,
the Receiver will send the message to higher level software to check to verify the
recipients of the message. If there is a local recipient, the message will be placed in
that users mailbox. If not, it will be placed in the queue, leaving the forwarding
job to the second part.
The second part is the SMTP Sender. This is the part that acts as a client. The job of
the Sender is to get the files from the queue and send them to their final destination.
The Sender will connect to the Receiver of the remote network the network where the
recipient of the message is located and send the message. The Receiver of the
remote network will see that the message is for a local user and handle it appropriately.
SMTP Client
The SMTP client (SMTP Client) takes over after the SMTP server has done its job. The
role of the client is to send the mails that cannot be delivered locally, to the final
destination.
As with the Server, the Client also acts as a state machine. The FSM (finite state
machine) of the Client is similar to the one of the SMTP Receiver. However, the machine
state changes as a result of the reply codes. So the client sends the command, and based
on the reply code, it either moves to a new state or it stays on the same state, and sends
the correct command this time. IN SMTP programming, all reply codes that start with 4 or 5
are errors the client FSM does not change state when it gets those reply codes
(until timeout). The reply codes starting with 2 are successes. Upon success, the client
FSM should change state. There are exceptions, for example, if the command issued was the
HELP command, the server has not changed its state, so the client should not change state
either.
How to Send Mail via SMTP
Based on RFC 821 about Simple Mail Transfer Protocol, here's the handshaking for
sending mail:
-
Receiver: 220 server.com Simple Mail Transfer Service Ready
-
Sender: HELO server.com
-
Receiver: 250 server.com
-
Sender: MAIL FROM: <andrewm@tekguard.com>
-
Receiver: 250 OK
-
Sender: RCPT TO: <sales@vinfo.com>
-
Receiver: 250 OK
-
Sender: DATA
-
Receiver: 354 Start mail input: end with <CRLF>.<CRLF>
-
Sender: all data like From, To, Subject, Body etc. ended with
<CRLF>.<CRLF>
-
Receiver: 250 OK
-
Sender: QUIT
-
Receiver: 250 server.com closing transmission channel
POP (Post Office Protocol) with .NET
There is no POP3 class in the .NET framework, so an application such as Email
Interceptor needs to create its own.
A finite state machine also controls the POP3 server. When you retrieve lists of POP3
messages from a POP3 server, the list includes a message number and number of bytes. Use
the message number to retrieve the message content. This is implemented later to define
the List and Retrieve methods.
How to retrieve mail using POP3
Based on RFC 1725 about Post Office Protocol, it's easy to read mail via POP3. Here's
the handshaking for receiving mail:
-
Server: +OK Server POP Ready!!
-
Client: USER xxx
-
Server: +OK
-
Client: PASS yyy
-
Server: +OK user successfully logged on
-
Client: STAT
-
Server: +OK n m
-
Client: RETR 1
-
Server: +OK
---{data}-----
-
Client: QUIT
-
Server: +OK Server POP signing off
POP3 Sockets Programming
We begin by calling the Socket.Connect() method and passing the IP address and the 110
port. The 110 port number is the well known port number for POP3 operations. This means
that POP3 servers by default should listen for connections on port 110. When the POP3
server connects to a client, it should immediately respond with the +OK acknowledgement
message. Next we send two messages, USER and PASS, back to the server. The POP3 server
should acknowledge a successful login by acknowledging both messages. If the POP3 server
returns anything but +OK, then the message will contain the reason for the failure. In the
advent of a failure, I attach that failure message to our exception class and throw it
back to the client.
Note that some POP3 servers don't require authentication and may reject the calls to
USER and PASS. These are rare, but the protocol allows for it.
To retrieve a message from a POP3 server, we send a RETR message with the unique
message number. The server then responds with the +OK acknowledgement, the message content
and finally the single period terminating line.
Retrieving a message does not remove the message from the POP3 server. A further call
to LIST will still return the message. To remove a message from POP3 server, call the
Delete method.
MIME (Multipurpose Internet Mail Extensions)
Typically messages sent and retrieved by a POP3 Server are "MIME Encoded".
This means that they are stored in an agreeded upon format so that information other than
plain text can be handled appropriately.
The basic idea of "multimedia" electronic mail is to extend email to include
many other types of data beyond plain English text. Email may include text in any of the
world's languages and character sets, pictures, sounds, animations, active spreadsheets,
or any other kind of data that can be stored on a computer. The MIME standard for the
format of multimedia mail allows software from different vendors to work together smoothly
with multimedia mail. The key is a mechanism that allows the easy configuration of mail
readers to call external "viewers" for different types of mail. Examples
include:
-
Plain US ASCII (i.e., English) text.
-
Rich text
-
Image formats
-
Audio
-
Multipart mail, combining several other types
-
Multipart/alternative mail, offering data in multiple formats.
-
Encapsulated messages
-
Arbitrary (untyped) binary data
Other media types and character sets may be easily supported with this mechanism, using
the provided types as examples/templates.
Protocol State Machines
State charts are the most natural, & intuitive way to graphically represent
complex, reactive systems. State charts are a standard in the automotive, aerospace,
telecoms and semiconductor industries for the intuitive modeling of complex, event based
systems. State charts are a standard in modern Object Oriented Programming methodologies
and provide an excellent means of observing the behavior of state machine designs.
Basically, what the above state machine means is the following:
-
After connecting to the server, the client must send the HELO command to move the server
to the HELO state. Any other command will keep the server on the Initial state
-
After HELO, the client issues the MAIL command that specifies the sender of the message,
to move the serve to the MAIL state. Any other command will leave it to the HELO state.
-
After the MAIL command, the client will issue the RCPT command, specifying the recipient
of the message in order to move the server to the RCPT state. Any other command will keep
the server at the MAIL state.
-
After RCPT, the client sends the DATA command and moves the server to the DATA state,
any other command will keep the server on RCPT state.
-
The DATA state is different from the rest. In the DATA state, the client sends the
actual message, including the headers, subject and body of the message. The message
terminates with a pound (".") ona line by itself (that is, "\n.\n", or
"\r\n.\r\n"). When the client sends the special termination character, the
server puts the message on the queue, and gets back on the HELO state.
-
At any state, besides the DATA and Initial states, a RCPT command will put the server
back to the HELO state.
-
At any state, besides the DATA state, the QUIT command will terminate the session, thus
moving the server to the "accepting" stateand terminating the connection.
-
The HELP command will never move the server to a new state.
-
The server should terminate the connection ONLY after it has received a QUIT command.
The server must also reply to a QUIT command.
Email Interceptor implements protocols as a finite state machine (FSM). Using stateful
objects is a more powerful way of implementing this particular algorithm. Email
Interceptor defines the tables using XML. The format is very simple and defines state and
transition elements.
ADO (Active Data Objects) Access with .NET
ADO.NET is an evolution of the ADO data access model that directly addresses user
requirements for developing scalable applications. It was designed specifically for the
web with scalability, statelessness, and XML in mind.
ADO.NET uses some ADO objects, such as the Connection and Command objects, and also
introduces new objects. Key new ADO.NET objects include the DataSet, DataReader, and
DataAdapter.
The important distinction between this evolved stage of ADO.NET and previous data
architectures is that there exists an object the DataSet that is separate
and distinct from any data stores. Because of that, the DataSet functions as a standalone
entity. The DataSet is like an always disconnected recordset that knows nothing about the
source or destination of the data it contains. Inside a DataSet, much like in a database,
there are tables, columns, relationships, constraints, views, and so on.
A DataAdapter is the object that connects to the database to fill the DataSet. Later,
the DataAdapter connects back to the database for updates, based on operations performed
while the DataSet held the data. In the past, data processing has been primarily
connection-based. Now, in an effort to make multi-tiered apps more efficient, data
processing is turning to a message-based approach that revolves around chunks of
information. At the center of this approach is the DataAdapter, which provides a bridge to
retrieve and save data between a DataSet and its source data store. It accomplishes this
by means of requests to the appropriate SQL commands made against the data store.
The disconnected nature of the DataSet makes life for developers more complex, but it
greatly improves its versatility. Developers can now fill a DataTable with data taken from
any data sourcewhether its SQL Server, a text file, or a mainframeand
process it with the same routines, regardless of its origin. The decoupled architecture
based on the DataSet and the DataAdapter makes it possible to read data from one source
and send updates to another source, should it be necessary. Developers have a lot more
freedom when working with ADO.NET but also many more responsibilities.
Datasets
The most common way access method is through the DataSet, which is the primary object
in ADO.NET. The DataSet object can be regarded as a virtual database, which will reside in
the client memory. Unlike an ADO RecordSet, DataSet object provides disconnected access to
underlying data source, i.e. any modification or insertion of data will not be made to
underlying data source until we explicitly call the DataSetCommand object's Update method,
thus improving scalability of the data source.
The DataSet represents a complete set of data including tables, constraints, and
relationships among the tables. Because the DataSet is independent of the data source, a
DataSet can include data local to the application, as well as data from multiple data
sources. Interaction with existing data sources is controlled through the DataAdapter.
The XML-based DataSet object provides a consistent programming model that works with
all models of data storage: flat, relational, and hierarchical. It does this by having no
'knowledge' of the source of its data, and by representing the data that it holds as
collections and data types. No matter what the source of the data within the DataSet is,
it is manipulated through the same set of standard APIs exposed through the DataSet and
its subordinate objects.
DataSets, in turn, will talk to a data source through Managed Providers, using a
DataSetCommand. Each Managed provider will effectively implement the methods of
DataSetCommand, Command, Connection, DataReader etc, appropriate to their data source. In
case of changing the data source, developers don't need to alter the program, just the
appropriate connection string.
The DataSetCommand is a mediator between DataSet and Data Source. This object can be
used to fill the DataSet with the appropriate tables and other stuff such as relations,
constraints, etc. Once a DataSet is filled with tables, it can be accessed freely with
DataSet object hierarchy.
Adapters
While the DataSet has no knowledge of the source of its data, the managed provider has
detailed and specific information. The role of the managed provider is to connect, fill,
and persist the DataSet to and from data stores. The OLE DB and SQL Server .NET Data
Providers (System.Data.OleDb and System.Data.SqlClient) are part of the .NET Framework and
provide four basic objects: the Command, Connection, DataReader and DataAdapter.
ADO.NET provides disconnected access to any data source for which a Managed Provider
exists. A Managed Provider is nothing but set of objects that talks to a data source
directly. Each RDBMS vendors will provide appropriate Managed Providers.
For now, Microsoft has provided two Managed Providers:
-
SQL Managed Provider Provides access to SQL Server version 7 or later.
-
ADO Managed Provider Provides access to any data source through OLEDB Providers.
This is for compatibility with old data source for which no Managed Providers exists.
Each .NET data provider included with the .NET Framework has a DataAdapter object: the
OLE DB .NET Data Provider includes an OleDbDataAdapter object, and the SQL Server .NET
Data Provider includes a SqlDataAdapter object. All DataProvider objects expose the same
set of properties and methods because they inherit from the DbDataAdapter abstract class.
All the .NET data providers that are to be released in the future will include their own
DataAdapter because the DataAdapter must know how to read from and update a specific data
source. Except for their names and a few other detailssuch as how they deal with
parametersdevelopers can use the OleDbDataAdapter and the SqlDataAdapter in exactly
the same way.
The DataAdapter also resolves changes made to the DataSet back to the data source. The
DataAdapter uses the Connection object of the .NET data provider to connect to a data
source, and Command objects to retrieve data from and resolve changes to the data source.
The SelectCommand property of the DataAdapter is a Command object that retrieves data
from the data source. The InsertCommand, UpdateCommand, and DeleteCommand properties of
the DataAdapter are Command objects that manage updates to the data in the data source
according to modifications made to the data in the DataSet. These properties are covered
in more detail in later
in this document.
The Fill method of the DataAdapter is used to populate a DataSet with the results of
the SelectCommand of the DataAdapter. Fill takes as its arguments a DataSet to be
populated, and a DataTable object, or the name of the DataTable to be filled with the rows
returned from the SelectCommand.
The Fill method uses the DataReader object to implicitly return the column names and
types used to create the tables in the DataSet, as well as the data to populate the rows
of the tables in the DataSet. Tables and columns are only created if they do not already
exist; otherwise Fill uses the existing DataSet schema. Column types are created as .NET
Framework types according to the tables in Mapping .NET Data Provider Data Types to .NET
Framework Data Types. Primary keys are not created unless they exist in the data source
and DataAdapter.MissingSchemaAction is set to MissingSchemaAction.AddWithKey. If Fill
finds that a primary key exists for a table, it will overwrite data in the DataSet with
data from the data source for rows where the primary key column values match those of the
row returned from the data source. If no primary key is found, the data is appended to the
tables in the DataSet. Fill uses any TableMappings that may exist when populating the
DataSet (see Setting Up DataTable and DataColumn Mappings).
Command Builder
The DataAdapter has four properties that are used to retrieve data from and update data
to the data source. The SelectCommand property returns data from the data source. The
InsertCommand, UpdateCommand, and DeleteCommand properties are used to manage changes at
the data source. The SelectCommand property must be set before calling the Fill method of
the DataAdapter. The InsertCommand, UpdateCommand, or DeleteCommand properties must be set
before the Update method of the DataAdapter is called, depending on what changes were made
to the data in the DataSet. For example, if rows have been added, the InsertCommand must
be set before calling Update. When Update is processing an inserted, updated, or deleted
row, the DataAdapter uses the appropriate Command property to process the action. Current
information about the modified row is passed to the Command object through the Parameters
collection.
For cases where the SelectCommand is dynamically specified at runtime, such as through
a query tool that takes a textual command from the user, it may not be possible to specify
the appropriate InsertCommand, UpdateCommand, or DeleteCommand at design time. If the
DataTable maps to or is generated from a single database table, you can take advantage of
the CommandBuilder object to automatically generate the DeleteCommand, InsertCommand, and
UpdateCommand of the DataAdapter.
As a minimum requirement, you must set the SelectCommand property in order for
automatic command generation to work. The table schema retrieved by the SelectCommand
determines the syntax of the automatically generated INSERT, UPDATE, and DELETE
statements.
The CommandBuilder must execute the SelectCommand in order to return the metadata
necessary to construct the insert, update, and delete commands. As a result, an extra trip
to the data source is necessary which can hinder performance. Optimal performance is
achieved when commands are specified explicitly rather than using the CommandBuilder. The
SelectCommand must also return at least one primary key or unique column. If none are
present, an InvalidOperation exception is generated, and the commands are not generated.
When associated with a DataAdapter, the CommandBuilder automatically generates the
InsertCommand, UpdateCommand, and DeleteCommand properties of the DataAdapter if they are
null references. If a Command already exists for a property, the existing Command is used.
Database views that are created by joining two or more tables together are not
considered a single database table. In this instance you will not be able to use the
CommandBuilder to automatically generate commands and will need to specify your commands
explicitly.
You might want to map output parameters back to the updated row of a DataSet. One
common task would be retrieving the value of an automatically generated identity field
(SQL Identity or Access Autonumber values) or time stamp from the data source. The
CommandBuilder will not map output parameters to columns in an updated row by default. In
this instance you will need to specify your command explicitly.
The automatic command generation logic generates INSERT, UPDATE, or DELETE statements
for standalone tables without taking into account any relationships to other tables at the
data source. As a result you may encounter a failure when calling Update to submit changes
for a column that participates in a foreign key constraint in the database. To avoid this
exception, do not use the CommandBuilder for updating columns involved in a foreign key
constraint and instead, explicitly specify the statements used to perform the operation.
Navigating Multiple Related Tables in an ADO.NET Dataset
A dataset in ADO.NET is an in-memory representation of data that can contain multiple
related data tables. Therefore it is often necessary to navigate these related data tables
within a dataset. A typical .NET Windows application returns related records based on a
selected record, as well as compiles aggregate information for related records using
expression columns.
Because datasets can contain several related tables, understanding how to navigate from
parent to child records and back is a fundamental task that may not be intuitive
especially in situations where you are trying to access data that is several tables deep
in the relational hierarchy.
Here is a basic explanation of how to access data between two tables in a dataset that
participate in a one-to-many relationship. After you select a data row, you can return its
related records by calling the GetChildRows or GetParentRow method and passing it the
appropriate data relation. Note that the GetChildRows method will return data in an array
of DataRow objects, whereas the GetParentRow method will return a single data row.
An example would be adding code to an application that returns all the orders (child
rows) for the customer selected in the combo box. Changing the selected customer in the
combo box raises the ComboBox.SelectedIndexChanged event and fills the list box with the
OrderID of each order for the selected customer. Then call the GetChildRows method, based
on the customer selected in the combo box. All of the related records from the Orders
table are assigned to the array of data rows returned by the function call.
Multiple Result Sets
If the DataAdapter encounters multiple result sets, it will create multiple tables in
the DataSet. The tables will be given an incremental default name of TableN, starting with
"Table" for Table0. If a table name is passed as an argument to the Fill method,
the tables will be given an incremental default name of TableNameN, starting with
"TableName" for TableName0.
Populating a DataSet from Multiple DataAdapters
Any number of DataAdapters can be used in conjunction with a DataSet. Each DataAdapter
can be used to fill one or more DataTable objects and resolve updates back to the relevant
data source. DataRelation and Constraint objects can be added to the DataSet locally,
enabling you to relate data from multiple dissimilar data sources. For example, a DataSet
can contain data from a Microsoft SQL Server database, an IBM DB2 database exposed via OLE
DB, and a data source that streams XML. One or more DataAdapter objects can handle
communication to each data source.
Identity and Autonumber Values
You can set a column in a DataTable to be an auto-incrementing primary key in order to
ensure a unique value for each row in the table. However, you may have multiple clients
for your application, and each of those clients may be working with a separate instance of
the DataTable. In this case, you might end up with duplicate values between the separate
instances of the DataTable. Because all your clients will be working with a single data
source, you can resolve this conflict by letting the data source define the
auto-incremented value. To accomplish this you use Identity fields in Microsoft SQL
Server, or Autonumber fields in Microsoft Access.
Using the data source to populate an Identity or Autonumber column for a new row added
to a DataSet creates a unique situation because the DataSet has no direct connection to
the data source. As a result, the DataSet is unaware of any values generated automatically
by the data source. However, with a data source that can create stored procedures with
output parameters, such as Microsoft SQL Server, you can specify the automatically
generated values, such as a new identity value, as an output parameter and use the
DataAdapter to map that value back to the column in the DataSet.
Your data source may not support stored procedures with output parameters. In this case
you may be able to use the RowUpdated event to retrieve an automatically generated value
and place it in the inserted or updated row in the DataSet. This section includes a sample
that shows how, with Microsoft Access 2000 or later, and using the Jet 4.0 OLE DB
Provider, you can add code to the RowUpdated event to determine if an insert has occurred
and to retrieve the auto-incremented value and store it in the currently updated row.
ADO & Binding Context
CurrencyManager and BindingContext
The ADO datasource does not have a position (unlike ADO or Delphi), which requires the
creation of a distinct object which keeps track of the position and gets the data from the
datasource and manages the updates to and from a control.
Since the data binding infrastructure (databinding) creates CurrencyManagers
automatically there has to be a mechanism which makes it possible that every control bound
to the same datasource (table) is synchronized. So there is a BindingContext object which
keeps tracks of the CurrencyManagers and gives the same CurrencyManager to the controls
bound to the same datasource. Of course the controls have to get the same BindingContext
to get the same CurrencyManager.
.NET DataGrid Control
It is possible to extend the DataGridTextBoxColumn control so that you can
custom-format your data for display and editing in a Windows Form DataGrid.
The DataGrid control is used to display data from the datasource defined by its
DataSource property. There are tons of features provided by DataGrid in displaying data.
Sorting each column when clicked on the name of the column in the DataGrid is one of the
best features. We will concentrate on sorting XML.
Displaying XML data using DataGrid and achieving the same tasks is a little bit
different. The reason is, as such an XML file when loaded by a DataGrid will only display
the XML content as "text". On the other hand, data from a database table will
have all the constraints (like what type of data int, float, double,...) embedded
into the DataGrid instance. So you dont have to emphasize in your code that "Col1 of
my data is of type integer(and not text)".
But if you display XML you have to specify somehow or other that what type of data each
of your column contains. Otherwise, all values are considered as type "text".
Remember, you have to do this only if you want to get certain type-specific features of
DataGrid. If you dont need these features, but simply want to display your XML in a
DataGrid you dont have to worry at all. You can proceed in the same way as you do for
displaying your SQL data.
Email Interceptror demonstrates how to add three "dots" to IP addresses and
how to parse entries that have three "dots" inserted within them. This technique
can be extended to any data type, including enumerations and user-defined types. To
display data, you override the GetColumnValueAtRow method. To parse data, you override the
Commit method.
Threading
EMail Interceptor uses thread pooling to make efficient use of multiple threads. Many
applications use multiple threads, but often those threads spend a great deal of time in
the sleeping state waiting for an event to occur. Other threads might enter a sleeping
state and be awakened only periodically to poll for a change or update status information
before going to sleep again. Using thread pooling provides your application with a pool of
worker threads that are managed by the system, allowing you to concentrate on application
tasks rather than thread management. In fact, if you have a number of short tasks that
require more than one thread, using the ThreadPool class is the easiest and best way to
take advantage of multiple threads. Using a thread pool enables the system to optimize
this for better throughput not only for this process but also with respect to other
processes on the computer, something your application will know nothing about. Using a
thread pool enables the system to optimize thread timeslices taking into account all the
current processes on your computer.
The .NET Framework uses thread pools for several purposes: asynchronous calls,
System.NET socket connections, asynchronous I/O completion, and timers and registered wait
operations, among others.
XML Usage
XML is rapidly becoming the de facto standard for data exchange over the Internet and
across multiple disparate platforms. The ability to populate database tables with data
contained in XML documents is a common application requirement.
The Microsoft .NET XML Designer allows you to easily create schemas and datasets based
on any table, stored procedure, or view that can be browsed in the data connection area of
Server Explorer. Additionally, you can pick and choose individual columns if the entire
data structure is not needed.
You create a schema based on existing data sources when the existing source contains
the desired structure for your application. This is a quick way to build up your schema
without having to define each individual element.
Schemas and datasets can also be created programmatically from any data source that you
have access to at run time.
HTML Help
There has been an industry move to HTML-based Help files. This change, initiated by
Microsoft, makes possible a wide range of new and better help systems.
The primary benefit of this new system is that it is truly "write once, use
everywhere." Once an HTML Help file is built, which is actually a compiled program,
it can be mounted on disk, on the Internet, or on an intranet. The same application can be
launched from these platforms without any need for modification or intervention on your
part.
Another benefit of HTML Help is simplicity. Previous flavors of WinHelp used RTF (Rich
Text Format) files, identified by their .rtf extension. RTF files could be cantankerous,
with their many curly brace delimiters, etc. It is much easier to edit an HTML file than
an RTF file. Furthermore, Microsoft has announced that it will continue to support
RTF-based Help files, and the HTML Help compiler/development kit even allows you to port
existing RTF-based systems to the new standard. The Help files built using HTML are much
more robust than RTF-based files. You can easily drop in nice little goodies like
multimedia Java applets that add spit and polish to even the most prosaic help file.
Finally, one of the biggest benefits afforded by this system is the way it augments
information searches. You can designate topics as members of a given type; this is similar
to the concept of a set in mathematics. As an example, you can tag topics as being
conceptual, tutorial, procedural, or any other category you care to imagine. Suppose
you're writing a Help file for two closely related, but slightly different, products. By
using these topic tags, you could write one Help file for both products. When the user
sought help for one of the products, only those topics would appear. Partitioning in this
manner makes for a much easier maintenance cycle too.
There are drawbacks to HTML Help, depending on your perspective. First, you must have
Microsoft's Internet Explorer web browser installed on the target platform. Microsoft has
a run-time product for non-IE users which eliminates this requirement. Second, every topic
requires its own HTML file, which can make project maintenance somewhat of a pain. Third,
the new standard supports popup windows (which aren't as elegant as those in the old
WinHelp system) and they are somewhat difficult to implement. Last, the help author must
know at least the basics of HTML, which probably most developers know already.
|