Skip to content

Instantly share code, notes, and snippets.

@randyburden
Last active September 29, 2017 17:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save randyburden/2cc72c8f52e97646868e2a9b643987a8 to your computer and use it in GitHub Desktop.
Save randyburden/2cc72c8f52e97646868e2a9b643987a8 to your computer and use it in GitHub Desktop.
C# strongly named application settings/options implementation. Can be used with Dependency Injection or with static helper method AppSettingsOptionsProvider<T>.Bind();.
using System;
using System.Configuration;
using System.Reflection;
namespace Utilities.Options
{
/// <summary>
/// Uses <see cref="ConfigurationManager"/> to populate options.
/// Finds setting names/keys by <see cref="OptionNameAttribute"/>, {PropertyName}, or {ClassName}.{PropertyName}.
/// </summary>
/// <typeparam name="T">The type of options being requested.</typeparam>
public class ConfigurationManagerOptionsProvider<T> : IOptions<T> where T : class, new()
{
/// <summary>
/// The default configured TOptions instance.
/// </summary>
public T Value
{
get
{
return Bind();
}
}
/// <summary>
/// Binds the <see cref="ConfigurationManager"/> values to the options being requested.
/// </summary>
/// <remarks>
/// Uses the following patterns when locating a setting by name/key:
/// Option 1: [OptionNameAttribute]
/// Option 2: {PropertyName}
/// Option 3: {ClassName}.{PropertyName}
///
/// It is recommended to use option 3 as it is more explicit and reduces setting name/key collisions.
/// </remarks>
/// <returns>Configured options instance.</returns>
public static T Bind()
{
var type = typeof(T);
var instance = Activator.CreateInstance<T>();
var properties = type.GetProperties();
foreach (var property in properties)
{
if (!property.CanWrite)
{
continue;
}
var settingType = property.PropertyType == typeof(ConnectionStringSettings) ? SettingType.ConnectionString : SettingType.AppSetting;
string settingName = null;
object settingValue = null;
// Try to get setting value by OptionNameAttribute
settingName = GetOptionNameAttributeValue(property);
if (settingName != null)
{
settingValue = GetSetting(settingName, settingType);
}
// Try to get setting name by property name
if (settingValue == null)
{
settingName = property.Name;
settingValue = GetSetting(settingName, settingType);
}
// Try to get setting name by {ClassName}.{PropertyName}
if (settingValue == null)
{
settingName = $"{type.Name}.{property.Name}";
settingValue = GetSetting(settingName, settingType);
}
if (settingValue == null)
{
continue;
}
var propertyType = property.PropertyType;
object propertyValue;
try
{
propertyValue = Convert.ChangeType(settingValue, property.PropertyType);
}
catch (Exception e)
{
throw new Exception($"An error occurred while converting AppSettings key '{settingName}' to type '{property.PropertyType.Name}' for type '{type.FullName}'", e);
}
try
{
property.SetValue(instance, propertyValue);
}
catch (Exception e)
{
throw new Exception($"An error occurred while setting the value of property '{property.Name}' to value '{propertyValue}' for type '{type.FullName}'", e);
}
}
return instance;
}
private static string GetOptionNameAttributeValue(PropertyInfo property)
{
var attributes = property.GetCustomAttributes(typeof(OptionNameAttribute), true);
if (attributes != null && attributes.Length > 0)
{
var attribute = (OptionNameAttribute)attributes[0];
if (!string.IsNullOrWhiteSpace(attribute.OptionName))
{
return attribute.OptionName;
}
}
return null;
}
/// <summary>
/// Gets the string value of a setting by name.
/// </summary>
/// <param name="settingName">Name of setting.</param>
/// <returns>Value of setting.</returns>
private static object GetSetting(string settingName, SettingType settingType)
{
if (settingType == SettingType.ConnectionString)
{
var connectionStringSettings = ConfigurationManager.ConnectionStrings[settingName];
return connectionStringSettings;
}
var settingValue = ConfigurationManager.AppSettings[settingName];
return settingValue;
}
private enum SettingType
{
AppSetting,
ConnectionString
}
}
}
using System;
namespace Utilities.Options
{
/// <summary>
/// Option/setting name to use when locating a value for the property.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class OptionNameAttribute : Attribute
{
/// <summary>
/// Option/setting name to use when locating a value for the property.
/// </summary>
public virtual string OptionName { get; private set; }
/// <summary>
/// Option/setting name to use when locating a value for the property.
/// </summary>
/// <param name="optionName">Option/setting name to use when locating a value for the property.</param>
public OptionNameAttribute(string optionName)
{
OptionName = optionName;
}
}
}
namespace Utilities.Options
{
/// <summary>
/// Used to retrieve configured TOptions instances.
/// </summary>
/// <remarks>
/// Source/Inspiration: https://github.com/aspnet/Options/blob/dev/src/Microsoft.Extensions.Options/IOptions.cs
/// </remarks>
/// <typeparam name="TOptions">The type of options being requested.</typeparam>
public interface IOptions<out TOptions> where TOptions : class, new()
{
/// <summary>
/// The default configured TOptions instance.
/// </summary>
TOptions Value { get; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment