Skip to main content

Strongly typed configuration through database

We usually save most of application setting under app.config or web.config with key and value pair under appSetting element. The big problem with those are whenever you need value, typecasting need to be done and also need to be called through key. The other problem I find is with hierarchy of settings like download related setting present should under download element for easy accessing.

To solve strongly typed configuration and hierarchy, ConfigurationManager (http://msdn.microsoft.com/en-us/library/system.configuration.configurationmanager(v=vs.110).aspx) class comes for rescue. You might have seen under web.config where we have sectionGroup and related configuration under elements. To implement it, lot of codes need to written to achieve simplification.

To reduce numbers of codes for implementing XML configuration a very good and free tool is available called "Configuration Section Designer" available on http://csd.codeplex.com/. There we do not have to write any line of code. All things could be managed in UI level only.

Now a days most of the projects are moving to cloud based and as a result we are loosing control to manipulate configuration. For example, Azure resets deployed builds after certain time. So, if you have changed anything in web.config it will get reset to depolyed version. We need to have certain permission to change configuration which creates problem on hosting server.

In my current approach, I have tried to check every drawbacks and simplify the items through database and T4 template. Strongly typed, hierarchy, zero maintenance and easy accessibility through class would be taken care.

First we need to have self relation table to create hierarchy which will result up in creation of classes with its different members.

Mandatory columns for table:
Id: Primary key
ParentId: This is a reference of Id to make relationship. Need to have 0 in case of root elements.
SettingName: Name of the setting. Through this property and classes would be created but we can put spaces between string.
SettingValue: Need to be NULL for parent members/classes and also proper value need to be put for others. Data type would be identified through this only.

That is all we need from table side to populate class. Clean and neat with few rules.

Example for table:
Id|ParentId|SettingName|SettingValue
1|0|System|NULL [NOTE: 0 as root element]
2|1|Application Name|Demo Setting [NOTE: ParantId is set to 1 (System)]
3|0|Password Policy|NULL [NOTE: Another root element]
4|3|Password Age In Days|3 [NOTE: SettingValue as 3 for generating int property]
5|3|Password Constraint|NULL [NOTE: Creating hierarchy under Password policy with NULL value under SettingValue]
6|5|Complex Password|true| [NOTE: SettingValue is set as true for bool]
7|5|Password Min Length|5

Result of above table into hierarchy
-- System [Root]
---- Application Name
-- Password Policy [Root]
---- Password Age In Days
---- Password Constraint [Root level 2]
-------- Complex Password
-------- Password Min Length

I am going to explain some key parts from T4 template codes so that it would be easy to modify and at last will put all codes from T4 code for implementation.

- Connection string: Modify according to your database
 var connectString = "datasource=.;initialcatalog=SettingDemo;integratedsecurity=True";  

- DB query. I have not implemented regular expression in this tutorial but we can take advantage to generate data annotation for regular expression.
 var queryString = "SELECT Id,ParentId, SettingName, 'RegularExpression', SettingValue from SystemSetting";  

-
 List<Tuple<int,int,string, string, string, string>> GetSettingsItems(string connectString)  

through above function we will get tuple as
Item1= Id
Item2 = ParentId
Item3 = Property/Class name
Item4 = Name or Description that could be used for XML comment. [Not used in this example]
Item5 = Regular Expression reserved [Not used in this example]

Item6 = Setting value

- Codes for T4
 <#@ template debug="true" hostSpecific="true" #>  
 <#@ output extension=".cs" #>  
 <#@ Assembly Name="System" #>  
 <#@ Assembly Name="System.Globalization" #>  
 <#@ Assembly Name="System.Data" #>  
 <#@ assembly name="System.Core" #>  
 <#@ import namespace="System" #>  
 <#@ import namespace="System.Text" #>  
 <#@ import namespace="System.Text.RegularExpressions" #>  
 <#@ import namespace="System.IO" #>  
 <#@ import namespace="System.Diagnostics" #>  
 <#@ import namespace="System.Linq" #>  
 <#@ import namespace="System.Collections" #>  
 <#@ import namespace="System.Collections.Generic" #>   
 <#@ include file="EF.Utility.CS.ttinclude"#>  
 <#@ import namespace="System.Data.SqlClient" #>   
 <#  
      var code = new CodeGenerationTools(this);  
      var connectString = "data source=.;initial catalog=SettingDemo;integrated security=True";  
      var settings = GetSettingsItems(connectString);  
 #>  
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 namespace <#= code.VsNamespaceSuggestion()#>  
 {    
      //------------------------------------------------------------------------------  
      // <auto-generated>  
      //   This code was generated from a template and will be overwritten as soon   
      //       as the template is executed.  
      //  
      //   Changes to this file may cause incorrect behavior and will be lost if  
      //   the code is regenerated.  
      // </auto-generated>  
      //------------------------------------------------------------------------------  
      /// <summary>  
      /// Application settings  
      /// </summary>  
      public class Setting  
      {  
      <#   
           var settingCreateMethod = new StringBuilder("public static Setting CreateSetting"  
           + "(Dictionary<string,string> settings)\n\t{\n\t var setting = new Setting();\n");  
      foreach (var setting in settings.Where(seting => seting.Item6 == null))  
      {#>  
      <#if(setting.Item2 == 0){  
        // method generation for root element  
           settingCreateMethod.Append("setting." + setting.Item3 + " = Setting"  
        + setting.Item3 +".Create" + setting.Item3 + "(settings);\n");  
      #>  
      public Setting<#= setting.Item3 #> <#= setting.Item3 #> { get; set; }  
      <#  
   }#>  
           public class Setting<#= setting.Item3 #>  
           {  
           <#   
   // String builder for parameter  
   var childCtor = new StringBuilder();  
   // String builder to put contents under constructor  
   var childCtorProrInit = new StringBuilder();  
   // String builder to create method parameters to use dictionary  
   var mthodDictionary = new StringBuilder();  
   childCtor.Append("public Setting" + setting.Item3 + "(" );  
   mthodDictionary.Append("public static Setting" + setting.Item3 + " Create" + setting.Item3   
   + "(Dictionary<string,string> settings)\n\t{\n\t"+  
   "return new Setting" + setting.Item3 + "(" );  
   #>  
   <#   
           foreach (var childSetting in settings  
            .Where(i=>i.Item2==setting.Item1)  
            .AsQueryable().OrderBy(value => value.Item3))  
     {  
                var dataType = GetType(childSetting.Item6);  
                // In case of class members  
                if(childSetting.Item6 != null)  
       {  
                     childCtor.Append("string " + code.CamelCase(childSetting.Item3) + ", ");  
                     if(dataType != "string")  
         {  
                          childCtorProrInit.Append(childSetting.Item3 + " = " + dataType + ".Parse("+code.CamelCase(childSetting.Item3)+");\n\t");  
         }  
                     else  
                     {  
                          childCtorProrInit.Append(childSetting.Item3 + " = "+code.CamelCase(childSetting.Item3)+";\n\t");  
         }  
                     mthodDictionary.Append("GetValue(settings, \"" + childSetting.Item4 + "\"), \n");  
                #>  
           public <#= dataType #> <#= childSetting.Item3 #> { get; set; }  
      <# }else{  
           // If type is class itself  
           // CTOR for class  
           childCtor.Append("Setting" + childSetting.Item3 + " " + code.CamelCase(childSetting.Item3) + ", ");  
           childCtorProrInit.Append(childSetting.Item3 + " = "+code.CamelCase(childSetting.Item3)+";\n\t");  
           // Get all values for the child class and add in method parameter  
           mthodDictionary.Append("new Setting" + childSetting.Item3 + "\n(\n");  
     foreach (var paramChildSetting in settings  
            .Where(i => childSetting.Item1 == i.Item2)  
            .AsQueryable().OrderBy(value => value.Item3))  
     {  
                mthodDictionary.Append("GetValue(settings, \"" + paramChildSetting.Item4 + "\"), \n");  
     }  
           var strWhole = mthodDictionary.ToString().Remove(mthodDictionary.ToString().Length -3, 3);  
           mthodDictionary.Clear();  
           mthodDictionary.Append(strWhole);  
           mthodDictionary.Append("), \n");  
      #>  
           public Setting<#= childSetting.Item3 #> <#= childSetting.Item3 #> { get; set;}   
                                    <# } #>  
                               <#  
            } ;#>  
            <#= childCtor.ToString().Remove(childCtor.ToString().Length -2, 2 ) + ")\n{\t" + childCtorProrInit + "}\n" #>  
           <#= mthodDictionary.ToString().Remove(mthodDictionary.ToString().Length -3, 3) + ");\n" #>  
                            }  
                            }  
                           <# } #>                       
           internal static string GetValue(Dictionary<string, string> settings, string settingKey)  
     {  
       return settings.FirstOrDefault(x => x.Key.ToLower() == settingKey.ToLower()).Value;  
     }  
 <#= settingCreateMethod.ToString() + "\nreturn setting;}\n"#>       
      }  
      /// <summary>  
   /// System settings key  
   /// </summary>  
      public enum SystemSettingKey  
      {  
      <#   
   foreach (var setting in settings)  
   {  
            #>  
           /// <summary>  
           /// <#= setting.Item4 #>  
           /// </summary>        
           <#= setting.Item3 #>,  
            <#  
   }       
      #>  
      }  
 }  
 <#+  
 private static List<Tuple<int,int,string, string, string, string>> GetSettingsItems(string connectString)  
 {  
   List<Tuple<int,int,string, string, string, string>> settings = new   
           List<Tuple<int,int,string, string, string, string>>();  
   var queryString = "SELECT Id,ParentId, SettingName, 'RegularExpression', SettingValue from SystemSetting";  
   using (SqlConnection connection = new SqlConnection(connectString))  
   {  
     var command = new SqlCommand(queryString, connection);  
     connection.Open();  
     var reader = command.ExecuteReader();  
     // Call Read before accessing data.  
     while (reader.Read())  
     {  
       settings.Add(new Tuple<int,int,string, string, string, string>  
         (int.Parse(reader[0].ToString()),int.Parse(reader[1].ToString()),  
                     GetPascalCase(Convert.ToString(reader[2])),Convert.ToString(reader[2]),   
         Convert.ToString(reader[3]),   
                     (Convert.IsDBNull(reader[4])?null:reader[4].ToString())));  
     }  
     reader.Close();  
   }  
   return settings.AsQueryable().OrderBy(value => value.Item3).ToList();  
 }  
 private string GetType(string value)  
 {  
      string settingType=null;  
      int i; bool b; decimal d;  
      if (int.TryParse(value, out i))  
           settingType = "int";  
      else if (bool.TryParse(value, out b))  
           settingType = "bool";  
      else if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out d))  
           settingType = "decimal";  
      else  
           settingType = "string";  
      return settingType;  
 }  
 private static string GetPascalCase(string name)  
 {  
      return new CultureInfo("en").TextInfo.ToTitleCase(name.ToLower()).Replace(" ", "");  
 }  
 #>  

The above code will generate classes for setting. It is also having a static method to generate "Setting" object based on dictionary value.

To generate setting object:

       var settings = new Dictionary<string, string>();  
       var queryString = "SELECT SettingName, SettingValue from SystemSetting";  
       using (SqlConnection connection = new SqlConnection("data source=.;initial catalog=TestDB;integrated security=True"))  
       {  
         var command = new SqlCommand(queryString, connection);  
         connection.Open();  
         var reader = command.ExecuteReader();  
         // Call Read before accessing data.  
         while (reader.Read())  
         {  
           settings.Add(reader[0].ToString(), reader[1].ToString());  
         }  
         reader.Close();  
       }  
       // That is all needed to populate  
       // NOTE: Much easy if we are using ORM to populate dictionary object  
       Setting setting = Setting.CreateSetting(settings);  

This approach is not just restricted to generate settings only but to generate classes based on table with different level of hierarchy.


NOTE: The generated code from T4 template will not be properly formatted.

Popular posts from this blog

Architecture solution composting Repository Pattern, Unit Of Work, Dependency Injection, Factory Pattern and others

Project architecture is like garden, we plant the things in certain order and eventually they grow in similar manner. If things are planted well then they will all look(work) great and easier to manage. If they grow as cumbersome it would difficult to maintain and with time more problems would be happening in maintenance.

There is no any fixed or known approach to decide project architecture and specially with Agile Methodology. In Agile Methodology, we cannot predict how our end products will look like similarly we cannot say a certain architecture will fit well for entire development lifespan for project. So, the best thing is to modify the architecture as per our application growth. I understand that it sounds good but will be far more problematic with actual development. If it is left as it is then more problems will arise with time. Just think about moving plant vs a full grown tree.

Coming to technical side, In this article, I will be explaining about the various techniques tha…

LDAP with ASPNet Core Identity in MVC Core

Lightweight Directory Access Protocol (LDAP), the name itself explain it. An application protocol used over an IP network to access the distributed directory information service.

AspNet Identity Core is a new offering from Microsoft in replacement of AspNet Identity for managing users.

In this tutorial, we would be looking for implementing LDAP with AspNet Identity Core to allow users to be able to log in through AD or AspNet Identity Core members.

The first and foremost thing is to add references for consuming LDAP. This has to be done by adding reference from Global Assembly Cache (GAC) into project.json

"frameworks": { "net461": { "frameworkAssemblies": { "System.DirectoryServices": "4.0.0.0", "System.DirectoryServices.AccountManagement": "4.0.0.0" } } },
These System.DirectoryServices and System.DirectoryServices.AccountManagement references are used to consume LDAP functiona…

Configuring Ninject, Asp.Net Identity UserManager, DataProtectorTokenProvider with Owin

It can be bit tricky to configure both Ninject and Asp.Net Identity UserManager if some value is expected from DI to configure UserManager. We will look into configuring both and also use OwinContext to get UserManager.

As usual, all configuration need to be done on Startup.cs. It is just a convention but can be used with different name, the important thing is to decorate class with following attribute to make it Owin start-up:

[assembly: OwinStartup(typeof(MyProject.Web.Startup))]
Ninject configuration

Configuring Ninject kernel through method which would be used to register under Owin.

Startup.cs
public IKernel CreateKernel() { var kernel = new StandardKernel(); try { //kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); // TODO: Put any other injection which are required. return kernel; } catch { kernel.Dispose(); throw; }…