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(…){
RegSave.LoadRegSaveProperties(this);
}
public void ButtonSaveClick(…){
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;
bool is_local = CFG.IsLocal;
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);
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");
String dec_string = StringEncrypter.StringDecrypt(enc_string, "password");
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();
var de_ser = SerializableDictionary<string, DateTime>.Deserialize(ser);
var dt = de_ser["Key1"];
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)
{
SortCompare = new GenericCompare<FileInfo>((fr1, fr2) =>
{
return fr1.FullName.ToLower().CompareTo(fr2.FullName.ToLower());
});
}
else if (SortMode == Mode.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)
{
string FullKey = ((EnvironmentSpecific &&
!String.IsNullOrEmpty(Environment)) ? (Environment + "_") : "") + Key;
…
if (!ConfigurationManager.AppSettings.AllKeys.Contains(FullKey))
{
FullKey = Key;
}
result = (ConfigurationManager.AppSettings[FullKey] ?? "").Trim();
if (String.IsNullOrEmpty(result)
&& !ConfigurationManager.AppSettings.AllKeys.Contains(FullKey))
{
result = ErrorDefault;
…
}
if (!ThrowErrors || String.IsNullOrEmpty(ErrorMessage)) {
return result;
}
…
}
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.
public int Compare(T x, T y)
{
if (x == null || y == null)
{
if (y == null && x == null) { return 0; }
if (y == null) { return 1; }
if (x == null) { return -1; }
}
try
{
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; }
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;
}
var pop = p.GetFirstCustomAttribute <PropOrder>(true);
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.
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");
}
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 (RegistryKey Reg = Registry.CurrentUser.OpenSubKey(sFullPath))
{
if (Reg != null)
{
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)
{
var ret_atrb = new RegSave
{
Load = false,
Save = false,
Encrypt = false,
CurrentProperty = pi
};
var found_atrb = pi.GetFirstCustomAttribute(ret_atrb, true);
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):
public ReadWriteFlag CurrentAccess
{
get
{
if (CurrentProperty == null)
{
return ReadWriteFlag.Neither;
}
bool r = CurrentProperty.CanRead && Save;
bool w = CurrentProperty.CanWrite && Load;
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 =>
{
if (rs.Encrypt && !includeEncrypted)
{
return false;
}
var ca = rs.CurrentAccess;
if (readable && !(ca == ReadWriteFlag.Read || ca == ReadWriteFlag.Both))
{
return false;
}
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);
var matchProps = GetRegSaveProps(o.GetType(), true, false, true)
.Where(itm => AnyProp || propertyNames.Contains(itm.CurrentProperty.Name));
foreach (var rsa in matchProps)
{
string strVal = String.Format("{0}", rsa.CurrentProperty.GetValue(o, null));
if (rsa.Encrypt)
{
strVal = StringEncrypter.StringEncryptWithCheck
(strVal, rsa.CurrentProperty.Name);
}
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);
var matchProps = GetRegSaveProps(o.GetType(), false, true, true)
.Where(itm => AnyProp || propertyNames.Contains(itm.CurrentProperty.Name));
foreach (var rsa in matchProps)
{
string strVal = String.Format("{0}",
RegistryAccess.Get(rsa.CurrentProperty.Name, SubPath, String.Empty));
if (rsa.Encrypt)
{
strVal = StringEncrypter.StringDecryptWithCheck(strVal, rsa.CurrentProperty.Name);
}
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)
{
if (outputType == null)
{
return null;
}
var ty = GetUnderlyingTypeIfNullable(outputType);
bool is_null = String.IsNullOrEmpty(value);
if (ty.IsEnum)
{
if (is_null)
{
return Enum.ToObject(outputType, 0);
}
else
{
return Enum.Parse(ty, value);
}
}
else
{
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; }
var atrbs = pi.GetCustomAttributes(typeof(T), inherit);
if (atrbs == null || atrbs.Length == 0)
{
return errorDefault;
}
var ret = atrbs[0] as T;
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.