Tuesday, November 1, 2011

Injecting Your Web.Config Connection String Into Your T4 Template

I've just recently came across an article explaining how to generate your enumerations from a database table. If you haven't read it yet, I'd highly recommend giving it a read. Generate enum from a database lookup table using T4. I've been looking for a solution to keep my enumerations persistent in both code and my database with minimal effort for awhile now.

However, this post not about how to generate your enums from a T4 template, but instead how to inject your connection strings into a T4 template from your existing web.config or app.config files. No need to hard code your connection strings!

Here's how I dealt with the situation. First I can't take credit for the majority of this code. I got the following include template from Sky Sanders' Blog - Accessing app.config/web.config from T4 template. However, it did not work out of the box for me. I had to make some modifications to get it to work the way I liked. The code I provide below should be able to plugged right into your existing T4 templates and work. So without further ado, let's take a look:



The first difference that you'll notice is the fact that we used the fully qualified reference "System.Configuration.Configuration" for the Configuration objects. I was getting type clashes between the EnvDTE.Configuration and System.Configuration namespaces, so the easy fix was to just fully qualify the objects.

The next and most substantial issue I was facing was that my include and template files were almost always not in the same project as my .config files. The version on Sky Sander's site assumes that this is the case, which for many people may be fine, but was a problem for me. So as you can see in my code above, I added another constructor which accepts a string that defines the location of the project file that contains the .config, relative to the solution file. (I'll show an example later). I moved the code into the new constructor and I now call the new constructor from the base constructor, passing null as the string value.

Now, the new constructor starts out as normal, checking the active project for a .config file. If it finds one, it continues on happily as if nothing was changed. However, if it does not find a .config file in the active project, it will then check to see if a project location was passed into the constructor. In this scenario it will attempt to load the configuration file from that project. If no project location was passed in through the constructor, it will then grab the first startup project in the solution and check it for a .config file.

So basically we have 3 checks in place to find a .config file now:

  1. Check the active project in the solution
  2. Check the constructor parameter
  3. Check the first start-up project


And how do we dynamically get a connection string out? It's pretty simple, we just need to add 3 simple lines of code to our T4 template:



Want to use the .config file in a start-up project? Easy, just call the ConfigurationAccessor constructor without the string parameter and it will pick it up!

2 comments:

  1. hi guy!, first of all excellent blog, i have a big problem with your example:

    when I add the code var config = new ConfigurationAccessor((IServiceProvider)this.Host, @"path\to\ProjectWithConfig.csproj");

    I get this problem:

    Error 9 Compiling transformation: 'Microsoft.VisualStudio.TextTemplatingE1566217D1C68D64CBD73ED1ABC25E45.GeneratedTextTransformation' does not contain a definition for 'Host' and no extension method 'Host' accepting a first argument of type 'Microsoft.VisualStudio.TextTemplatingE1566217D1C68D64CBD73ED1ABC25E45.GeneratedTextTransformation' could be found (are you missing a using directive or an assembly reference?) c:\Users\admin\Desktop\Conadev\Code\Concurrence\Dynamic\Helper\TextTemplate3.tt 16 64

    could you tell me how to solve the problem!

    thanks!!

    ReplyDelete
  2. Your most likely missing an assembly import. Make sure you add the two following lines at the top of your template where your getting the error:

    <#@ assembly name="EnvDTE" #>
    <#@ import namespace="EnvDTE" #>

    That should fix it for you.

    ReplyDelete