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.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.