Click here to Skip to main content
Click here to Skip to main content
Go to top

.NET Library to Easily Save Property Data

, 12 May 2014
Rate this:
Please Sign up or sign in to vote.
[RegSave] your property to the Registry, and more.

Introduction

The following suite of tools can be used in web and desktop applications to make reading and writing data to .config files, the Registry, and the Windows Event Log easier. While the code provided will save data to the System Registry, you can easily substitute the RegistryAccess class with your own provider to save/load data from a database, file, or some other source. The library includes additional tools to encrypt and decrypt data, sort the properties in a class, and serialize data. The registry access functions are designed to work well with the MVVM design pattern, wiring in nicely to "Save" and "Change" event handlers. These tools should work in most web applications, WPF and Windows Forms applications. Given it references things like the Windows Event Log and System Registry, I did not attempt to make this available for Silverlight or mobile platforms. The library includes the following:

  • RegSave - Property Attribute used to indicate a property of a class should be saved to and loaded from the Windows Registry
  • RegistryAccess - Tools to make accessing the registry easier
  • Log - Tools to make writing Windows Event Log entries easier
  • CFG - Tools to make reading from machine.config, web.config, and app.config files easier
  • PropOrder - Attribute to assign sort order to properties of a class
  • StringEncrypter - Encrypt and Decrypt string data
  • SerializableDictionary - Serialize key/value pairs (Dictionary) into text
  • GenericComparer - Used to create an IComparer on the fly, useful for semi-dynamic LINQ sorting
  • Tools/ExtensionMethods - Support functions used across the other tools listed

Using the Code

RegSave

Designed with MVVM ViewModels in mind, RegSave is a system attribute that decorates properties to be saved and loaded from the system registry. This to me is the best feature in this library in that it reasonably gracefully handles the balance between short term and long term storage of data with little overhead code.

[RegSave]
public int PropA { set; get; }

[RegSave]
public string PropB { set; get; }

To enable saving and loading, wire in event handlers to specific Save and Load events (like on application startup and shutdown):

public void OnLoad(…){
// Load all RegSave tagged properties for this class from values saved in the registry
RegSave.LoadRegSaveProperties(this);
}

public void ButtonSaveClick(…){
// Save all RegSave tagged properties to the registry
RegSave.SaveRegSaveProperties(this);
}

Or, if your class implements INotifyPropertyChagned, you can wire RegSave into your OnPropertyChanged handler.

this.PropertyChanged += new PropertyChangedEventHandler((o, e) =>
{
RegSave.ProperyChangeRegSave(o, e.PropertyName);
});

There are additional features to specify if a property should or should not be saved or loaded, and an optional encryption toggle. See the code deep dive section or review comments in line in the code for full details.

RegistryAccess

This class provides easy access to the System Registry key at "HKEY_CURRENT_USER\Software\<company>\<application>\<value>". Features include additional functionality for working with sub keys, and deleting keys, with error management and registry handle management built in (will flush changes to registry before returning from the function call).

RegistryAccess.Set("My Key", "Hello");
…   
var The_Value = RegistryAccess.Get("My Key");

The underlying class, RegistryAccessor, can be used to extend functionality outside of the core application key as well.

var ra = new RegistryAccessor
    ("MyOtherCompany", "MyOtherProject");
ra.Set("My Other Key", "Goodbye");

Log

This class provides easy access to write to the Windows Event Log with embedded error trapping to avoid errors when logging errors, which usually means you are already in an error situation. The actual log entry looks in the assembly chain (Entry, Calling, Executing then finally Current) to try and get the most descriptive "Source" of the message.

Log.Write(EventLogEntryType.Information, "Hello World");
Log.Write(EventLogEntryType.Information, "Test Message: {0}", DateTime.Now);
Log.WriteError("Test Message 2: {0}", DateTime.Now);
Log.WriteError(new Exception("Test Message 3"));

CFG

This class provides easy access to .config files, including error management in the case of missing entries and environment management to allow for different settings across different environments on the path to production. The intended usage would be where the machine.config for each environment has an <appsetting> for "Environment", then each web.config (or app.config) can leverage that setting. For example, the machine.config may contain a setting like this…

<appSettings>
<add key="Environment" value="DEV"/>

Then the Web.config can prefix environment specific settings as needed, or no prefix for a default value.

<appSettings>
<add key="DEV_ServiceURL" value="https://dev...svc"/>
<add key="PROD_ServiceURL" value="https://prod...svc"/>
<add key="ServiceURL" value="https://default...svc"/>

The calling code to get the actual values can toggle between getting environment specific values or not. For example, to get the environment specific setting or the default setting if environment setting can't be found, the code might look like this:

var svc_url = CFG.EnvironmentSetting("ServiceURL");

If we just want a regular app setting, we can do that to, and in case of error (like setting not found) we can indicate what we want returned in line, making the code nice and tidy.

var app_setting = CFG.AppSetting
    ("MySetting", "Value When Not Found");

Since we expect to have some environment awareness, there are additional properties for checking on the environment, like:

bool is_prod = CFG.IsProduction; // Environment that is, 
        // or should be treated like, a production system
bool is_local = CFG.IsLocal; // Local development environment.

PropOrder

This is another system attribute used to define an order for properties of a class, useful when using reflection to display class details or define some sort of execution order.

[PropOrder(10)]
public int Alpha {Set; get;}
[PropOrder(5)]
public DateTime Beta {set; get;}
…
PropertyInfo[] props = PropOrder.GetPropertiesInOrder(this);
// Props[0] would be Beta since 5 < 10
// Props[1] would be Alpha since 10 > 5

StringEncrypter

This library was mostly copied from the web and I believe this to be the original source (www.dalepreston.com), but it was some time ago I copied this code so I am not 100% sure.

This code is used to encrypt and decrypt strings using the RijndaelManaged API. Use some caution when using this in a client-code-only application since any hard-coded private keys could be extracted by disassembling the code. Ideally, this code would only be used on a server where you can expose the Encrypt and Decrypt calls via a secure service.

String enc_string = StringEncrypter.StringEncrypt("Hello World", "password");
// enc_string = "22323201Z0Z4Z...5254"

String dec_string = StringEncrypter.StringDecrypt(enc_string, "password");
// dec_string = "Hello World"

SerializableDictionary

This code was taken from weblogs.asp.net/pwelter34.

Basically this class is just a Dictionary (key/value pairs list) that is XML serializable, so batches of data can be easily stored or returned from service calls.

var sd = new SerializableDictionary<string, DateTime>();
sd.Add("Key1", DateTime.Now);
sd.Add("Key2", new DateTime(2000, 1, 1));

string ser = sd.Serialize();

/*
ser will then look like this: @"
<r>
    <el>
        <k>
            <string>Key1</string>
        </k>
        <v>
            <dateTime>2013-11-06T11:29:53.1727908-08:00</dateTime>
        </v>
    </el>
    <el>
        <k>
            <string>Key2</string>
        </k>
        <v>
            <dateTime>2000-01-01T00:00:00</dateTime>
        </v>
    </el>
</r>
"
*/
var de_ser = SerializableDictionary<string, DateTime>.Deserialize(ser);
var dt = de_ser["Key1"];
// dt will be the valid datetime value set above

GenericComparer

A utility class used to create a dynamic or limited use IComparer. If you really need an IComparer for broad application use, actually create one, right? For example: In this case, the sort algorithm needs to change based on user input so we might do something like this:

GenericCompare<FileInfo> SortCompare;

if (SortMode == Mode.Name)
{
    // Create a sorter based on case insensitive file names..
    SortCompare = new GenericCompare<FileInfo>((fr1, fr2) =>
    {
        return fr1.FullName.ToLower().CompareTo(fr2.FullName.ToLower());
    });
}
else if (SortMode == Mode.Size)
{
    // Create another based on file size…
    SortCompare = new GenericCompare<FileInfo>((fr1, fr2) =>
    {
        return fr1.Size.CompareTo(fr2.Size);
    });
}
…
var res = (new DirectoryInfo("C:\\TestFolder"))
.GetFiles()
.OrderBy(fi => fi, SortCompare);

I know there are other threads on this, like having the one IComparer with a switch in the Compare() function, which is a fine solution as well. This is just a different way to solve the problem.

Tools/ExtensionMethods

Helper tools that are used by the libraries noted above. One that I really like and use on a lot of projects is GetUnderlyingTypeIfNullable, which takes in a Type, and if the type is nullable (i.e., int? or Nullable<int>) will return the non-nullable equivalent type, e.g., typeof(int). The other cool extension method is on PropertyInfo, GetFirstCustomAttribute(), that simplifies getting a single custom attribute. Details on these and other functions are explained in line in the code, with some additional examination in the sections below.

Code Deep Dive

The best way to understand what all this code is doing is to download it and review the comments in the code. To get you started, this section highlights some of the more interesting parts, broken out by class name.

CFG

This is an old library I have been using effectively since .NET 1.1. This library has accommodations for missing config settings and environment awareness that is useful for doing code pushes along a path to production.

public static string AppSetting(string Key, 
         bool EnvironmentSpecific, bool ThrowErrors, string ErrorDefault)
{
    // Get the fully qualified key based on what they are seeking.
    string FullKey = ((EnvironmentSpecific && 
      !String.IsNullOrEmpty(Environment)) ? (Environment + "_") : "") + Key;
…
    // See if the environment specific key exists
    if (!ConfigurationManager.AppSettings.AllKeys.Contains(FullKey))
    {
        // If we can't find the environment specific key, look for a generic key
        FullKey = Key;
    }

    // Get the value
    result = (ConfigurationManager.AppSettings[FullKey] ?? "").Trim();

    if (String.IsNullOrEmpty(result) 
    && !ConfigurationManager.AppSettings.AllKeys.Contains(FullKey))
    {
        // The value was not found, set to default value
        result = ErrorDefault;
        …
    }

    // Return the value if there were no error or they don't want to see errors
    if (!ThrowErrors || String.IsNullOrEmpty(ErrorMessage)) {
        return result;
    }
    …
    // Optional raise errors or return a blank string.
}

GenericCompare

Frankly, there are probably better ways to solve the problem of just-in-time comparers, but I liked this since it was versatile and portable (few dependencies). To add value to it, the null check to guarantee that both values passed into the compare function will not be null makes coding the set compare function easier.

// Execute the compare
public int Compare(T x, T y)
{
    if (x == null || y == null) 
    {
        // These 3 are bell and whistles to handle cases where
        // one of the two is null, to sort to top or bottom respectively
        // the advantage being that the compare function 
        // is guaranteed to have not null values passed to it.
        if (y == null && x == null) { return 0; }
        if (y == null) { return 1; }
        if (x == null) { return -1; }
    }

    try
    {
        // Do the actual compare
        return ComparerFunction(x, y);
    }
    catch (Exception ex) {…}
}

The function must be set on construction as well, which I did more for syntax reason (cleaner looking code) than anything else.

private Func<T, T, int> ComparerFunction { set; get; }

// Constructor
public GenericCompare(Func<T, T, int> comparerFunction)
{
    ComparerFunction = comparerFunction;
}

PropOrder

If you are comfortable with reflection, then there is nothing magical about PropOrder. The key is that it inherits from System.Attribute and then provides a meaningful way to get the attribute, and moreover handle cases where it does not exist. Note that much of this function has code in the shared extension library, explained later.

public static int? GetOrder(PropertyInfo p, int? errorDefeault)
{
    if (p == null)
    {
        return errorDefeault;
    }

    // GetFirstCustomAttribute custom extension explained later
    var pop = p.GetFirstCustomAttribute <PropOrder>(true);

    // If the property does not contain the custom attribute, return the default value
    if (pop == null)
    {
        return errorDefeault;
    }

    return pop.Order;
}

Registry Access

For most of its life, this was a static class, but for this posting, I converted it to use a singleton under the hood to extend flexibility. One of the key problems this library helps solve is that all registry reads/writes for a given application are rooted in the same palace, as defined by the hard coded root of HKCU\Software, and the mandated company and app names to make up the next two nodes in the tree.

// Constructor
public RegistryAccessor(string CompanyRoot, string AppName)
{
    if (String.IsNullOrEmpty(CompanyRoot))
    {
        throw new ArgumentException
        ("COMPANY_ROOT cannot be blank", "CompanyRoot");
    }
    if (String.IsNullOrEmpty(AppName))
    {
        throw new ArgumentException
        ("APP_NAME cannot be blank", "AppName");
    }
    // Root under HKCU\Software\<CompanyRoot>\<AppName>
    COMPANY_ROOT = Path.Combine(@"SOFTWARE\", CompanyRoot);
    APP_NAME = AppName;
}

The other key benefit is the commit pattern, where each hit to the registry is either wrapped in a using() block, or Close() is called when we are done with it, so changes are committed before exiting the function. This may present performance problems for apps that need to write to the registry a lot, so be advised. For better or worse, the library is pretty much geared to only work with String data, which, for the apps I use this in, makes my life easier.

private String GetFrom(string sKey, string sFullPath, string ErrorDefault)
{
    try
    {
        // Using to make sure registry is properly closed when we are done with our read
        using (RegistryKey Reg = Registry.CurrentUser.OpenSubKey(sFullPath))
        {
            // If the key is missing, handle it gracefully
            if (Reg != null)
            {
                // For better or worse, always return strings
                return String.Format("{0}", Reg.GetValue(sKey));
            }
        }
    }
    catch (Exception ex1){…}
    return ErrorDefault;
}

Finally, there is moderately good exception handling to try and gracefully handle cases where you try to get something and the key does not exist.

RegSave

There are several features in RegSave that I like, and is the whole reason I am posting this article. I like the low invasion and simplicity of using Attribute tags to solve problems, the relative simplicity of usage under different models and the general concept which could be extended to other repositories if desired. For example, you could use this same methodology to write to local cookie files instead of the registry in a desktop or Silverlight application. Some noteworthy sections of code follow, with the first being the function to get the attribute details from a given property, or the default one in case it is not found.

public static RegSave GetRegSaveAccess(PropertyInfo pi)
{
    // Negative return option, note that this is 
    // where we set the CurrentProperty for later use
    var ret_atrb = new RegSave
    {
        Load = false,
        Save = false,
        Encrypt = false,
        CurrentProperty = pi
    };

    // Arbitrarily grab the first one
    var found_atrb = pi.GetFirstCustomAttribute(ret_atrb, true);

    // And set return values…
    ret_atrb.Load = found_atrb.Load;
    ret_atrb.Save = found_atrb.Save;
    ret_atrb.Encrypt = found_atrb.Encrypt;

    return ret_atrb;
}

Then later, when we are trying to find all RegSave properties, we rely on that function to get us the CurrentProperty, so we can determine access rights. The actual access rights are merged together between what they have specified in the RegSave attribute and what the property supports natively (r and w variables):

/// <summary>
/// Aggregated access based on a comparison of the CurrentProperty and Load/Save values
/// </summary>
public ReadWriteFlag CurrentAccess
{
    get
    {
        if (CurrentProperty == null)
        {
            return ReadWriteFlag.Neither;
        }

        // Compare the tags desire with what the object will support
        bool r = CurrentProperty.CanRead && Save;
        bool w = CurrentProperty.CanWrite && Load;

        // And return the proper setting
        if (r && w)
        {
            return ReadWriteFlag.Both;
        }
        else if (w)
        {
            return ReadWriteFlag.Write;
        }
        else if (r)
        {
            return ReadWriteFlag.Read;
        }

        return ReadWriteFlag.Neither;
    }
}

All this ties together when getting all properties for a particular type.

public static RegSave[] GetRegSaveProps
    (Type ty, bool readable, bool writable, bool includeEncrypted)
{
    if (ty == null) { return new RegSave[] { }; }

    return ty.GetProperties()
    .Select(p => GetRegSaveAccess(p))
    .Where(rs =>
    {
        // Exclude encrypted items when instructed to do so
        if (rs.Encrypt && !includeEncrypted)
        {
            return false;
        }

        var ca = rs.CurrentAccess;

        // If not readable, but requested returned values must be readable, exclude item
        if (readable && !(ca == ReadWriteFlag.Read || ca == ReadWriteFlag.Both))
        {
            return false;
        }
        // If not writable, but requested returned values must be writable, exclude item
        if (writable && !(ca == ReadWriteFlag.Write || ca == ReadWriteFlag.Both))
        {
            return false;
        }
        return true;
    })
    .ToArray();
}

All of this works with the Save/Load methods highlighted above, where specific save and load event handlers can be wired in to the code, or you can use the canned OnPropertyChagned method, but in either case you end up saving the property to the registry.

public static void SaveRegSaveProperties
    (object o, string subPath, string[] propertyNames)
{
    bool AnyProp = (propertyNames == null || propertyNames.Count() == 0);

    // Find matching RegSave properties
    var matchProps = GetRegSaveProps(o.GetType(), true, false, true)
    .Where(itm => AnyProp || propertyNames.Contains(itm.CurrentProperty.Name));

    // Get the matching property where we can read/write and matches name
    foreach (var rsa in matchProps)
    {
        // Get the changed value as a string
        string strVal = String.Format("{0}", rsa.CurrentProperty.GetValue(o, null));

        // If the value should be encrypted before writing, do so here.
        if (rsa.Encrypt)
        {
            strVal = StringEncrypter.StringEncryptWithCheck
                (strVal, rsa.CurrentProperty.Name);
        }

        // Finally, save the value to the registry.
        RegistryAccess.Set(rsa.CurrentProperty.Name, 
            strVal, RegistryAccessor.EscapeSubPath(subPath));
    }
}

The corresponding load (set class properties from saved values in the registry):

public static void LoadRegSaveProperties
    (object o, string subPath, string[] propertyNames)
{
    bool AnyProp = (propertyNames == null || propertyNames.Count() == 0);

    // Find matching properties
    var matchProps = GetRegSaveProps(o.GetType(), false, true, true)
    .Where(itm => AnyProp || propertyNames.Contains(itm.CurrentProperty.Name));

    foreach (var rsa in matchProps)
    {
        // Get the value as a string
        string strVal = String.Format("{0}", 
          RegistryAccess.Get(rsa.CurrentProperty.Name, SubPath, String.Empty));
        if (rsa.Encrypt)
        {
            // If we need to decrypt it, do so here.
            strVal = StringEncrypter.StringDecryptWithCheck(strVal, rsa.CurrentProperty.Name);
        }

        // Set the property on the object, of proper type
        if (!String.IsNullOrEmpty(strVal))
        {
            rsa.CurrentProperty.SetValue(o, Tools.StringToObject(
                     rsa.CurrentProperty.PropertyType, strVal), null);
        }
    }
}

A small note about subPath in RegSave… Since the original code was designed to work with ViewModels that had unique names per application, always just saving bound to type was okay. However, if you have one class that is used to store generic data, you would likely pass in a sub path to distinguish between the multiple saves and loads. For example:

public class Person {
    [RegSave]
    public string Name {set;get;}
    [RegSave]
    public int Age { set; get; }
}

var person_a = new Person {Name = "Joe", Age  = 10};
var person_b = new Person {Name = "Mary", Age  = 10};

RegSave.SaveRegSaveProperties(person_a, person_a.Name);
RegSave.SaveRegSaveProperties(person_b, person_b.Name);

By passing in the .Name property to the second parameter on SaveRegSaveProperties(), each person's data will be stored in their own registry keys ("HKCU\Software\MyCompany\MyApp\Joe\Person" and "HKCU\Software\MyCompany\MyApp\Mary\Person"), otherwise they would both read and write to "HKCU\Software\MyCompany\MyApp\Person", thus overwriting each other's data.

StringEncrypter

As noted above, this library was mostly copied from a posting on the web. However, I have added an accommodation to deal with blank passwords by way of "KeyPrefix".

Read all the comments in the code before using this section of code, especially if you are developing client side applications where disassembling your code may reveal private keys.

Tools / ExtensionMethods

These two classes have assorted miscellaneous tools that are used across multiple projects (or provide use outside of this project). One that has been helpful is StringToObject, which extends the basic Convert.ChangeType() to work with more different types.

public static object StringToObject(Type outputType, string value)
{
    // Garbage in, garbage out.
    if (outputType == null)
    {
        return null;
    }

    // If the input type was nullable, try to get the non-nullable value first
    var ty = GetUnderlyingTypeIfNullable(outputType);
    bool is_null = String.IsNullOrEmpty(value);

    // If it's an enum value, try to cast the value to an enum of proper type
    if (ty.IsEnum)
    {
        if (is_null)
        {
            return Enum.ToObject(outputType, 0);
        }
        else
        {
            return Enum.Parse(ty, value);
        }
    }
    else
    {
        // Else do a standard type convert
        if (is_null)
        {
            if (outputType == typeof(string))
            {
                return String.Empty;
            }
            else
            {
                return null;
            }
        }
        return Convert.ChangeType(value, ty);
    }
}

The other piece of code that is noteworthy is the ProperyInfo extension method GetFirstCustomAttribute. This solves a few problems I was having with GetCustomAttributes(), dealing with the issue of property not having the custom attribute, or having too many attributes of the same type, and helps in error management by returning a known value on failures, like attribute not found.

public static T GetFirstCustomAttribute<t>
    (this PropertyInfo pi, T errorDefault, bool inherit) 
where T : class
{
    if (pi == null) { return errorDefault; }

    // Get all custom attributes
    var atrbs = pi.GetCustomAttributes(typeof(T), inherit);

    // If none are found, return errorDefault
    if (atrbs == null || atrbs.Length == 0)
    {
        return errorDefault;
    }

    // Arbitrarily grab the first one found, ensuring type is set (as T)
    var ret = atrbs[0] as T;

    // Ensure the cast did work as expected (paranoia)
    if (ret == null)
    {
        return errorDefault;
    }
    return ret;
}

History

  • 06 November 2013: Initial public posting.
  • 08 November 2013: Added demo app.
  • 18 November 2013: Small update to GenericCompare to support comparing two different types.
  • 23 November 2013: Fixed typo in article, accidentally calling CFG "CGF". Code is unchanged.
  • 05 May 2014: Bug Fix in SerializableDictionary. If a class has 2 SerializableDictionary properties implemented, on deserialize, the second property would not deserialize correctly. The proof of this failure (and fix) is in the unit test SerializableDictionary_MultiplePropTest(). The fix is in SerializableDictionary.ReadXml(), the last .Read() in the method was missing. Sincere apologies for the inconvenience.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Brad Joss
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmvpFlorian Rappl30-May-14 21:30 
QuestionUseful Library PinmemberNassir200515-May-14 0:03 
GeneralMy vote of 5 Pinmemberfredatcodeproject12-May-14 10:26 
GeneralMy Vote Pinmemberanezrouk18-Jan-14 9:49 
GeneralMy vote of 5 PinmemberChampion Chen21-Nov-13 17:20 
GeneralMy vote of 5 PinmemberVolynsky Alex8-Nov-13 20:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 12 May 2014
Article Copyright 2013 by Brad Joss
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid