EMail Interceptor Outlook PlugIn Technologies
Architecture:
EMail Interceptor Outlook PlugIn is an Anti-Spam Microsoft .Net C# Outlook PlugIn.
Outlook PlugIn runs stand-alone, with EMail Interceptor Server, and even 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
Outlook PlugIn central source code.

TekGuard Email Interceptor System Architecture
Technologies
The following outlines the technologies used to produce TekGuard Email Interceptor
Outlook PlugIn:
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.
SOAP 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".
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 don't 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 don't need these features, but simply want to display your XML in a
DataGrid you don't have to worry at all. You can proceed in the same way as you do for
displaying your SQL data.
Email Interceptor 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.
Serialization
More information...
Outlook Application Events
More information...
Outlook COM Add-Ins
More information...
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.
|