TekGuard Online Guardian

 
Register  Login 
Dot

   HomeMail ServerOutlook PluginWeb MailUseful Links

Contact Us

 

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
TekGuard Email Interceptor System Architecture

Technologies

The following outlines the technologies used to produce TekGuard Email Interceptor Server:

  • Windows Services

  • TCP/IPSockets Programming

    • DNS Lookup

    • Asynchronous Sockets Programming

    • Asynchronous Client Sockets

    • Asynchronous Server Sockets

  • SMTP (Simple Mail Transfer Protocol) with .NET

    • Two Parts of an SMTP Server

    • SMTP Client

    • How to Send Mail via SMTP

  • POP (Post Office Protocol) with .NET

    • How to retrieve mail using POP3

    • POP3 Sockets Programming

    • MIME (Multipurpose Internet Mail Extensions)

  • Protocol State Machines

  • ADO (Active Data Objects) Access with .NET

    • Datasets

    • Adapters

    • Command Builder

    • Navigating Multiple Related Tables in an ADO.NET Dataset

    • Multiple Result Sets

    • Populating a DataSet from Multiple DataAdapters

    • Identity and Autonumber Values

  • ADO & Binding Context

    • CurrencyManager and Binding Context

    • .NET DataGrid Control

  • Threading

  • XML Usage

  • Windows "HTML Help" System

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 Internet’s Domain Name Servers (DNS’s) 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:

  • IAsyncResult ar = d.BeginReceive(null, null);

The method call is started asynchronously. We can now doing something else in the main thread at the same time:

  • …Continue processing…

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 user’s 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" state—and 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 source—whether it’s SQL Server, a text file, or a mainframe—and 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 details—such as how they deal with parameters—developers 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.

 

Copyright © 1996-2007 by VINFO, all rights reserved  Last modified: 08/02/2007   Terms Privacy Policy

wwwTekGuardCom - Anti-Spam .NET C# SMTP POP3 Mail Server Outlook PlugIn Search Engine Free Source Code