There is a fine line between what should be core functionality and what should be "pluggable." Providers let flex points in an application block ensure that the core functionality is still applicable even when something outside of that core functionality changes. Throughout this book I have described and created many providers for all of the application blocks that ship with Enterprise Library. For example, Chapter 3 described the database providers (SqlDatabase, OracleDatabase, and Db2Database) that ship with the Data Access Application Block and showed how to create a new database provider that can be used to access other databases. Chapter 7 discussed the AuthenticationProvider that ships with the Security Application Block for authenticating users against a database and showed how to create a new AuthenticationProvider to authenticate users against a Microsoft Active Directory. Providers allow an application block to be adaptable so that it can fit into an environment where the needs may be different than what Enterprise Library allows out-of-the-box. There are several characteristics that define how an application block should be designed to use providers.
Provider Type ImplementationA provider type defines the kind of provider that is expected to be used by an application block. For example, the Data Access Application Block uses database providers, while the Security Application Block uses authentication, authorization, security cache, role management, and profile management providers. Every application block must determine which type of providers it expects to use and should provide at least one implementation of such a provider. The Data Mapping Application Block expects to use DataMappingProviders. I have defined the IDataMappingProvider interface to represent the expected functionality that any DataMappingProvider should possess. When the DatabaseWrapper maps database command, table, and field information to the properties and fields of an "entity" (e.g., a typed DataSet), it uses the methods exposed by an implementation of the IDataMappingProvider interface. The DatabaseWrapper can use any class that implements the IDataMappingProvider interface to map database constructs to the corresponding construct for that provider. The DataSetMappingProvider is provided with the application block as an implementation of the DataMappingProvider type and is intended to be used to map typed DataSets to database commands and command parameters. Figure 9.2 shows how the DataSetMappingProvider implements the IDataMappingProvider interface to serve as a provider type implementation for the DataMappingProvider type. Figure 9.2. Primary Classes Used for DataSetMappingProvider ImplementationDecoupling from the Core FunctionalityBy developing the block so that it uses the interface exposed by the provider type and not any one specific provider, the application block is decoupled from any single provider's implementation. This helps to achieve the following goals.
Not only does Figure 9.2 show how the IDataMappingProvider interface exposed specific functionality that was implemented by the DataSet-MappingProvider class, but it also showed the use of several abstract base classes that complement the IDataMappingProvider. The Data Mapping Application Block takes advantage of these classes to remain completely decoupled from any one provider's implementation. The DatabaseWrapper accesses a DataMappingCollection from the provider that it is configured to use and accesses all of the settings for mapping entities from the DataMapping objects contained in this collection. The DataMappingCollection class and the DataMapping class are both abstract; an implementation of a DataMappingProvider must supply the specific way that it intends to map database fields to entities. The DataSetMappingProvider supplies this by way of the derived DataSet-Mapping class; however, the Data Mapping Application Block knows nothing of this derived class. It just expects to retrieve a class that is derived from the DataMappingCollection class and use it to access classes that must derive from the DataMapping class. Listing 9.1 shows the code that illustrates how the DatabaseWrapper is decoupled from any one provider's implementation. In this method, the DatabaseWrapper is not referencing any classes that are used in the DataSetMappingProvider implementation; it only references the IDataMappingProvider interface and the abstract base classes shown in Figure 9.2. Listing 9.1. Binding Only to the IDataMappingProvider Interface
ConfigurableAs I created new providers in the previous chapters, I described the benefit that can be achieved by enabling each provider to initialize itself with the configuration information that it needs to run properly. For example, for the XMLFileDatabase database provider to run properly, it needs to obtain configuration information about which file to use to store and retrieve data. Therefore, in Chapter 3, I designed the XMLFileDatabase database provider so that it could initialize itself with information about which XML file to use. The configuration data for a provider is generally stored with the rest of the configuration data for an application block and is specified in configuration by its type information and a name. As discussed in Chapter 1, the abstract ConfigurationProvider base class defines the contract for initializing a provider with configuration information. A provider is afforded the ability to initialize itself with configuration data by deriving from this abstract base class and implementing the Initialize method. The Initialize method accepts an object containing the current ConfigurationView and uses it to obtain the provider's runtime configuration data. When building a provider for an application block, it is important to derive that provider either directly or indirectly from the ConfigurationProvider class so that it can initialize itself with configuration data. The DataSetMappingProvider, for example, implements the IDataMappingProvider interface and derives from the ConfigurationProvider class. Additionally, the runtime configuration data for a provider is typically contained in a class that derives from the ProviderData class. In the case of the DataSetMappingProvider, the runtime configuration data is contained in a class named DataSetMappingProviderData.DataSetMappingProviderData derives from an abstract DataMappingProviderData class that derives from ProviderData. The DataMappingProviderData class contains runtime configuration information that would be common for all DataMappingProviders. The DataSetMappingProviderData class just adds configuration information that is specific to its implementation. Listing 9.2 has a property for accessing the CacheManager that is used to cache typed DataSets and a property for returning the DataMappingCollection. This listing also demonstrates how each of these properties is decorated with attributes that allow the XmlSerializer to be used to serialize and deserialize the class. That is how the Configuration Application Block creates the provider classes (or any classes, for that matter) from the configuration data. You can read more about the ConfigurationProvider, ConfigurationView, and the other configuration runtime classes in Chapter 1. Listing 9.2. Representing the Configuration Data for the DataSetMappingProvider
|