Provider Based Services
(by John Roberts for Blayd Software)
Note: this article relates to the .Net Framework version 2.0, some
of the classes and class members referenced in this article were not available in
earlier framework versions.
Introduction
The Provider Design Pattern allows developers to define a documented and (hopefully)
simple API to supply a specific type of service to clients without providing the
actual API implementation. A base provider defines the generic API that a client
uses to interact with the service but the API implementation is supplied by a specific
type of provider. For example, the MembershipProvider type in ASP.Net defines a
membership API but the (derived) ActiveDirectoryMembershipProvider and SqlMembershipProvider
types supply most of the API implementation (the base class may provide some common
functionality). Therefore, in this example there is a common membership API defined
in the base provider but the actual functionality is provided by derived provider
types which allow either Active Directory or SQL Server to be used as the backing
store for membership data. In addition to the two built-in membership providers,
developers can create their own, storage specific, provider and "plug" it into the
ASP.Net membership system.
The Provider Design Pattern as implemented in the .Net Framework dictates certain
"rules" for implementing a provider. A provider implementation must derive from
an abstract base class in which the API for a specific service or feature is defined.
For example, the SqlMembershipProvider class derives from the service abstract base
class MembershipProvider. The MembershipProvider service base class derives from
the ProviderBase abstract base class. The ProviderBase abstract base class is used,
by all providers, to define an API that is common to all providers. Developers wanting
to add a new provider type to a defined service should derive their new provider
class from the service or feature base class rather than derive directly from ProviderBase.
In the membership example being used here, for example, a new provider should derive
from the MembershipProvider abstract base class. Developers creating a new, custom,
service should create their own feature specific base class by deriving directly
from the ProviderBase abstract base class and then create specific implementations
of the custom service by deriving the relevant types from the feature specific base
class.
With the notable exception of the configuration system the Provider Design Pattern
is implemented primarily and fairly extensively in ASP.Net, however, there is nothing
to prevent developers using the pattern in their Windows Forms systems. In this
article we are going to demonstrate the use of the Provider Design Pattern in a
fairly typical line of business system architecture. We will also demonstrate how
the pattern can be used to develop a loosely coupled system that is inherently flexible
and easy to extend.
Sample Code Requirements Description
When creating sample code to demonstrate the techniques being discussed in an article
there is always a dilemma of where to draw the line. The sample code needs to demonstrate
the relevant functionality but ideally, the functionality should not be obscured
by extraneous support code. The primary objective should be to keep the sample code
simple and clear, however, the danger of this approach is that the sample code does
not represent a "real" system and whilst it may demonstrate the techniques being
discussed, it gives little or no indication of how to apply the techniques or of
how much work is required to include the techniques in a "real" system.
We decided that to demonstrate the techniques required to implement provider based
services in a realistic way in a Windows Forms based system we needed to develop
a working system. Inevitably that means that the sample code does contain quite
a bit of code that does not relate directly to the implementation of providers but
the code does demonstrate the potential benefits of implementing provider based
services in the "real" world of line of business systems development. The sample
code represents a line of business system for a fictitious direct mail company.
The company produces printed letters, brochures, leaflets, etc. for a variety of
customers and mails the output to the specified recipients. A brief description
of the requirements for the system is given below.
- The Company:
-
has a wide variety of customers and produces a wide variety of mailings. Some customers
have only a single mailing, others have several different mailings. The company
creates a template for each, customer specific, mailing and merges the variable
data e.g. name, address, salutation, etc. that is supplied by the customer with
the template to produce the required, printer specific, output. The output is then
collated and inserted into the specified envelopes and mailed to the recipients.
- The System:
-
is required to process the data supplied by a customer and merge it with the relevant
mailing template to produce the required output. Customers supply the data required
to produce their mailing(s) mainly as flat files e.g. delimited or fixed width text
files, however other formats, such as XML for example, may be used. It is company
policy not to constrain customers to specific, supported, data formats. The customer
shall be able to supply their data in a format that best suits them, although the
company may provide guidance and specify preferred formats. Customers may also use
different data formats for different mailings.
The system must be able to validate
and confirm the correctness of the data supplied by a customer as an integral part
of the process of producing the output. The company shall imposes certain validation
rules, such as the presence of a salutation value, for example but the customer
may specify their own validation rules for each of their mailings. The system must
be able to accommodate new customer validation rules as seamlessly as possible,
ideally without requiring any development work and certainly without requiring a
complete system recompile and redeployment.
The system must be able to accommodate
new customers and new data formats as seamlessly as possible, ideally without requiring
any development work. It is accepted that some development work may be required
to integrate new mailing templates but this work should be confined to a single
area of the system and should not require a complete system recompile and redeployment.
The sample code that accompanies this article represents an implementation of the
requirements outlined above and will be referred to as The Mailshot System in this
article from now on. The code is not a complete implementation, there is only a
single mailing data source provider, for example but it does demonstrate how the
requirements may be delivered and could certainly be used as a proof of concept
for such as system. We will show as much of the source code as we have space for,
however, we will not able to show it all. You can download the full source code
for this article using the link provided in the "Article Options" on the left of
the page. The source code shown in this article will be in C# but the download sample
code is available as either a C# or a VB Visual Studio 2005 solution. Both the C#
and VB solutions should convert to Visual Studio 2008 without any problems.
Component Dependencies
The benefits of component based development have been widely acknowledged and most
of us have been using the principles in our work for quite a while. The architectures
illustrated below should be familiar to most line of business system designers and
developers.
The figures above illustrate package or when specifically applied to .Net, assembly
dependencies. For example, in both figures the client application assembly (A) requires
a reference to the component assembly (B) because it makes use of at least one type
defined in assembly (B).
Figure 1 illustrates a system in which a client application (A) depends on a class
library assembly (B) which in turn depends on a class library assembly (C). If assembly
(C) is, for example, a utility library and assembly (B) does not publicly expose
any of the types defined in assembly (C) then the client assembly (A) does not have
a, direct, dependency on assembly (C). However, if assembly (B) does publicly expose
at least one type defined in assembly (C), then the client assembly (A) also has
a direct dependency on assembly (C) as illustrated in Figure 2.
This architecture was in use well before the dawn of the .Net Framework and systems
are still being designed today using this simple and effective technique. Breaking
a system down into individual modules of components and/or controls has many benefits,
however it also has one or two major drawbacks for certain types of system. Figure
2 illustrates a fairly typical tightly coupled system, Figure 1 is less typical
but is still tightly coupled because of the dependencies. The dependencies illustrated
above can have a significant impact when changes are made to the system and when
the system needs to be extended to accommodate new functionality. If we assume that
assemblies (B) and (C) illustrated above both have a strong name, as they should,
then changing assembly (C), incrementing its version number and recompiling it will
mean that both assembly (B) and the client assembly (A) will have to be recompiled
to enable them to make use of the new version of assembly (C). Interestingly, this
applies to both Figure 1 and Figure 2 because even though assembly (A) in Figure
1 does not have a direct dependency on assembly (C), assembly (B) does and recompiling
assembly (B) will require a recompile of the client assembly (A).
In systems as simple as the examples used above configuration management and system
deployment are unlikely to be much of an issue. However, most line of business systems
are considerably more complex than these examples, they may be built from many different
components and some of those components will depend on other components. Line of
business systems are also likely to be subjected to considerable pressure for change
and they may well also have the odd bug or two that needs to be fixed! Businesses
evolve constantly and the systems that they use to support their activities also
have to evolve. The maintenance of a typical component based line of business system
generates configuration management and system deployment activities that can swamp
a development team or systems department.
The Provider Design Pattern, which encourages a component based design, provides
system designers and developers with the opportunity to take a different approach
to component dependencies.
Figure 3 illustrates a system in which a client application (A) depends on a core
class library assembly (B). Two "satellite" assemblies (C) and (D) also depend on
the core assembly (B). In this, loosely coupled, system the client assembly (A)
and the core assembly (B) have no knowledge of the two satellite assemblies (C)
and (D) and therefore the assemblies (C) and (D) can be changed without requiring
a recompile of assemblies (B) or (A). New satellite assemblies may be added to the
system and existing satellite assemblies may be removed from the system without
affecting the core assembly (B) or the client assembly (A).
This architecture depends heavily on the core assembly (B) which defines abstract
base classes, when implementing the Provider Design Pattern or interfaces to define
an API "contract" between the client assembly (A) and the satellite assemblies (C)
and (D). The core assembly (B) defines the API and the satellite assemblies (C)
and (D) provide different implementations of the API. The core assembly (B) also
provides a "service" or "factory" class which the client assembly (A) uses to retrieve
a type instance from a satellite assembly. The satellite assembly instance is returned
to the client assembly (A) as a core assembly (B) base class type. The method used
by the "service" class to create an instance of the requested type is implementation
specific but when implementing the Provider Design Pattern configuration data is
used to instantiate the requested type and the type is requested by specifying the
"friendly" name defined in the configuration data.
When implementing a system based on this architecture it is essential to get the
design of the base classes in the core assembly (B) right. The architecture does
allow for new satellite assemblies to be added without a recompile but a type defined
in a new satellite assembly must derive from a base type that already exists in
the core assembly (B) to enable the client assembly (A) to make use of it. If the
core assembly (B) has to be extended and recompiled to accommodate new functionality
then every other assembly in the system will need to be recompiled and redeployed
and this is likely to involve more work than the systems illustrated in Figures
1 and 2.
Whether the core assembly should define abstract base classes or interfaces is open
to debate, we tend to think that interfaces make more sense, however, the Provider
Design Pattern which is the subject of this articles requires abstract base classes.
When using base classes, care has to be taken when deciding on the functionality
that is to be implemented in a base class, altering a base class in the core assembly
(B) to fix a problem should be avoided where possible (to avoid the problems outlined
in the preceding paragraph). Conversely, if the abstract base classes have little
or no functionality to avoid possible "flaky base class" problems then using interfaces,
rather than base classes, starts to make more sense!
Provider Base Classes
When implementing the Provider Design Pattern the feature specific provider class
should derive from the System.Configuration.Provider.ProviderBase abstract base
class. The ProviderBase class has two, virtual, properties Description and Name
which are, typically, set directly from the values specified in an application configuration
file but can be set by a derived class, although this involves altering or creating
configuration settings before the values are passed to the base class. The Description
property is optional but the Name property value is required and will result in
an exception in ProviderBase if it missing or empty. ProviderBase has a single,
virtual, method Initialize which takes the provider's "friendly" name, which is
used to set the Name property and the provider's configuration settings, represented
by a NameValueCollection, as parameters. Derived classes will, typically, override
this method to extract their feature specific configuration settings before calling
the ProviderBase implementation.
When using the .Net Framework's built-in providers the relevant .Net Framework sub-system
will instantiate each, concrete, provider defined in an application's configuration
file and call its Initialize method. For example, when using protected configuration,
the .Net Framework's configuration sub-system will instantiate, concrete, ProtectedConfigurationProvider
derived provider instances and call their Initialize method. When we create our
own providers, the .Net Framework knows nothing about them and can't therefore load
and initialize them, so we have to write our own code to load and initialize our
custom providers. We will be demonstrating how to do this later in the article but
the important point to note for now is that the concrete provider's Initialize method
must be called before the provider is called from client code.
The ProviderBase class Initialize method implementation can only be called once
and will throw an exception if it is called for a second time. Therefore, assuming
that a derived concrete provider class calls the base class Initialize method implementation,
as it should, there can only be a single instance of each provider defined in the
configuration file. It is important to understand what is meant by a provider instance
to fully appreciate the single instance constraint applied by the ProviderBase Initialize
method. Provider instances, as specified in a configuration file, are differentiated
by their "friendly" name not their Type. In the sample code that accompanies this
article, there is a single concrete provider class DelimitedFileMailshotProvider,
however there are two instances of this provider Type specified in the client application's
configuration file with the friendly names "CsvGenericProvider" and "TestCustomerProvider".
When our code loads and initializes these two provider instances the ProviderBase
Initialize method implementation allows only a single instance of each one, therefore
there will only be a single instance of the DelimitedFileMailshotProvider class
named "CsvGenericProvider" and a single instance of the DelimitedFileMailshotProvider
class named "TestCustomerProvider".
The single instance of each named provider constraint has other implications when
designing and developing providers. Providers must be thread-safe because there
is only one instance of each named provider and there is no way of knowing if the
client code will be calling the provider from a single thread or from multiple threads
concurrently. If a provider performs multiple operations, such as multiple data
source updates, in a single call then it should use transactions to ensure the atomicity
of updates. For example, the provider should be capable of rolling back updates
that have already completed if a subsequent update fails.
The Mailshot System sample code that we are using to demonstrate provider based
services in this article has the following ProviderBase derived abstract base classes
defined in a project/assembly named MailshotCore.
The Mailshot System sample code solution contains three projects, the MailshotCore
project contains the abstract base classes and base classes that define the mailing
data provider API, the FileProviders (satellite dll) project contains the concrete
provider classes used to process file based mailing data and the MailshotDemo project
represents the client application that consumes the mailing data and processes it
to produce the required output.

MailshotProvider abstract base class:
public abstract class MailshotProvider : ProviderBase
{
private string _validatorName;
private string _validatorDescription;
private string _validatorType;
private string _definitionDir;
private string _definitionFile;
protected MailshotProvider() : base() { }
public string DefinitionDirectory
{
get { return _definitionDir; }
set { _definitionDir = (value == null) ? string.Empty : value; }
}
public string DefinitionFile
{
get { return _definitionFile; }
set { _definitionFile = (value == null) ? string.Empty : value; }
}
public string ValidatorName
{
get { return _validatorName; }
set { _validatorName = (value == null) ? string.Empty : value;}
}
protected string GetDefinitionFilePath(string definitionFile)
{
// Implementation elided...
}
protected static void SetApplicationRootDirectory()
{
// Implementation elided...
}
public Definition CreateDefinition()
{
if (this.DefinitionFile.Length == 0)
{
throw new ProviderException(
"Definition file is not specified in configuration.");
}
return CreateDefinition(this.DefinitionFile);
}
public abstract Definition CreateDefinition(string definitionFile);
public virtual RowValidator CreateValidator()
{
RowValidator validator = null;
if ((this.ValidatorName.Length > 0) &&
(!string.IsNullOrEmpty(_validatorType)))
{
Type customValidatorType = Type.GetType(_validatorType, true, true);
if (!typeof(RowValidator).IsAssignableFrom(customValidatorType))
{
throw new InvalidOperationException("Configured validator
Type is not derived from RowValidator");
}
validator = (RowValidator) Activator.CreateInstance(customValidatorType,
false);
if (validator != null)
{
validator.Name = this.ValidatorName;
validator.Description = _validatorDescription;
}
}
return validator;
}
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
{
throw new ArgumentNullException("config");
}
base.Initialize(name, config);
this.DefinitionDirectory = config["definitionDirectory"];
config.Remove("definitionDirectory");
this.DefinitionFile = config["definitionFile"];
config.Remove("definitionFile");
this.ValidatorName = config["validatorName"];
config.Remove("validatorName");
if (this.ValidatorName.Length > 0)
{
_validatorDescription = config["validatorDescription"];
config.Remove("validatorDescription");
if (string.IsNullOrEmpty(_validatorDescription))
{
_validatorDescription = "Mailshot data row validator";
}
_validatorType = config["validatorType"];
config.Remove("validatorType");
}
}
public abstract IDataReader RetrieveMailshotData();
}
The MailshotProvider abstract base class represents the feature specific provider
base class and derives from the ProviderBase abstract base class. The Initialize
method makes sure that configuration settings have been specified and then calls
the base class to set the provider's Name and Description properties. The optional,
MailshotProvider specific, configuration settings are retrieved using the following
configuration attributes:
- definitionDirectory
- Specifies the absolute or relative path of the definition file directory.
- definitionFile
- Specifies the name of the definition file.
- validatorName
- Specifies the "friendly" name of the validator as specified in the
validators section
in the configuration file.
For details of the Definition and RowValidator classes, see the Other Core Base
Classes section.
The abstract RetrieveMailshotData method is called by client code to retrieve the
mailshot data. All of the mailshot providers return the mailshot data as a System.Data.IDataReader
implementation. For example, a mailshot provider that represents data that is stored
in an SQL Server database may return the mailshot data as a System.Data.SqlClient.SqlDataReader
and a mailshot provider that represents data that is stored in a file may return
the mailshot data as a FileReader derived class. Both the SqlDataReader and FileReader
classes implement the IDataReader interface and both also implement the interface
through their System.Data.Common.DbDataReader base class and therefore, client code
can cast the RetrieveMailshotData return value to DbDataReader if required (although
we wouldn't recommend it). For details of the FileReader class see the Other Core
Base Classes section.
The MailshotProvider class has methods that client code can use to create an instance
of a data row validator and create an instance of the data source file definition.
The CreateValidator method is implemented in the base class because the values required
to create a validator instance may (it is optional) only be specified in the configuration
file, therefore the base class has the information needed to instantiate an instance.
However, this implementation will only work if the validator has a default, parameterless,
constructor, therefore the method is virtual to allow a derived class to override
it and use a validator that requires parameterized construction. The CreateDefinition
method is, ultimately, abstract because a data source file definition may or may
not be used and if one is used it may either be specified in the configuration settings
or the file name may be specified by client code at runtime. Therefore only a derived
provider class will know what is permissible or expected.
There are two possible types of mailshot data source, the data may be stored in
a database in which case it is represented by the DbMailshotProvider class or it
may be stored in a file in which case it is represented by the FileMailshotProvider
class. Both of these base classes are abstract as there are many different types
of database and many different types of file format. Therefore, a concrete derived
class is required to provide specific mailshot data source processing. For example,
in the sample code, the FileProviders (satellite) assembly defines the DelimitedFileMailshotProvider
class that derives from the FileMailshotProvider class to handle mailshot data that
is stored in a delimited, text, file.

FileMailshotProvider abstract base class:
public abstract class FileMailshotProvider : MailshotProvider
{
protected FileMailshotProvider() : base() { }
protected static string GetInputFile(string sourceDirectory, string pattern)
{
// Implementation elided...
}
protected static string GetWorkingFile(string workingDirectory,
string sourcePath)
{
// Implementation elided...
}
public override void Initialize(string name, NameValueCollection config)
{
if (string.IsNullOrEmpty(config["definitionDirectory"]))
{
throw new ProviderException("Configuration does not contain
a definitionDirectory attribute or it is empty.");
}
base.Initialize(name, config);
}
public abstract IDataReader RetrieveMailshotData(string definitionFile);
}
The FileMailshotProvider abstract base class derives from the feature specific MailshotProvider
base class to represent file based mailshot data sources. It overrides the Initialize
method to enforce the inclusion of the DefinitionDirectory value in the configuration
data as it is optional in the MailshotProvider class. The FileMailshotProvider class
adds an, abstract, overload of the RetrieveMailshotData method that allows client
code to specify a definition file at runtime, rather than specify the value in the
configuration file. A definition file is required by all file based providers to
supply the meta data required to read the mailshot data file, see the Definition
class in the Other Core Base Classes section for details. If the, required, definition
file could only be specified in the configuration file we would need a configuration
file provider definition for each combination of customer and mail template which
would result in a very large configuration file! It would also mean that all of
the defined provides would be loaded into memory when the client application started
and they would remain in memory throughout the lifetime of the client application.
Obviously the client application will have to know the name of the relevant definition
file for a specific customer, mail template combination in order to pass the value
into the RetrieveMailshotData method, however, this data is likely to be smaller
than that required to define a provider and the client application can store the
data however and wherever it wishes and may also remove the data from memory when
it has finished with it.
Other Core Base Classes
In addition to the provider abstract base classes, the MailshotCore assembly also
contains other notable base classes that satellite assembly types can derive from
in order to provide their services to the client application. As outlined earlier
in the Component Dependencies section, the client code knows nothing about the satellite
assemblies that contain the concrete provider classes, it only knows about and therefore
only interacts with providers using the base types defined the MailshotCore assembly.
Definition abstract base class
The Definition abstract base class represents the meta data of a mailshot data source.
An instance of a concrete Definition derived class is used internally by satellite
assemblies to process a mailshot data source and create the IDataReader implementation
returned from the MailshotProvider RetrieveMailshotData method. A mailshot data
source definition is intended primarily for use by providers that handle file based
mailshot data sources, however, there is nothing to stop a database mailshot provider
from using the class. Client code can also retrieve an instance of a provider's
concrete Definition derived class by calling the MailshotProvider CreateDefinition
method. The actual Type of the definition returned will depend on the provider,
however, the client code can retrieve the information it requires using the Definition
base class.
The meta data for a mailshot data source is represented in an XML definition file
and a concrete Definition derived class processes the XML file to initialize its
properties. Using a definition file to describe the format and layout of a mailshot
data source allows us to develop providers that are data source specific rather
than customer specific, although there is nothing to stop a customer specific provider
instance being specified in the configuration file. In a production system there
will be occasions when a customer specific provide instance will have to be specified.
However, in general we want to discourage this to prevent the configuration file
from getting out of control and to keep the number of provides loaded into memory
as small as possible. The FileProviders satellite assembly demonstrates the principle
of the design by defining a concrete, file based, provider DelimitedFileMailshotProvider
which derives from the FileMailshotProvider base class. This provider can create
the meta data, at runtime, for a delimited file data source and perform a "generic"
read of the file. A generic read has to rely on certain, default, assumptions, e.g.
it assumes that the file uses a "comma" delimiter and that text fields are not qualified,
it also loads each field as Type of String. The provider can also process a delimited
file data source using a specified definition file. In this situation all of the
information, such as delimiter, text qualifier, field name, field Type, etc. required
to process the specified file can be specified in the definition file and the provider
uses this meta data to create named, correctly typed fields.
Sample definition file:
<?xml version="1.0" encoding="utf-8" ?>
<definition customer="Test Customer"
mailTemplate="MemRenewal01"
sourceType="delimitedfile">
<file textQualifier="doublequote" pattern="TestCustomer01.csv"
sourceDir="Input" workingDir="Working" />
<fields>
<field ordinal="0" name="Id" type="System.Int32"
unique="true" required="true" />
<field ordinal="1" name="Name" type="System.String"
maxSize="25" required="true" />
<field ordinal="2" name="Address1" type="System.String"
maxSize="30" required="true" />
<field ordinal="3" name="Address2" type="System.String"
maxSize="30" required="true" />
<field ordinal="4" name="Address3" type="System.String"
maxSize="30" required="false" />
<field ordinal="5" name="Address4" type="System.String"
maxSize="30" required="false" />
<field ordinal="6" name="PostCode" type="System.String"
maxSize="12" required="true" />
<field ordinal="7" name="MembershipRef" type="System.String"
maxSize="20" required="true" />
<field ordinal="8" name="Salutation" type="System.String"
maxSize="50" required="true" />
<field ordinal="9" name="ExpiryDate" type="System.DateTime"
required="true" />
<field ordinal="10" name="MoneySymbol" type="System.String"
maxSize="1" required="true" />
<field ordinal="11" name="Price" type="System.Decimal"
required="true" />
</fields>
</definition>
The sample definition file listing above represents a delimited file mailshot data
source that uses the default "comma" delimiter. The delimiter attribute can be used
in the file element to define a specific delimiter and the pattern attribute accepts
wild card characters so the actual file name does not have to be specified in full.
The Definition base class processes the definition element that is common to all
providers i.e. the customer, mailTemplate and sourceType attributes. The Definition
base class also looks for and reads the fields element and calls its abstract CreateFieldInfo
method for each field element it finds to allow a concrete derived class to read
the element's attributes. Each field element is represented by an instance of the
FieldInfo class or a class derived from the FieldInfo class. The FileDefinition
abstract base class, which derives from the Definition class, processes the file
element that is common to file based mailshot data sources.
The design enforces the extension of the Definition and/or FileDefinition base classes
to allow a concrete derived class to specify a full schema for the definition file.
For example, the FileProviders satellite assembly defines the, concrete, DelimitedFileDefinition
class which derives from the FileDefinition base class and expects a definition
file like the one in the listing above. In a production system we would probably
also need to create a, concrete, FixedWidthFileDefinition class to handle fixed
width file based mailshot data sources. A fixed width file definition would have
the sourceType attribute set to "fixedwidthfile" and would require, at least, two
additional attributes on the field element to specify the "offset" and "length"
of each field. Having created the FixedWidthFileDefinition class to handle the meta
data required to process a fixed width file, we would also need to extend the FieldInfo
class to add the extra properties required by a fixed width field. The FixedWidthFileDefinition
class would also need to override the CreateFieldInfo method to process each field
element and return a instance of a derived FieldInfo class that represents a fixed
width field.
FieldInfo base class
The FieldInfo base class, not to be confused with the System.Reflection.FieldInfo
class, represents the meta data of mailshot data source field. Each row of mailshot
data contains one or more fields and the meta data of each field is represented
by an instance of the FieldInfo class or a class that is derived from the FieldInfo
class. A concrete class that derives, ultimately, from the Definition abstract base
class is responsible for processing the field meta data for a specific type of mailshot
data source. The concrete class should override the Definition class CreateFieldInfo
method to construct and return an instance of the FieldInfo class or a class derived
from it for each definition node that it is passed.
The FieldInfo class is used, as is, for delimited file based mailshot data sources
and may be suitable, as is, for other types of data source. However, it is anticipated
that the FieldInfo class will need to be extended to accommodate the extra properties
required to describe some types of data source fields.
FileDefinition abstract base class
The FileDefinition abstract base class extends the Definition abstract base class
to represent the meta data of a file based mailshot data source. A concrete class
derived from the FileDefinition base class should be created in a satellite assembly
to represent and process the meta data of a specific type of file format. For example,
the FileProviders satellite assembly contains the DelimitedFileDefinition class
which extends the FileDefinition base class to represent and process the meta data
of a delimited file mailshot data source. See the Definition abstract base class
section for details of the processing sequence of definition files.
FileReader abstract base class
The FileReader abstract base class extends the System.Data.Common.DbDataReader abstract
base class to provide the client application with a consistent mechanism for reading
the data supplied by all types of mailshot data source. The DbDataReader base class
provides, a largely abstract, default implementation of the IDataReader interface.
All providers return their mailshot data to the client application as an implementation
of and typed as an IDataReader interface.
The FileReader abstract base class provides the implementation that allows the client
application to read through the rows of mailshot data and it also provides the implementation
to access each field in either a generic or type-safe way. However, it is the responsibility
of a derived class to retrieve the mailshot data from the relevant data source and
to present it to the FileReader base class in a consistent format. The FileReader
class has two, protected, abstract methods that it calls to retrieve the mailshot
data, in batches, from a derived class, the InitalBatch method is called from the
Read method to retrieve the first batch of rows and the NextBatch method is called
to retrieve any subsequent row batches. By default the FileReader class sets the
batch size to 100 rows but a derived class can set the batch size in the call to
the base class constructor.
The FileReader class always calls the NetxBatch method, if another batch is required,
before the end of the current batch is reached and the call is always made on a
background thread retrieved from the thread pool. It is up to a derived class to
determine how the initial batch of rows is built but if a synchronous read is used
the client code will have to wait until the first batch is built before its first
call to the Read method returns.
Both the InitialBatch and NextBatch methods return their mailshot data rows as an
array of MailshotDataRow derived instances.

MailshotDataRow abstract base class:
public abstract class MailshotDataRow
{
protected MailshotDataRow() : base() { }
public abstract object this[int index] { get; }
}
As can be seen in the listing above the MailshotDataRow abstract base class defines
a very simple interface that the FileReader class can use to retrieve the value
of each field in a single row of mailshot data. The MailshotDataRow class is public
so that satellite assemblies can derive from it to create their own concrete implementation.
We strongly recommend that concrete MailshotDataRow derived classes are scoped as
internal as there is no need for client code to access instances directly and the
FileReader class does not provide direct client access to its data at the row level.
There is no need for concrete derived MailshotDataRow classes to check the value
passed to the indexer as the FileReader class validates all ordinal values specified
by calling code before using them.
The FileProviders satellite assembly contains the concrete DelimitedFileReader class
which derives from the FileReader base class. The DelimitedFileMailshotProvider
concrete provider class uses the DelimitedFileReader to read mailshot data from
a delimited text file and this is the Type that is returned from its RetrieveMailshotData
method.
RowValidator abstract base class
The RowValidator abstract base class provides the client application with row level
validation of mailshot data. The MailshotProvider class has a CreateValidator method
that client code can use to retrieve a provider specific row validator. Row validators
are optional and may only be specified in the configuration file. Satellite assemblies
that define one or more providers may also define concrete implementations of the
RowValidator base class.

RowValidator abstract base class:
public abstract class RowValidator
{
private string _name;
private string _description;
protected RowValidator() : this(string.Empty, string.Empty) { }
protected RowValidator(string name, string description) : base()
{
this.Name = name;
this.Description = description;
}
public string Description
{
// Get and Set elided...
}
public string Name
{
// Get and Set elided...
}
public abstract bool IsValid(IDataReader reader);
public abstract void Validate(IDataReader reader);
}
A concrete RowValidator derived class should return true from its IsValid method
if the current IDataReader row is valid and false if it is invalid. The Validate
method should do nothing if the current row is valid and throw an exception if it
is invalid. A derived class can use the IDataReader GetSchemaTable method to retrieve
the meta data of the mailshot data in the form of a System.Data.DataTable and the
GetValue, GetValues or any of the type-safe get methods to retrieve the fields of
the current row.
Validators have their own sub-section under the mailshotService section in the configuration
file and are assigned to a provider by specifying the validator "friendly" name
in the provider's validatorName configuration attribute. A validator may be provider
specific or several providers may share the same named validator, although each
call to the MailshotProvider CreateValidator method returns a new instance.
The FileProviders satellite assembly contains the concrete FileRowValidator class
which derives from the RowValidator base class. The DelimitedFileMailshotProvider
instance named as "TestCustomerProvider" in the configuration file uses an instance
of the FileRowValidator class named as "CsvFileRowValidator" in the configuration
file. The FileRowValidator class ensures that all required fields are present in
the data row and that each row's required fields contain data.
Provider Configuration Settings
The Mailshot System providers are loaded and initialized in the MailshotCore assembly
from configuration data passed in to the mailshot service class by the client code.
The settings required to load and initialize the providers are specified in the
client application's configuration file. The MailshotCore assembly contains the
following classes that are used by .Net Framework configuration sub-system to represent
the Mailshot System configuration settings:
MailshotServiceSection class
Derives from the System.Configuration.ConfigurationSection class to represent the
mailshotService section in the configuration file. The section element has a defaultProvider
attribute that specifies the "friendly" name of the default provider, a providers
section that specifies an AddRemoveClearMap collection of providers and a validators
section that specifies an AddRemoveClearMap collection of validators.

MailshotServiceSection class:
public class MailshotServiceSection : ConfigurationSection
{
[StringValidator(MinLength = 1)]
[ConfigurationProperty("defaultProvider",
DefaultValue = "CsvGenericProvider")]
public string DefaultProvider
{
get { return (string) base["defaultProvider"]; }
set { base["defaultProvider"] = value; }
}
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get { return (ProviderSettingsCollection) base["providers"]; }
}
[ConfigurationProperty("validators", IsRequired = false)]
public ValidatorSettingsCollection Validators
{
get { return (ValidatorSettingsCollection) base["validators"]; }
}
}
The MailshotServiceSection listing above uses the declarative coding model to create
a custom configuration section.
ValidatorSettings class
Derives from the System.Configuration.ConfigurationElement class to represent an
individual validator in the validators section AddRemoveClearMap collection. A validator
element has two required attributes name and type and one optional attribute description.

ValidatorSettings class:
public sealed class ValidatorSettings : ConfigurationElement
{
private readonly ConfigurationProperty _propertyDescription;
private readonly ConfigurationProperty _propertyName;
private readonly ConfigurationProperty _propertyType;
public ValidatorSettings() : base()
{
_propertyName = new ConfigurationProperty("name", typeof(string),
null, null, new StringValidator(1),
ConfigurationPropertyOptions.IsKey |
ConfigurationPropertyOptions.IsRequired);
_propertyType = new ConfigurationProperty("type", typeof(string),
null, null, new StringValidator(1),
ConfigurationPropertyOptions.IsRequired);
_propertyDescription = new ConfigurationProperty("description",
typeof(string), string.Empty);
base.Properties.Add(_propertyName);
base.Properties.Add(_propertyType);
base.Properties.Add(_propertyDescription);
}
public ValidatorSettings(string name, string typeName) : this()
{
this.Name = name;
this.Type = typeName;
}
public string Description
{
get { return (string) base[_propertyDescription]; }
set { base[_propertyDescription] = (value == null) ? string.Empty : value; }
}
public string Name
{
get { return (string) base[_propertyName]; }
set { base[_propertyName] = (value == null) ? string.Empty : value; }
}
public string Type
{
get { return (string) base[_propertyType]; }
set { base[_propertyType] = (value == null) ? string.Empty : value; }
}
}
The ValidatorSettings listing above uses the programmatic coding model to create
a custom configuration element.
ValidatorSettingsCollection class
Derives from the System.Configuration.ConfigurationElementCollection class to represent
an AddRemoveClearMap collection of validator elements.

ValidatorSettingsCollection class:
[ConfigurationCollection(typeof(ValidatorSettings))]
public sealed class ValidatorSettingsCollection : ConfigurationElementCollection
{
public ValidatorSettingsCollection()
: base(StringComparer.OrdinalIgnoreCase) {}
public ValidatorSettings this[int index]
{
get { return (ValidatorSettings) base.BaseGet(index); }
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
public new ValidatorSettings this[string key]
{
get { return (ValidatorSettings) base.BaseGet(key); }
}
protected override ConfigurationElement CreateNewElement()
{
return new ValidatorSettings();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((ValidatorSettings) element).Name;
}
public void Add(ValidatorSettings element)
{
// Implementation elided...
}
public void Clear()
{
// Implementation elided...
}
public void Remove(string key)
{
// Implementation elided...
}
}
The ValidatorSettingsCollection listing above demonstrates a simple implementation
of an AddRemoveClearMap collection of ValidatorSettings instances. Configuration
settings may be used to add an instance to the collection, remove an instance from
the collection or clear the collection.
The client application can make use of these MailshotCore assembly configuration
types in its configuration file because it has a reference to and a direct dependency
on the MailshotCore assembly. Typically, the MailshotCore assembly will be in the
same, working, directory as the client application assembly, therefore, the .Net
Framework configuration sub-system will be able to locate the MailshotCore assembly
to access the configuration types.
The configuration settings for the providers are handled automatically by the configuration
sub-system using the built-in System.Configuration.ProviderSettings and System.Configuration.ProviderSettingsCollection
classes.
Using the classes outlined above the app.config file of
the Mailshot System client application specifies providers and validators as follows:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="mailshotService"
type="Mailshot.MailshotServiceSection,
MailshotCore, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=400915eda22cb254"
allowExeDefinition="MachineToApplication"
restartOnExternalChanges="true" />
</configSections>
<mailshotService defaultProvider="CsvGenericProvider">
<providers>
<add name="CsvGenericProvider"
type="FileProviders.DelimitedFileMailshotProvider,
FileProviders, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=400915eda22cb254"
definitionDirectory="Definitions" />
<add name="TestCustomerProvider"
type="FileProviders.DelimitedFileMailshotProvider,
FileProviders, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=400915eda22cb254"
definitionDirectory="Definitions"
definitionFile="TestCustomerDefinition.xml"
validatorName ="CsvFileRowValidator" />
</providers>
<validators>
<add name="CsvFileRowValidator"
type="FileProviders.FileRowValidator,
FileProviders, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=400915eda22cb254" />
</validators>
</mailshotService>
</configuration>
The listing above demonstrates how to declare and implement the mailshotService
section. In a production system the client application is likely to require additional
configuration settings to enable it to associate a provider with a customer and
specify a definition file dynamically rather than in the provider settings, for
example.
Provider Service or Factory Class
The client application needs a way to retrieve a provider instance from the MailshotCore
assembly and a service or factory type of class is one way that the core assembly
can provide access to providers. As stated earlier in the article there will only
ever be one instance of each named provider specified in the configuration file
therefore a static service class seems like an good way to supply a provider instance
to client code. However, the service class needs the client to pass in the mailshotService
section from its configuration file in order for it to load and initialize the providers,
therefore the service class needs a "create" or an "initialize" method. Expecting
client code to "create" a static class does not seem like an intuitive solution,
so we decided to implement the service class as singleton rather than use a static
class. We feel that developers will be more familiar with the technique of creating
an instance of a singleton class than the rather strange concept of creating a static
class.

MailshotService class:
public sealed class MailshotService
{
private static volatile MailshotService _instance;
private static object _target = new object();
private MailshotProvider _defaultProvider;
private MailshotProviderCollection _providers;
private MailshotService() { }
public static MailshotService Instance
{
get
{
if (_instance == null)
{
lock (_target)
{
if (_instance == null)
{
throw new InvalidOperationException("The CreateService
method must be called before an instance
can be retrieved.");
}
}
}
return _instance;
}
}
public MailshotProvider Provider
{
get { return _defaultProvider; }
}
public MailshotProviderCollection Providers
{
get { return _providers; }
}
private void LoadProviders(MailshotServiceSection config)
{
_providers = new MailshotProviderCollection();
ProviderLoadHelper.InstantiateProviders(config.Providers,
config.Validators, _providers,
typeof(MailshotProvider));
_providers.SetReadOnly();
_defaultProvider = _providers[config.DefaultProvider];
if (_defaultProvider == null)
{
throw new ProviderException("Unable to find or load
a default mailshot provider.");
}
}
public static void CreateService(MailshotServiceSection config)
{
if (config == null)
{
throw new ArgumentNullException("config");
}
if (_instance == null)
{
lock (_target)
{
if (_instance == null)
{
_instance = new MailshotService();
_instance.LoadProviders(config);
}
}
}
}
}
The MailshotService class listing above demonstrates the, slightly unconventional,
singleton implementation. Client code has to call the CreateService method, passing
in the MailshotServiceSection as retrieved from the configuration sub-system, to
create the mailshot service. Once the service has been created client code can use
the Instance property to retrieve an instance of the service and use the instance
properties to retrieve a provider. We could return the one and only MailshotService
instance from the CreateService method but again we feel that this is not intuitive.
The CreateService method "creates" the service and should only be called once by
client code, however, the method is implemented so that no harm will be done if
it is called more than once. To reinforce the design principle, the Instance property
throws an exception if an instance of the MailshotService class does not already
exist. Client code would, typically, use the MailshotService class as follows:
Using the MailshotService class from client code:
private void Form1_Load(object sender, EventArgs e)
{
MailshotServiceSection config =
ConfigurationManager.GetSection("mailshotService")
as MailshotServiceSection;
MailshotService.CreateService(config);
}
// Once the service has been created...
MailshotService service = MailshotService.Instance;
// Get the default provider, as specified in app.config.
// Assumes that the client code knows that the default provider
// derives from FileMailshotProvider...
FileMailshotProvider fileProvider = service.Provider as FileMailshotProvider;
In the sample code listing above the client code creates the mailshot service in
the Load event of its main form. Once the service instance has been created and
the providers have been loaded and initialized the client can retrieve an instance
of the MailshotService class from anywhere within its code by calling the, static,
Instance property.
The MailshotCore assembly has an internal, helper, class ProviderLoadHelper that
creates the provider instances and initializes them from the data contained in the
configuration file. The .Net Framework does have a built-in helper class for loading
providers System.Web.Configuration.ProvidersHelper, however, as the namespace suggests,
this class is primarily intended for use by ASP.Net web applications. As we are
developing a library intended for use by Windows Forms applications and we also
have a requirement to process the validators configuration section, which ProvidersHelper
knows nothing about, when initializing the mailshot providers, we decided to develop
our own provider load helper.
Conclusion
The Provider Design Pattern has a lot to offer when developing certain types of
line of business systems. One of the most interesting aspects of the provider model,
in our opinion, is that it provides developers with a mechanism to decouple a client
application from the system implementation. The client code uses a known, public,
API defined by the core abstract base classes but knows nothing about the actual
implementation of the API. The potential benefits of decoupling the client from
the implementation can be considerable in systems that have to change and adapt
quickly and frequently.
Fixing a bug in an implementation specific satellite assembly will require a recompile
and a version increment of the satellite assembly but all that is required on the
client side is to replace the existing version of the satellite assembly with the
new version and then alter the provider's version in the client's configuration
file.
Adding a new provider to an existing satellite assembly or adding a new satellite
assembly is just as easy. However, when adding new providers you will need a client
application that is a bit smarter than one we have provided in the sample Mailshot
System. The sample code is primarily intended to demonstrate how to design and develop
providers, therefore the client application that runs the demonstration is a bit
simplistic!
Copyright ©Blayd Software Limited 2008-2010. All rights reserved.