How to provide custom connection string for Logging Application Block instead of using the one in .config file?

Topics: Data Access Application Block, Logging Application Block
Dec 29, 2009 at 12:22 AM

Hi, I'm modifying an existing winforms application to use the Logging Application Block. For historical reasons this app gets its main database connection string from the registry, and I'd like the Logging Application Block to use the same details for logging to the database. How can I do this?

The approaches i can think of are:

1) Create a new TraceListener and implement the same sort of functionality as in FormattedDatabaseTraceListener. If I take this approach, should I inherit from CustomTraceListener, and if so how do I pass an attribute of the formatter to use?

2) Create a new ConfigurationSource that provides different details when asked for the database connection. All other requests would be passed through to a FileConfigurationSource, but when asked for the database connection details the object would read the appropriate bits from the registry instead.

but it's not obvious which is more appropriate, or how to go about doing it. Any suggestions?

I'm using EntLib 3.1.

thanks,

- Rory

Dec 29, 2009 at 3:31 AM

I'd go for the first approach and yes, you need to inherit from the CustomTraceListener.  What attribute of the formatter do you want to pass?  The only property configurable for a Formatter is the Template.  Or are you asking for the trace listener's attribute?  If so, you can pass an attribute through the Attributes property collection.  

 

Sarah Urmeneta
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com

Dec 29, 2009 at 12:45 PM

Thanks for the suggestion. It wasn't obvious to me how to pass the AddStoredProcedureName and WriteStoredProcedureName attributes to my custom tracelistener, nor how to create a Database object since it's normally done through factory methods that use a ConfigurationSource. (I suppose I could have used a non-Entlib db object, but I wanted to mostly copy & paste from FormattedDatabaseTraceListener rather than rewrite all the db logic). So the solution I've come to is based on (2) above. I create a new IConfigurationSource which wraps a FileConfigurationSource, but when GetSection("connectionStrings") is called it first populates the ConnectionStringsSection with a ConnectionStringSettings representing my custom connection string retrieved from the registry instead of from the file:

    public class PSConfigurationSource : IConfigurationSource
{
/// <summary>
/// Name of the connection string that will be set to use the standard connection
/// string from the registry. Anything wanting to reference the RM database should
/// reference this connection string name.
/// </summary>
private const string RMDatabaseName = "RMDatabase";

private IConfigurationSource wrappedSource;

private ConnectionStringsSection cxnStringsSection;

/// <summary>
/// Creates a PSConfigurationSource based on the wrappedSource.
/// </summary>
/// <param name="wrappedSource"></param>
public PSConfigurationSource(IConfigurationSource wrappedSource)
{
this.wrappedSource = wrappedSource;
}

/// <summary>
/// Retrieves the specified <see cref="T:System.Configuration.ConfigurationSection"/>,
/// unless the connectionStrings section is requested in which case our custom
/// config section is returned, which contains our custom connection string.
/// </summary>
/// <param name="sectionName">The name of the section to be retrieved.</param>
/// <returns>
/// The specified <see cref="T:System.Configuration.ConfigurationSection"/>, or <see langword="null"/> (<b>Nothing</b> in Visual Basic)
/// if a section by that name is not found.
/// </returns>
public ConfigurationSection GetSection(string sectionName)
{
if (sectionName=="connectionStrings")
{
EnsureConnectionStringsSectionSet();
return cxnStringsSection;
}
return wrappedSource.GetSection(sectionName);
}

/// <summary>
/// Sets the cxnStringsSection object, populating it with our standard connection
/// string retrieved from the registry.
/// </summary>
private void EnsureConnectionStringsSectionSet()
{
if (cxnStringsSection == null)
{
// Get the connectionStrings section from the config file.
ConfigurationSection configSection = wrappedSource.GetSection("connectionStrings");
if ((configSection != null) && (configSection is ConnectionStringsSection))
cxnStringsSection = configSection as ConnectionStringsSection;
else
cxnStringsSection = new ConnectionStringsSection();

// Add in the RM database settings. Seems that ConnectionStringSettingsCollection[<string>] doesn't have a setter,
// despite it being in the documentation, so need to remove then add in case it's already there.
cxnStringsSection.ConnectionStrings.Remove(RMDatabaseName);
cxnStringsSection.ConnectionStrings.Add(new ConnectionStringSettings(
RMDatabaseName, [[MY_CONNECTION_STRING_RETRIEVED_FROM_REGISTRY]], "System.Data.SqlClient"));
}
}

#region WrappedMethods


/// <summary>
/// Adds a <see cref="T:System.Configuration.ConfigurationSection"/> to the configuration source location specified by
/// <paramref name="saveParameter"/> and saves the configuration source.
/// </summary>
/// <remarks>
/// If a configuration section with the specified name already exists in the location specified by
/// <paramref name="saveParameter"/> it will be replaced.
/// </remarks>
/// <param name="saveParameter">The <see cref="T:Microsoft.Practices.EnterpriseLibrary.Common.Configuration.IConfigurationParameter"/> that represents the location where
/// to save the updated configuration.</param><param name="sectionName">The name by which the <paramref name="configurationSection"/> should be added.</param><param name="configurationSection">The configuration section to add.</param>
public void Add(IConfigurationParameter saveParameter, string sectionName, ConfigurationSection configurationSection)
{
wrappedSource.Add(saveParameter, sectionName, configurationSection);
}

/// <summary>
/// Removes a <see cref="T:System.Configuration.ConfigurationSection"/> from the configuration source location specified by
/// <paramref name="removeParameter"/> and saves the configuration source.
/// </summary>
/// <param name="removeParameter">The <see cref="T:Microsoft.Practices.EnterpriseLibrary.Common.Configuration.IConfigurationParameter"/> that represents the location where
/// to save the updated configuration.</param><param name="sectionName">The name of the section to remove.</param>
public void Remove(IConfigurationParameter removeParameter, string sectionName)
{
wrappedSource.Remove(removeParameter, sectionName);
}

/// <summary>
/// Adds a handler to be called when changes to the section named <paramref name="sectionName"/> are detected.
/// </summary>
/// <param name="sectionName">The name of the section to watch for.</param><param name="handler">The handler for the change event to add.</param>
public void AddSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
{
wrappedSource.AddSectionChangeHandler(sectionName, handler);
}

/// <summary>
/// Removes a handler to be called when changes to section
/// <code>
/// sectionName
/// </code>
/// are detected.
/// </summary>
/// <param name="sectionName">The name of the watched section.</param><param name="handler">The handler for the change event to remove.</param>
public void RemoveSectionChangeHandler(string sectionName, ConfigurationChangedEventHandler handler)
{
wrappedSource.RemoveSectionChangeHandler(sectionName, handler);
}

#endregion 


}

I then use this ConfigurationSource instead of the default. The text "[[MY_CONNECTION_STRING_RETRIEVED_FROM_REGISTRY]]" above is actually replaced with a call to retrieve the connection string. This solution also has the advantage that I don't need to reproduce the functionality of the FormattedDatabaseTraceListener - ie actually handling the database logic.