I've a problem where I want to design a settings structure for my app that needs to be as optimal as possible in terms of localization, extension and grouping. I want to group settings per entity type (you can think of it as grouping settings per controller). Settings will get displayed to the user so each setting needs a pretty title and a description both of which need to be localized. New settings will only be introduced by the developers and a recompile will be needed.
What I came up with is a class that exposes settings as static properties so they are easily accessible to be used throughout the application in a static manner. The settings get loaded the first time the class is constructed (which happens when setting is requested) and I use database to store the settings and use reflection to assign them to their corresponding properties at runtime.
It looks like this
public class FirmSettings
{
private static IFirmSettingsRepository _repository { get; set; }
public static bool ShowInvoicePaymentDetails { get; set; }
public static bool ShowInvoiceDiscountValue { get; set; }
public static bool ShowDocumentComment { get; set; }
public static bool ShowDocumentTaxStatement { get; set; }
public static bool ShowDocumentAuthor { get; set; }
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref = "FirmSettings" /> class.
/// </summary>
static FirmSettings()
{
Load();
}
#endregion
#region Load Settings
public static void Load()
{
_repository = MvcApplication.Container.Get<IFirmSettingsRepository>();
Type settingsType = typeof (FirmSettings);
//------------------------------------------------------------
// Enumerate through individual settings nodes
//------------------------------------------------------------
StringDictionary dic = _repository.LoadSettings();
if (dic == null)
{
Save(); // prepares the settings with blank settings
dic = _repository.LoadSettings(); // reload
}
foreach (string key in dic.Keys)
{
//------------------------------------------------------------
// Extract the setting's name/value pair
//------------------------------------------------------------
string name = key;
string value = dic[key];
//------------------------------------------------------------
// Enumerate through public properties of this instance
//------------------------------------------------------------
foreach (PropertyInfo propertyInformation in settingsType.GetProperties(BindingFlags.Public |
BindingFlags.Static))
{
//------------------------------------------------------------
// Determine if configured setting matches current setting based on name
//------------------------------------------------------------
if (propertyInformation.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
//------------------------------------------------------------
// Attempt to apply configured setting
//------------------------------------------------------------
try
{
if (propertyInformation.CanWrite)
{
propertyInformation.SetValue(typeof (FirmSettings),
Convert.ChangeType(value, propertyInformation.PropertyType,
CultureInfo.CurrentCulture), null);
}
}
catch
{
// TODO: Log exception to a common logging framework?
}
break;
}
}
}
// perform resave if there are any new settings
Save();
}
#endregion
#region Save settings
/// <summary>
/// Saves the settings to disk.
/// </summary>
public static void Save()
{
StringDictionary dic = new StringDictionary();
Type settingsType = typeof (FirmSettings);
//------------------------------------------------------------
// Enumerate through settings properties
//------------------------------------------------------------
foreach (PropertyInfo propertyInformation in settingsType.GetProperties(BindingFlags.Public |
BindingFlags.Static))
{
//------------------------------------------------------------
// Extract property value and its string representation
//------------------------------------------------------------
object propertyValue = propertyInformation.GetValue(typeof (FirmSettings), null);
string valueAsString;
//------------------------------------------------------------
// Format null/default property values as empty strings
//------------------------------------------------------------
if (propertyValue == null || propertyValue.Equals(Int32.MinValue) || propertyValue.Equals(Single.MinValue))
{
valueAsString = String.Empty;
}
else
{
valueAsString = propertyValue.ToString();
}
//------------------------------------------------------------
// Write property name/value pair
//------------------------------------------------------------
dic.Add(propertyInformation.Name, valueAsString);
}
_repository.SaveSettings(dic);
}
#endregion
}
Each setting is stored in DB as lowercased version of the property name (for loading we disregard the case). The same will go for localization string which will be stored as, for instance, FirmSettings_ShowDocumentTaxStatement_Title and FirmSettings_ShowDocumentTaxStatement_Desc. (convention)
This approach does not solve grouping however. In the UI, some sort of settings grouping will be required, so the Invoice settings would get displayed in a group. I could introduce a prefix for certain settings, then render it out based on the prefix (another convention).
Do you like this approach? If not, how do you do it? There's lots of conventions in this approach and that's what's bothering me, just a little.