Practical .NET

Using MEF to Retrieve Connection Strings

Rather than try to ensure that he's getting the right config file for his production and test systems, Peter Vogel lets the application configure itself, using the Managed Extensibility Framework to enable automatic selection of the right connection string

In a recent article , I discussed how to use the Managed Extension Framework (MEF) to dynamically select the class you want to use at run time. While dynamically loading classes sounds very exotic, you can use MEF for very prosaic activities, also. Any time your code needs to make a choice at runtime between several different application components, you should consider MEF.  Every ASP.NET Website or application, for instance, needs to select the right connection string (even if just to choose between the test and production databases). MEF will let your site automatically select the right connection string from an available pool at startup.

MEF also supports an application "self configurating." Rather than tell the site which connection string to pick, code in the site will gather information available at runtime and use that information to select the correct connection string. This example uses an ASP.NET Website, but would work as well in any application. When the code's in place, all you'd need to do in order to change the connection string used by a site is to drop a new DLL into the site's Bin folder.

Building the Framework
I start by adding a Class library to my ASP.NET Web site solution to hold the code for my connection string framework. The first thing I'll add to my class library is an interface that will be shared among all of my connection string objects. Since all I require is a method that returns a string, that interface is very simple:
Public Interface IConnectionString
Function GetConnectionString() As String
End Interface

Then I need to create classes that implement my interface: one class for each connection string I'll need. These classes can be part of this application or in a completely separate DLL. Regardless, the library with these classes will need a reference to the System.ComponentModel.Composition DLL to support MEF. Each of those classes will implement my IConnectionString interface and need an MEF Export attribute to make the class available through MEF. The Export attribute specifies the kind of request that this class will satisfy.

This is the class for my test system's configuration string, with an Export attribute that specifies to MEF that this class is to be used whenever an application needs something that implements my IConnectionString interface:

Imports  System.ComponentModel.Composition

<Export(GetType(ConnectionClasses.IConnectionString))>
Public Class TestConnString
Implements IConnectionString

  Public Function GetConnectionString() As String
Implements IConnectionString.GetConnectionString

    Return "…connection string…"

  End Function

End Class

Specifying the interface is only the start of ensuring that my site selects the correct connection string object. To provide more information about the connection string objects, I define an interface that holds the information my application will need, to select the connection string object it needs. Since that information consists of the site name and whether the site is in test or production, my interface has one property for each of those pieces of information (I add this interface to the same class with my other interface):

Public Interface IConnectionMetaData
ReadOnly Property Site As String
ReadOnly Property IsProd As Boolean
End Interface

I then return to my connection classes and add the ExportMetadata attributes that specify values for each property in my interface. The connection string class I created to be used on the Northwind site when in test mode has ExportMetadata attributes like this:

<Export(GetType(ConnectionClasses.IConnectionString))>
<ExportMetadata("Site", "/Northwind")>
<ExportMetadata("IsProd", False)>
Public Class TestConnString
Selecting the Connection String

I want to set the connection string as early as possible in the life of my site, so I add the code to find the right connection string to the Application Start method in my Global.asax file (that ensures that the site will configure itself the first time someone requests a page). To support MEF, I'll need to add both a reference to System.ComponentModel.Composition and, to my Global.asax file, these three imports:

<%@Import Namespace="System.ComponentModel.Composition"  %>
<%@Import Namespace="System.ComponentModel.Composition.Hosting" %>
%@Import Namespace="System.ComponentModel.Composition.AttributedModelServices" %

Also in my Global.asax file, I need a property to hold the connection string object. Because I want my code to select the right object from all those available, I decorate the class with an ImportMany and declare it as IEnumerable(of Lazy). I further define Lazy as requiring two interfaces: the interface of the classes I'm interested in and the interface that describes the information attached to each class. The result looks like this:

<ImportMany()>
Public Property ConnStrings As IEnumerable(Of Lazy(
Of ConnectionClasses.IConnectionString,
ConnectionClasses.IConnectionMetaData))

To support this code, I need a reference to the class library with the interfaces and any class libraries containing my connection classes.

I'm now ready to add the code to the Application Start method to load the connection string objects, select the right one, and update my configuration file with this code. I'm going to keep all the DLLs with connection string objects in the site's Bin folder. To let MEF seach a folder for matching objects, I need to create a DirectoryCatalog and pass it to a CompositionContainer. This code retrieves the path to my site's Bin folder and builds a DirectoryCatalog for the folder which it then passes to a CompositionContainer:

Dim strPath =  Server.MapPath("Bin")
Dim dirCat As New DirectoryCatalog(strPath)
Dim con As CompositionContainer

Once you've built a CompositionContainer (which can contain many Catalogs), you can call the container's ComposteParts method. That will find all the properties marked with the ImportMany (or Import) attributes, determines the requirements for those properties, then searches the Catalogs associated with the container for objects that meet the requirements on the properties. This code calls my CompositionContainer's ComposeParts method to have MEF search the Bin folder for all the classes implementing the IConnectionString and IConnectionMetaData interfaces specified on my ConnStrings property:

 con = New  CompositionContainer(dirCat)
con.ComposeParts(Me)

All of my ICconnectionString objects will now be associated with the ConnStrings property; next, I need to select the right connection string for the site and mode (test or production). To select the right connection string, my code needs to gather the information to select the right connection string. First, I get access to my site's configuration file and determine if the site is in Debug mode (that tells me whether I'm in test or production mode):

 Dim cfg As Configuration
Dim compile As System.Web.Configuration.CompilationSection
cfg = System.Web.Configuration.
WebConfigurationManager.OpenWebConfiguration("~")
compile = cfg.GetSection("system.web/compilation")
Dim debug As Boolean = True

If compile Is Nothing OrElse
compile.Debug = False Then
debug = False
End If

I use the Request object's URL to retrieve my site name:
Dim siteName As String
siteName = Me.Context.Request.Url.PathAndQuery

I'm ready now to check to find the connection string object attached to my property that's in the right mode, and for this site. That's what this code does; if I don't find a matching connection string object, I throw an exception (you, on the other hand, might want to search for some default connection string object):

 Dim connStr = (From connString In Me.ConnStrings
Where connString.Metadata.IsProd <> debug And
siteName.StartsWith(connString.Metadata.Site)
Select connString).FirstOrDefault
If connStr Is Nothing Then
Throw New Exception("Site not configurable")
End If

If I do find a matching object, I extract my connection string class from the object's Value property:

 Dim ConnectionString As 
ConnectionClasses.IConnectionString = connStr.Value

The rest of the code just checks to see if the connection string returned by this object matches the one already present; if it doesn't, it rewrites the config file (since rewriting the config file triggers a recompile of the site, I don't want to do this any more often than I have to.)

Dim css As ConnectionStringsSection
css = cfg.GetSection("connectionStrings")
Dim setting As New System.Configuration.ConnectionStringSettings
setting = css.ConnectionStrings("SiteConnectionString")

If setting IsNot Nothing Then
If setting.ConnectionString <> ConnectionString.GetConnectionString Then
setting.ConnectionString = ConnectionString.GetConnectionString
cfg.Save()
End If
Else
setting = New System.Configuration.ConnectionStringSettings(
"SiteConnectionString", ConnectionString.GetConnectionString)
css.ConnectionStrings.Add(setting)
cfg.Save()
End If

Right now, to change the connection strings for a site, I'd add or remove DLLs with the connection string objects to the site's Bin folder and restart the site. A worthwhile enhancement might be to put the connection string selection code behind a button on a form, in addition to the Application Start method. That would allow the site to select a new connection string without having to be explicitly restarted. Another option is to extend this framework to support other configuration options (e.g. the endpoints for Web Services requested by the site, values in the application settings) by adding more methods to the IConnectionString interface. But right now, just letting the site manage its own connection strings is a big enough win for me.

Reader Comments:

Mon, Oct 24, 2011 Peter Vogel Canada

Evan: Glad that I (finally) was able to articulate that. But I think that Ben makes a good case for whether this is overkill for most (all?) scenarios. It has also occurred to me that I'm often creating sites that will be installed at several locations (some of my clients sell the software we create onto their customers). This might be a solution that's more appropriate to that scenario (i.e. self-configuring at the client's site and keeping the client away from the web.config file) than the more usual scenarios.

Wed, Oct 19, 2011 Evan Larsen Florida

Peter, I see your point now. I can see creating a dll with the connection string information for each environment. Then you wont have to worry about transformations. Every application can share the same dll and you would only have to manage 1 dll for all your applications (per environment). I can see the benefit to this. Thanks

Wed, Oct 19, 2011 Peter Vogel Canada

Ben: Actually, your question (and Evan's) made me think more deeply about why I do like the MEF solution--it's more about not having to manage the config file than updating them at runtime. But I can also see how it's overkill. I think that I must fundamentally feel that any solution with more code (and less people) is just obviously better. Which is sad, really.

Tue, Oct 18, 2011 Ben

Peter: I guess to each their own. If you think that dropping different DLLs on each server just to provide configuration is better than adding a connection string to a parent web.config on each server, then I guess this works. Seems like overkill to me. I guess I just don't feel like this is a problem that needs to be solved, especially by MEF.

Sat, Oct 15, 2011 Peter Vogel Canada

Evan: I think that I have a better answer to your question ("Why is this preferable to using config transformations") than my first one. What I like about this solution is that I never have to touch the web.config file. In the fully implemented solution it handles all replaceable items (e.g. it includes AppSettings), uses a class file also returns a connection string's name, and has code in the Application Start event that both picks a default value and accepts multiple results in the LINQ statement that it writes to the config file. This means that when I want to change something in the config file, I make a change in a class file with IntelliSense support--I either change an existing class or add a new one. When the time comes to deploy the change, I don't have to force a transformation to generate the production config file or rewrite the config file on the production site (there's a mistake!). I just copy the new DLL into the bin folder and click the button that reruns the MEF code. While I value that process, others (who don't regard working with the config file as equivalent to performing brain surgery on yourself) may not.

Fri, Oct 14, 2011 Peter Vogel Canada

Evan: That's a good question and I'm willing to say that it may well come down to personal preference. Personally, I don't like having the web.config file spread over multiple files as transformation requires (though I've written about transformation: http://visualstudiomagazine.com/articles/2010/09/30/webconfig-to-production-in-aspnet4.aspx). Also, the default assumption in transformation is that there are only two versions--debug and release. On the other hand, with the MEF solution, I do like having one config file for all the things don't change and letting the site select the most appropriate connection string available to it based on any parameters that are important to you (you'll notice I included the site in my example, in addition to debug/release). The MEF solution is also very extensible for anything you might want to control in the config file. While transformation requires every site to create a unique set of transformations, the MEF code would work, unchanged, for any site. Don't get me wrong--I'm not suggesting that the MEF solution is without flaws. With transformation, all the variations to the config file are present in the project and easy to read. With MEF, the complete web.config file doesn't really exist in the project--it's created on the fly (in my case, when the Application Start event executes). The MEF solution is oriented to procedural code while Web transformation is more declarative--and declarative is usually the better way to go. It's probably worthwhile to remember that I really like code generation solutions (wrote a book about it) and favour those kinds of solutions over others.

Fri, Oct 14, 2011 Evan Larsen Florida

So whats the benifit of this over just using a web.config transformation.

Thu, Oct 13, 2011 Peter Vogel Canada

Richard: It should work with an encrypted config file because I'm using the ConfigurationManager class to manipulate the config file--it takes care of all of the encryption/decryption for you. Ben: You're missing a key point--one of the biggest pains in ASP.NET is managing the changes in the cinfig file between production and test (over the years, ASP.NET has had three separate solutions to this issue). Using MEF eliminates the problem by allowing the site to pick the right connection string from the DLLs available to it, using information in the current environment. You stop having to rewrite the config file at all (or have separate test/prod versions).

Thu, Oct 13, 2011 Peter Vogel Canada

Richard: It should work with an encrypted config file because I'm using the ConfigurationManager class to manipulate the config file--it takes care of all of the encryption/decryption for you. Ben: You're missing a key point--one of the biggest pains in ASP.NET is managing the changes in the cinfig file between production and test (over the years, ASP.NET has had three separate solutions to this issue). Using MEF eliminates the problem by allowing the site to pick the right connection string from the DLLs available to it, using information in the current environment. You stop having to rewrite the config file at all (or have separate test/prod versions).

Wed, Oct 12, 2011 Ben

MEF is great, no doubt about it. But this is a really poor example of how to use it. "When the code's in place, all you'd need to do in order to change the connection string used by a site is to drop a new DLL into the site's Bin folder." ... really? Because that seems like more work than changing a web.config value, which would achieve the same exact result. You could do the same runtime calculations and pick a connection string from web.config. I'm glad you decided to write about MEF, but next time try to think up a better example... this one just adds complication to something you can do without MEF.

Wed, Oct 12, 2011 Richard Thomas

Hello Peter, This is great, thanks. One question: will this still work if the ConnectionStrings section of web.config is encrypted? Thanks.

Add Your Comments Now:

Your Name:(optional)
Your Email:(optional)
Your Location:(optional)
Comment:
Please type the letters/numbers you see above