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.

Tightly coupled single dependency

Figure 1

Tightly coupled double dependency

Figure 2

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.

Decoupled application and implementation

Figure 3

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.

Core assembly abstract base classes

Figure 4

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.

ExpandMailshotProvider abstract base class:

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.

ExpandFileMailshotProvider abstract base class:

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.

ExpandSample definition file:

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.

ExpandMailshotDataRow abstract base class:

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.

ExpandRowValidator abstract base class:

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.

ExpandMailshotServiceSection class:

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.

ExpandValidatorSettings class:

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.

ExpandValidatorSettingsCollection class:

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.

ExpandUsing the classes outlined above the app.config file of the Mailshot System client application specifies providers and validators as follows:

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.

ExpandMailshotService class:

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:

ExpandUsing the MailshotService class from client code:

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!