Tuesday, September 24, 2013

T4 Template for Visual Studio Settings file

Let's say you have .NET application with settings. Visual Studio does a great job generating class with properties that allows to access those settings, and let's say if you renamed the Setting name you will find out all reference errors during compile time, which is great.

However sometimes you might need to get names of Settings properties as constants - you might need it for using those settings in Attribute initializers, which allow only constant parameters.

So here is T4 template that generates subclass with names as constants and names as Enumerable (for strongly typed references):

<#@ template language="C#" debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="System.Xml.dll" #>
<#@ Assembly Name="System.Xml.Linq.dll" #>
<#@ Assembly Name="System.Windows.Forms.dll" #>
<#@ Assembly name="System.Configuration"#>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Specialized"#>
<#@ import namespace="System.Collections.Generic" #> 
<#@ import namespace="System.Configuration" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#
 const string SettingsFile = "Settings.settings";
#>
/* 
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a template.
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 */
using System;
using System.Configuration;
<# 
 
 var doc = XDocument.Load(Path.Combine(this.GetTemplatePath(), SettingsFile));
 var xmlNs = doc.Root.Name.Namespace;
#>
namespace <#= doc.Root.Attribute("GeneratedClassNamespace").Value #>
{
 partial class <#= doc.Root.Attribute("GeneratedClassName").Value #>
 {
        /// <summary>
        /// Contains all settings' names
        /// </summary>
  public class Names
  {
<# foreach(var setting in doc.Descendants(xmlNs+"Setting")) {
  var settingName = setting.Attribute("Name").Value;
#>
<# 
     var settingDescription = setting.Attribute("Description")==null?null:setting.Attribute("Description").Value;
  if(!string.IsNullOrEmpty(settingDescription))
  {
 #>
   /// <summary>
   /// <#= settingDescription #>
   /// </summary>
 <##>
   public const string <#= GetSafeName(settingName) #> = "<#= settingName #>";
<# } #>
  }
 
        /// <summary>
        /// Contains enumeration of all settings, you can use it as strong type key for specific setting
        /// </summary>
  public enum NameEnum
  {
<# foreach(var setting in doc.Descendants(xmlNs+"Setting")) {
    var settingName = setting.Attribute("Name").Value;#>
<# 
     var settingDescription = setting.Attribute("Description")==null?null:setting.Attribute("Description").Value;
  if(!string.IsNullOrEmpty(settingDescription))
  {
 #>
   /// <summary>
   /// <#= settingDescription #>
   /// </summary>
 <##>
   <#= GetSafeName(settingName) #>,
<# } #>
  }
 
        /// <summary>
        /// Gives setting value by name passed as enum
        /// </summary>
        /// <param name="settingName">setting key</param>
        /// <returns>setting value</returns>
  public static object GetValue(NameEnum settingName)
  {
   return Default[settingName.ToString()];
  }
 
        /// <summary>
        /// Gives specified type setting value by name passed as enum
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="settingName"></param>
        /// <returns>setting calue of specified type</returns>
  public static T GetValue<T>(NameEnum settingName)
  {
   return (T)Default[settingName.ToString()];
  }
 }
}
<#+ 
    public string GetTemplatePath ( )
    {
        return Path.GetDirectoryName(Host.TemplateFile);
    }
 
 public string GetSafeName ( string value)
 {
  var builder = new System.Text.StringBuilder();
  
  foreach(var ch in value)
  {
   if (Char.IsLetterOrDigit(ch) || ch == '_')
    builder.Append(ch);
   else if (ch == '.')
    builder.Append('_');   
  };
  
  return builder.ToString();
 }
 #>



You will can use this template for other settings files in your application, in order to do that just change SettingsFile constant.