Runtime Configuration


If you have perused even a few of the previous chapters, it should be fairly obvious that all application blocks are driven by their configuration information. The configuration information for an application block defines how the block and its providers will get the information they need to initialize, validate, and determine if any cross-block relationships exist. This provides several benefits.

  • It provides the application block's flexibility and extensibility. An application block can be more easily adapted to meet the needs of different enterprise scenarios by allowing its runtime behavior to be configurable.

  • Characteristics of an application block can be configured by different people at different times in the application's life cycle.

  • The application block can be incrementally adapted for increasingly complex situations.

  • The Configuration Tool provided with Enterprise Library makes it easy to create and edit configuration settings for an application block and its providers. Additionally, this tool makes it easy to specify cross-block relationships.

However, there is one detriment to being so configuration driven. It is sometimes more difficult to debug an issue in an application if it involves the way an application block or provider is configured. Developers are used to stepping through code if an issue occurs in an application, but unfortunately there is no way to step through the configuration data that drives the runtime behavior for an application block or provider. That is one reason why it is so important to use a tool like the Enterprise Library Configuration Tool for configuring an application block and its providers. While it will not catch every possible issue that can occur, it can catch many of them.

Specifying how the runtime configuration information for an application block will be represented entails designing and developing the set of objects that are needed to contain the configuration settings for that application block. For example, for the Data Access Application Block to function properly, the block needs to obtain information about the connection string to use for connecting to a database and the type of database that an application is configured to use (e.g., SQL Server, Oracle, or DB2). Therefore, it contains runtime configuration objects for representing the connection string and database type.

The Configuration Application Block reads settings from storage and returns the objects to the application block. Because all of the StorageProviders that ship with the Configuration Application Block require an object to be XML-serializable, the runtime configuration objects for a custom application block must also be XML-serializable. Therefore, in all of the runtime configuration objects in Enterprise Library and many of the code samples that you will see in the rest of this chapter, you will notice that the properties exposed by an object will be attributed with certain XML attributes like XmlIgnore, XmlAttribute, and XmlArray. These attributes allow the XmlSerializer to recognize how the values for these properties should be serialized and deserialized. For example, XmlIgnore indicates that the XmlSerializer should not attempt to serialize the value returned by that property. XmlAttribute signifies that the XmlSerializer should write the values out as an XML attribute and should read the values for this property out from this XML attribute when deserializing the object.

Figure 9.4 depicts the runtime configuration objects that are needed for the Data Mapping Application Block and the DataSetMappingProvider that ships with it. I won't provide the code for every class that is shown in Figure 9.4 because much of the code that is created for each class is similar in its intent; the classes primarily differ only in the specific properties that they expose. I will, however, provide code samples for the classes where there is a significant difference in the way the class needs to be created or the purpose it serves. This should provide enough detailed information for you to be able to design and develop the objects that represent the runtime configuration for your own application blocks. All of the code for this application block is on this book's Web site if you want to review the code for all of the configuration objects.

Figure 9.4. Runtime Configuration Object Graph for the Data Mapping Application Block


The first runtime configuration class that needs to be created for an application block is the one that will hold the configuration information for the entire block. Typically this class is named according to the name of the application block and suffixed with Settings (e.g., DatabaseSettings, ExceptionHandlingSettings, LoggingSettings, etc.). For the Data Mapping Application Block I have named this class DataMappingSettings. In this class, the section name that identifies the section that the metaconfiguration data for this block will exist in must be specified. The Configuration Application Block looks for the metaconfiguration data in this section in the application's domain configuration file (i.e., app.config or web.config) and also uses the section name as the default file name for the configuration file (when using the XmlFileStorageProvider). Because the section name in the application's domain configuration file must be unique, the string that represents this application block must also be unique among all application blocks.

Listing 9.4 shows the code for the DataMappingSettings class. This class contains properties that return the name of the default DataMappingProvider that has been set through configuration, as well as a property that returns a collection of objects that contain the configuration data for the DataMappingProviders that have been configured for an application. Additionally, the section name is set to DataMappings. This means that the Configuration Application Block will read and write the metaconfiguration data for this application block in a section named DataMappings, and if the XmlFileStorageProvider is used to store the configuration data for this application block, that configuration data will be serialized to a file named DataMappings.config by default.

Listing 9.4. The DataMappingSettings Class

[C#] [XmlRoot("dataMappingSettings")] public class DataMappingSettings {     public const string SectionName = "DataMappings";     private DataMappingProviderDataCollection dataMappingProviders;     private string defaultDataMapper;     public const string ConfigurationNamespace =          "http://www.example.com/eaf/datamappings";     public DataMappingSettings()     {          dataMappingProviders = new               DataMappingProviderDataCollection();     }     [XmlAttribute("defaultDataMapper")]     public string DefaultDataMapper     {          get {return defaultDataMapper;}          set {defaultDataMapper = value;}     }     [XmlArray(ElementName="dataMappingProviders")]     [XmlArrayItem(Type=typeof(DataMappingProviderData))]     public DataMappingProviderDataCollection DataMappingProviders     {          get { return dataMappingProviders; }     }     public static DataMappingSettings          GetSettings(ConfigurationContext context)     {          return (DataMappingSettings)               context.GetConfiguration(SectionName);     } } [Visual Basic] <XmlRoot("dataMappingSettings")> _ Public Class DataMappingSettings     Public Const SectionName As String = "DataMappings"     Private dataMappingProviders As DataMappingProviderDataCollection     Private defaultDataMapper As String     Public Const ConfigurationNamespace As String = _          "http://www.example.com/eaf/datamappings"     Public Sub New()          dataMappingProviders = _               New DataMappingProviderDataCollection()     End Sub     <XmlAttribute("defaultDataMapper")> _     Public Property DefaultDataMapper() As String          Get               Return defaultDataMapper          End Get          Set               defaultDataMapper = Value          End Set     End Property     <XmlArray(ElementName:="dataMappingProviders"), _     XmlArrayItem(Type:=GetType(DataMappingProviderData))> _     Public ReadOnly Property DataMappingProviders() _          As DataMappingProviderDataCollection          Get               Return dataMappingProviders          End Get     End Property     Public Shared Function GetSettings _          (ByVal context As ConfigurationContext) _          As DataMappingSettings          Return CType(context.GetConfiguration _           (SectionName), DataMappingSettings)     End Function End Class

The rest of the runtime configuration objects that need to be created are to represent the configuration data for the DataSetMappingProvider and not the core functionality for the Data Mapping Application Block. Often the configuration data for a provider can be represented by an object or two. However, the configuration data for the DataSetMappingProvider is a bit more complex. At the lowest level I need to map DataFields in a Data-Table to the parameters for stored procedures. So, I need an object to represent that mapping. I named this object CommandParameterMapping, and it holds the state for the name of the parameter and the source column (i.e., the name of the DataField) for the parameter. If the source column is left blank, the Data Mapping Application Block will assume that there is no mapping and that the value for the parameter will be set at runtime. This is to allow for non-DataSet parameters. (For information on the UpdateDataSet method, see the section Updating Data in Chapter 3.)

Furthermore, it is perfectly normal that different stored procedures may need to be called depending on the type of change that occurs in a Data-Table in a DataSet. One stored procedure will generally need to be called to retrieve the data that is used to populate the DataTable; a different stored procedure is typically used when data is deleted from the table. Still another stored procedure is called when inserting or updating records in the DataTable. Often the same stored procedure will be called to handle both inserts and updates by determining if a database record already exists for the data that was modified in the DataTable: if a record does not exist, then the stored procedure performs an insert; otherwise, it updates the record that already exists.

I needed to create a configuration object that couples the name of the stored procedure that needs to be called for a specific operation for a DataTable with the collection of parameter mappings for that stored procedure. This class is named CommandMapping. The CommandMapping contains a collection of CommandParameterMappings, the command text (i.e., the stored procedure name), the command timeout, and an enumeration that represents the type of operation that it represents. The enumeration, CommandStatementType, contains values for Select, Insert, Update, Delete, and InsertUpdate.InsertUpdate is used to specify that the Data Mapping Application Block should call the same stored procedure for both inserts and updates that occur in the DataTable. This way, administrators who configure the Data Mapping Application Block do not need to replicate the same set of configuration data for two different CommandMappings just because they differ in the type of operations that they handle.

I have also created a specialized CommandMapping, named SelectCommandMapping, which derives from the CommandMapping class. This class has its CommandStatementType preset to Select. This type of command is used a bit differently than the other types because, in addition to setting a SelectCommand for a DataTable, an application may need to set multiple SelectCommands for the DataSet itself. One of the SelectCommands must be configured as the DefaultSelectCommand that will be used to retrieve the data for the entire DataSet; however, other SelectCommands can be used to return DataReaders and to call ExecuteNonQuery commands.

Since many DataTables can exist in one DataSet, I also needed to be able to signify the CommandMappings that are configured for each DataTable in the DataSet. The DataTableMapping object holds the state for the name of the DataTable and the CommandMappings for each of the types of operations that can occur for a DataTable.

Lastly, I needed to create the DataSetMapping to hold the collection of DataTableMappings and a collection of SelectCommandMappings. In addition to these collections, the DataSetMapping also keeps state about the name of the DataSet, the default SelectCommand (if one exists) for populating all the DataTables in the DataSet, the DatabaseInstance that is used to access the Database object from the Data Access Application Block, and a transaction's IsolationLevel so operations on the entire DataSet can be wrapped in the scope of a single database transaction. Because some of this same functionality must exist whether or not DataSets are used to map the relational database information to an object representation, many of its properties are actually defined in the DataMapping base class from which the DataSetMapping class derives.

As mentioned previously, I am not going to show all the code for all of these classes. Instead, the code for just the CommandMapping configuration object is provided in Listing 9.5. The code for the other configuration objects is very similar, differing only in the types of properties they expose.

Listing 9.5. The CommandMapping Class

[C#] [XmlInclude(typeof(CommandParameterMapping))] public class CommandMapping {     private CommandStatementType commandStatementType;     private string commandText;     private int commandTimeout;     private CommandParameterMappingDataCollection               parameterMappingDataCollection;     public CommandMapping()     {}     public CommandMapping(CommandStatementType commandStatementType,               string commandText) : this()     {          CommandType = commandStatementType;          CommandText = commandText;     }     public CommandMapping(CommandStatementType commandStatementType,               string commandText, int commandTimeout) :               this(commandStatementType, commandText)     {          Timeout = commandTimeout;     }     [XmlAttribute]     public CommandStatementType CommandType     {          get { return commandStatementType; }          set { commandStatementType = value;}     }     [XmlAttribute]     public string CommandText     {          get { return commandText; }          set { commandText = value;}     }     [XmlAttribute]     public int Timeout     {          get { return commandTimeout; }          set { commandTimeout = value; }     }     [XmlArrayItem("ParameterMapping")]     public CommandParameterMappingDataCollection ParameterMappings     {          get { return parameterMappingDataCollection; }          set { parameterMappingDataCollection = value;}     }     [XmlIgnore()]     public CommandParameterMapping this[int index]     {          get { return (parameterMappingDataCollection == null)? null                         : parameterMappingDataCollection[index];}          set          {               if (parameterMappingDataCollection == null)               {                    parameterMappingDataCollection = new                         CommandParameterMappingDataCollection();                    Add(value);               }               else                    parameterMappingDataCollection[index] = value;          }     }     [XmlIgnore()]     public CommandParameterMapping this[string name]     {          get { return (parameterMappingDataCollection == null)? null                         : parameterMappingDataCollection[name];}          set          {               if (parameterMappingDataCollection == null)               {                    parameterMappingDataCollection = new                         CommandParameterMappingDataCollection();                    Add(value);               }               else                    parameterMappingDataCollection[name] = value;          }     }     public void Add(CommandParameterMapping mappingData)     {          if (parameterMappingDataCollection == null)               parameterMappingDataCollection = new                    CommandParameterMappingDataCollection();          parameterMappingDataCollection.Add(mappingData);     } } [Visual Basic] <XmlInclude(GetType(CommandParameterMapping))> _ Public Class CommandMapping     Private commandStatementType As CommandStatementType     Private _commandText As String     Private commandTimeout As Integer     Private parameterMappingDataCollection As _               CommandParameterMappingDataCollection     Public Sub New()     End Sub     Public Sub New(ByVal commandStatementType As _               CommandStatementType, ByVal _commandText As String)          Me.New()          CommandType = commandStatementType          CommandText = _commandText     End Sub     Public Sub New(ByVal commandStatementType As _               CommandStatementType, ByVal _commandText As String, _               ByVal commandTimeout As Integer)          Me.New(commandStatementType, _commandText)          Timeout = commandTimeout     End Sub     <XmlAttribute> _     Public Property CommandType() As CommandStatementType          Get               Return commandStatementType          End Get          Set               commandStatementType = Value          End Set     End Property     <XmlAttribute> _     Public Property CommandText() As String          Get               Return _commandText          End Get          Set               _commandText = Value          End Set     End Property     <XmlAttribute> _     Public Property Timeout() As Integer          Get               Return commandTimeout          End Get          Set               commandTimeout = Value          End Set     End Property     <XmlArrayItem("ParameterMapping")> _     Public Property ParameterMappings() _               As CommandParameterMappingDataCollection          Get               Return parameterMappingDataCollection          End Get          Set               parameterMappingDataCollection = Value          End Set     End Property     <XmlIgnore()> _     Public Default Property Item(ByVal index As Integer) _               As CommandParameterMapping          Get               Return IIf((parameterMappingDataCollection Is _                         Nothing), Nothing, _                         parameterMappingDataCollection(index))          End Get          Set               If parameterMappingDataCollection Is Nothing Then                         parameterMappingDataCollection = New _                         CommandParameterMappingDataCollection()                    Add(Value)               Else                    parameterMappingDataCollection(index) = Value               End If          End Set     End Property     <XmlIgnore()> _     Public Default Property Item(ByVal name As String) _               As CommandParameterMapping          Get               Return IIf((parameterMappingDataCollection Is _                    Nothing), Nothing, _                    parameterMappingDataCollection(name))          End Get          Set               If parameterMappingDataCollection Is Nothing Then                    parameterMappingDataCollection = New _                         CommandParameterMappingDataCollection()                    Add(Value)               Else                    parameterMappingDataCollection(name) = Value               End If          End Set     End Property     Public Sub Add(ByVal mappingData As CommandParameterMapping)          If parameterMappingDataCollection Is Nothing Then               parameterMappingDataCollection = New _                    CommandParameterMappingDataCollection()          End If          parameterMappingDataCollection.Add(mappingData)     End Sub End Class

Once the core functionality, providers, factories, and runtime configuration objects have been created for an application block and its provider implementations, the application block can be used in an application. However, to do so requires manually creating the configuration data for the application block and manually modifying it whenever a change occurs. Because of the deep configuration hierarchy that is needed by this application block, manually creating and modifying the configuration data are exercises that are very prone to error. Besides that, while an application block is certainly usable without any design-time capabilities, it just doesn't feel like an application block that is on par with the application blocks that ship with Enterprise Library. To make it an application block that can be used in the Enterprise Library Configuration Tool as easily as the application blocks that ship with Enterprise Library, the design-time features must be added to the application block.




Fenster Effective Use of Microsoft Enterprise Library(c) Building Blocks for Creating Enterprise Applications and Services 2006
Effective Use of Microsoft Enterprise Library: Building Blocks for Creating Enterprise Applications and Services
ISBN: 0321334213
EAN: 2147483647
Year: 2004
Pages: 103
Authors: Len Fenster

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net