Using T4 to Create an AppSettings Wrapper, Part 2

In the first article in this series we created a basic, static T4 template.  The template allowed us to replace standard boilerplate code for reading an app setting


string setting = ConfigurationManager.AppSettings["someSetting"];

with strongly typed property references like this


var myIntSetting = AppSettings.Default.IntValue;
var myDoubleSettig = AppSettings.Default.DoubleValue;

Here’s a summary of the requirements from the first article (slightly reordered).


  1. All settings defined in the project’s config file should be exposed, by default, as a public property that can be read. I’m not interested in writing to them.
  2. Based upon the default value (in the config file) each setting should be strongly typed (int, double, bool, string).
  3. The configuration file cannot be cluttered with setting-generation stuff. This was the whole issue with the Settings designer in .NET.
  4. Sometimes the project containing the config file is different than the project where the settings are needed (ex. WCF service hosts) so it should be possible to reference a config file in another project.
  5. Some settings are used by the infrastructure (such as ASP.NET) so they should be excluded.
  6. Some settings may need to be of a specific type that would be difficult to specify in the value (ex. a long instead of an int).

In this article we’re going to convert the static template to a dynamic template and begin working on requirements 1-3.  From last time here are the areas of the template that need to be made more dynamic.


  1. The namespace of the type
  2. The name of the type, including pretty formatting
  3. Each public property name and type

Template Structure


Pretty much every T4 template will include a type within a namespace.  A template should follow the standard practices that are currently in use in terms of names and formatting.  Every (code) project has a default namespace.  It is expected that all types added to the project be placed into the default namespace.  However, at least in C#, if a file is placed into a subfolder then the subfolder becomes part of the namespace.  The template should use the default namespace combined with the folder(s) containing the file. Unfortunately this information is not directly accessible to the template.  We’ll have to write some code to get this information but a quick overview of underpinnings of the template is in order.


A template is compiled into a derived type of TextTransformation.  This type exposes the core functionality needed for a template include error reporting, writing to the generated file and context information.  Unfortunately the base type does not expose the information we need so we’ll have to call out to Visual Studio (the host).  In order to do that we first need to set the hostspecific attribute on the template directive to true.  You will probably want to set the debug attribute to true as well to make debugging easier.  Once the host attribute is set we can use the Host property to access information about the template.  This opens up most of the dynamic data we need.


It is important to distinguish between code used for template generation vs code that will end up in the generated file.  Template generation code will reside inside statement blocks (<# #>), expression blocks (<#= #>) or class blocks (<#+ #>).  A statement block is used to execute code during template generation.  Any variables defined in block are accessible to later blocks.  An expression block is used to inject a value into the generated code.  It is normally a: variable defined in a statement block, a function defined in a class block or a property of the template class.  Expression blocks are how we’ll get the dynamic data into the generated code. A class block is used to add members to the generated template class.  Class blocks are useful for creating reusable functions that can be used during template generation.  One limitation of class blocks is that they cannot be followed by statement blocks.  In general this isn’t an issue because a statement block is used to start the code generation and therefore it will be first.


Standard Variant Replacement


Now that we have access to Host we can get the name of the template file (as set in the Add New Item dialog).  The file name (without the extension) will be the name of the settings class so we should go ahead and store off the name in a variable for later use.


<#
    var ClassName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
#>

We’re using Path here so we should also include an import for System.IO since we’ll be needing this namespace later.  Now that we have a variable representing the class name we should go ahead and replace the static AppSettings typename with the type name as determined by the filename using an expression block.


internal partial class <#= ClassName #>

Each time you make a change to the template you should go ahead and save the template.  Check the Error List window to ensure the template compiled correctly and verify the generated file.  Finding template generation errors can be difficult so verifying each change as you go along makes things much easier.  Note that we are currently ignoring the case where the name might contain invalid identifier characters.


Getting the namespace requires a little more work.  There are quite a few ways to get the information but I prefer to get the namespace straight from the project.  Each project item has a custom namespace property but I generally don’t bother using it.  Once you start needing information from VS then you’re going to run into DTE which is the main VS object.  A good understanding of the VS object model will really be beneficial but is beyond the scope of this post so I’m just going to provide the necessary code to get the project associated with the template.  Because this is useful code and we’ll need it later I’m going to place it in a class block at the bottom of the template.  Remember these methods will be part of the type that is generated to back the template generation.


  1. Add assembly directives to include the required Visual Studio assemblies (EnvDTE in this case).
  2. Add import directives to include the DTE namespaces.  Note that I prefer to not import the EnvDTE namespace where the core model is at because it makes it much cleaner to read, in my opinion.
  3. Add the code to a class block at the bottom of the template.  Note there are several helper methods that will be beneficial later.

<#+
public
 EnvDTE.Project ActiveProject
{
    get
    {   if (m_activeProject == null)
        {
            if (DteInstance != null)
            {
                var projects = (Array)DteInstance.ActiveSolutionProjects;

                m_activeProject = (projects != null && projects.Length > 0) ? (EnvDTE.Project)projects.GetValue(0) : null;
            };
        };

        return m_activeProject;
    }
}

public EnvDTE.DTE DteInstance
{
    get
    {
        if (m_dte == null)
            m_dte = (EnvDTE.DTE)((IServiceProvider)Host).GetService(typeof(EnvDTE.DTE));

        return m_dte;
    }
}

public static EnvDTE.ProjectItem FindItem ( EnvDTE.Project source, string itemName, bool recurse )
{
    var items = source.ProjectItems;

    //ProjectItems.Item() will throw if the item does not exist so do it the hard way        
    foreach (EnvDTE.ProjectItem child in items)
    {
        if (String.Compare(child.Name, itemName, true) == 0)
            return child;
    };

    if (recurse)
    {
        foreach (EnvDTE.ProjectItem child in items)
        {
            var item = FindItem(child, itemName, true);
            if (item != null)
                return item;
        };
    };

    return null;
}

public static EnvDTE.ProjectItem FindItem ( EnvDTE.ProjectItem source, string itemName, bool recurse )
{
    var items = source.ProjectItems;

    //ProjectItems.Item() will throw if the item does not exist so do it the hard way        
    foreach (EnvDTE.ProjectItem child in items)
    {
        if (String.Compare(child.Name, itemName, true) == 0)
            return child;
    };

    if (recurse)
    {
        foreach (EnvDTE.ProjectItem child in items)
        {
            var item = FindItem(child, itemName, true);
            if (item != null)
                return item;
        };
    };

    return null;
}

public string GetNamespaceForTemplate ()
{
    //Get the project’s root namespace
    var rootNamespace = ActiveProject.Properties.Item(“RootNamespace”).Value as string;

    //Get the project item for the template file
    var templateItem = FindItem(ActiveProject, Path.GetFileName(Host.TemplateFile), true);

    //Walk backwards until we get to the project root, concatenate each folder to the namespace
    var parentNamespaces = new List<string>();
    var parent = templateItem.Collection.Parent as EnvDTE.ProjectItem;
    while (parent != null)
    {
        parentNamespaces.Add(GetSafePascalName(parent.Name));
        parent = parent.Collection.Parent as EnvDTE.ProjectItem;
    };

    var orderedNamespaces = parentNamespaces as IEnumerable<string>;
    var folderPath = String.Join(“.”, orderedNamespaces.Reverse());

    return (folderPath.Length > 0) ? rootNamespace + “.” + folderPath : rootNamespace;
}

public string GetProjectItemFileName ( EnvDTE.ProjectItem item )
{
    return (item != null && item.FileCount > 0) ? item.FileNames[0] : “”;
}

public string GetSafePascalName ( string baseName )
{
    var builder = new System.Text.StringBuilder();

    //Starts with capital letter or underscore
    bool isFirstLetter = true;
    foreach (var ch in baseName)
    {
        if (isFirstLetter)
        {
            if (Char.IsLetter(ch))
                builder.Append(Char.ToUpper(ch));
            else if (Char.IsDigit(ch) || ch == ‘_’)
                builder.Append(ch);

            isFirstLetter = false;
        } else
        {
            if (Char.IsLetterOrDigit(ch) || ch == ‘_’)
                builder.Append(ch);
        };
    };

    if (builder.Length == 0)
        builder.Append(‘_’);

    return builder.ToString();
}

private EnvDTE.DTE m_dte;
private EnvDTE.Project m_activeProject;
#>

The GetSafePascalName method is used to generate a safe Pascal name.  It is probably a good idea to go back and add a call to this method when setting the ClassName variable from earlier.


<#
   var ClassName = GetSafePascalName(Path.GetFileNameWithoutExtension(Host.TemplateFile));
#>

Finally we can use the new functionality to replace the static namespace.


namespace <#= GetNamespaceForTemplate() #>
{  …

At this point we should be able to move the template file to a different folder and the generated code should update the namespace accordingly.  If we rename the template file then the generated type name should reflect this as well.


We’ve written a lot of code and we’re not done yet.  But the functionality that has been added will be reusable in all the other templates we create.  In the next article in this series we’ll replace the static properties with the real settings from the configuration file.

Tags:

34 Comments on “Using T4 to Create an AppSettings Wrapper, Part 2”

  1. Good post! We will be linking to this great post on our website.

    Keep up the good writing.

  2. An outstanding share! I’ve just forwarded this onto a co-worker who was doing a little research on this. And he actually ordered me breakfast due to the fact that I discovered it for him… lol. So allow me to reword this…. Thank YOU for the meal!! But yeah, thanx for spending the time to discuss this subject here on your web page.

  3. I was very happy to uncover this web site.

    I want to to thank you for ones time for this particularly wonderful read!

    ! I definitely liked every part of it and i
    also have you saved to fav to look at new information on your web site.

  4. Hello, I enjoy reading all of your article. I wanted to
    write a little comment to support you.

  5. I don’t drop many comments, but after browsing through a few of the responses on Using T4 to Create an AppSettings Wrapper, Part 2 – P3.NET. I do have 2 questions for you if you tend not to mind. Could it be only me or does it seem like a few of the comments come across as if they are coming from brain dead individuals? :-P And, if you are posting on additional places, I would like to keep up with everything fresh you have to post. Could you post a list of all of your social community pages like your twitter feed, Facebook page or linkedin profile?

  6. I do agree with all the ideas you’ve introduced to your post. They’re
    very convincing and will certainly work. Nonetheless, the posts
    are very brief for novices. Could you please lengthen them a little from next time?
    Thanks for the post.

  7. Awesome! Its really remarkable article, I have got much clear idea regarding from
    this piece of writing.

  8. When someone writes an piece of writing he/she maintains the plan
    of a user in his/her mind that how a user can be aware of it.

    So that’s why this piece of writing is amazing. Thanks!

  9. Neat blog! Is your theme custom made or did you download it from
    somewhere? A design like yours with a few simple adjustements would really
    make my blog shine. Please let me know where you got your design.
    Thanks

  10. Greetings from Carolina! I’m bored to tears at work so I decided to check out your blog on my iphone during lunch break. I enjoy the information you provide here and can’t wait to take a look when I get home.

    I’m amazed at how quick your blog loaded on my mobile .. I’m not even using WIFI,
    just 3G .. Anyways, very good blog!

  11. Good day! I know this is kinda off topic but I was wondering which
    blog platform are you using for this website?
    I’m getting sick and tired of WordPress because I’ve had issues with hackers and I’m looking at options for another platform. I would be fantastic if you could point me in the direction of a good platform.

  12. Go into your profile for msmvps.com and there are options to disable e-mails from the site.

  13. When I originally commented I clicked the “Notify me when new comments are added”
    checkbox and now each time a comment is added I get several emails with the same comment.
    Is there any way you can remove me from that service? Appreciate it!

  14. Great delivery. Sound arguments. Keep up the good
    work.

  15. Thanks very interesting blog!

  16. Excellent post but I was wondering if you could write a litte more on this subject?
    I’d be very thankful if you could elaborate a little bit further. Cheers!

  17. That is very interesting, You’re an excessively professional blogger. I’ve joined
    your rss feed and look forward to seeking extra of your fantastic
    post. Additionally, I’ve shared your site in my social networks

  18. Wow! At last I got a website from where I
    can truly take helpful information concerning my study and knowledge.

  19. I like the valuable info you provide in your articles.
    I will bookmark your weblog and test once more here frequently.
    I’m quite sure I will be told lots of new stuff right right here! Best of luck for the following!

  20. Oh my goodness! Amazing article dude! Many thanks, However I am going through
    troubles with your RSS. I don’t know why I cannot subscribe to it. Is there anybody getting identical RSS problems? Anyone who knows the answer will you kindly respond? Thanks!!

  21. Marvelous, what a website it is! This webpage gives
    useful information to us, keep it up.

  22. Dag,uitstekende blog!

  23. Hello! This is my first comment here so I just wanted to
    give a quick shout out and say I really enjoy reading your articles.
    Can you recommend any other blogs/websites/forums that cover the same subjects?

    Thank you so much!

  24. Hi, i read your blog from time to time and i own a
    similar one and i was just wondering if you get a lot of
    spam feedback? If so how do you stop it, any plugin or anything you can advise?
    I get so much lately it’s driving me crazy so any help is very much appreciated.

  25. Good day! Would you mind if I share your blog with my facebook group?
    There’s a lot of people that I think would really appreciate your content. Please let me know. Many thanks

  26. I’m not aware of any issues with the RSS feeds other than the sample code that doesn’t generally display properly. I’m trying to resolve that.

    I was able to set up an RSS feed from Internet Explorer and Firefox without any issues.

  27. Oh my goodness! Awesome article dude! Thank you, However I am having difficulties with your RSS.
    I don’t know why I am unable to join it. Is there anyone else having identical RSS problems? Anyone who knows the answer can you kindly respond? Thanx!!

  28. A fascinating discussion is definitely worth comment.
    There’s no doubt that that you should publish more about this subject, it may not be a taboo subject but generally folks don’t
    discuss these subjects. To the next! All the best!

    !

  29. Hi there to every body, it’s my first pay a quick visit of this website; this blog consists of amazing and truly fine stuff in support of visitors.

  30. The expertise shines through. Thanks for taking the time to anwesr.

  31. Fantastic article. Must be bookmarked:)

  32. Hello! Quick question that’s entirely off topic. Do you know how to make your site mobile friendly? My website looks weird when viewing from my apple iphone. I’m trying
    to find a theme or plugin that might be able to correct
    this problem. If you have any recommendations, please share.
    With thanks!

  33. Appreciating the time and energy you put into your website and detailed information you offer.
    It’s awesome to come across a blog every once in a while that isn’t the
    same unwanted rehashed material. Fantastic read!
    I’ve saved your site and I’m adding your RSS feeds to my Google account.

  34. Good day! This is my first visit to your blog!
    We are a group of volunteers and starting a new project in a community in the same niche.
    Your blog provided us beneficial information to work
    on. You have done a extraordinary job!