Design a settings structure

I have a problem when I want to create a settings structure for my application, which should be as optimal as possible in terms of localization, expansion and grouping. I want to group settings for each type of entity (you can think of it as grouping settings on a controller). Settings will be displayed to the user, so that each parameter needs a good name and description, which should be localized. New settings will be introduced by developers, and recompilation will be required.

What I came up with is a class that provides parameters as static properties, so they are easily accessible for use in the entire application in a static way. Settings are loaded when the class is first created (what happens when you request settings), and I use the database to store the settings and use reflection to assign them to the appropriate properties at runtime.

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 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 the database as a lower version of the property name (we ignore the case for loading). The same goes for the localization string that will be stored, for example, FirmSettings_ShowDocumentTaxStatement_Title and FirmSettings_ShowDocumentTaxStatement_Desc . (Convention)

However, this approach does not solve the grouping. The user interface will require some kind of grouping of settings, so the account settings will be displayed in the group. I can imagine a prefix for certain parameters, and then visualize it based on the prefix (another convention).

Do you like this approach? If not, how do you do it? There are many conventions in this approach and that bothers me a bit.

+4
source share
2 answers

You lost me here ...

I see that you are using some kind of Container, so why don't you just enter one instance of one of these settings classes every time you need a link to it? Static class methods + bad for unit testing (what you need for this).

Also, I don't understand why you want to use Reflections / string matching to configure storage / retrieval. If you really have a significant number of settings with complex grouping between them, you will need to spend time creating the proper DAL.

Just notice, your β€œkey” (for example, FirmSettings_ShowDocumentTaxStatement_Title) does not contain a namespace, so if two classes have the same name and the same method, you will get an error that is hard to catch. This is a simple scenario. My point is that a string matching the name of a class + class for identification purposes is not a good idea. (Because I assume that you have a very complex project to resort to in such a management of settings.)

Finally, β€œI don’t know how else you would assign value to static (or regular) properties at runtime other than using reflection.” You can use the class / method / property attributes and have a factory class to pump out (in your case) singleton from your desired settings class. The relevant database column / row connection information may be contained in attributes.

PS Reflection is excellent in terms of performance. Just don't use a static class, use singleton instead, and do the background job when initializing the custom classes. Once a singleton is triggered, you do not have to initialize it again. But no matter what you do, I strongly recommend that you lose the match of strings with class / method names.

PPS Look at the injection of AoP / policies (or this interference with the Microsoft Unity DI container). I believe they can help you?

PPPS Finally, in 3 posts the script is bad English ...

+1
source

IMHO the solution is too complicated. Try to come up with something simpler. Firstly, do not use reflection (performance considerations), and secondly, I would use configuration files (xml) instead of DBs for applications / user settings.

See this comprehensive article on reflection and performance.

0
source

All Articles