Skip to content

Instantly share code, notes, and snippets.

@christianacca
Created February 15, 2017 12:50
Show Gist options
  • Save christianacca/5024324ac1a011ccd41b727e773e6dd0 to your computer and use it in GitHub Desktop.
Save christianacca/5024324ac1a011ccd41b727e773e6dd0 to your computer and use it in GitHub Desktop.
Custom Castle.Windsor ISubDependencyResolver
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ram.Series5.Data.Settings
{
public class DbSetting
{
public DbSetting()
{
Scope = string.Empty;
}
[Key, Column(Order = 1)]
[Required(AllowEmptyStrings = true)]
public string Scope { get; set; }
[Key, Column(Order = 2)]
[Required]
public string Key { get; set; }
[Required(AllowEmptyStrings = true)]
public string Value { get; set; }
}
}
namespace Ram.Series5.Data.Settings
{
public class DbSettingKey
{
private readonly string _key;
private readonly string _scope;
public DbSettingKey(string scope, string key)
{
_scope = (scope ?? string.Empty).ToLower();
_key = (key ?? string.Empty).ToLower();
}
protected bool Equals(DbSettingKey other)
{
return string.Equals(_scope, other._scope) && string.Equals(_key, other._key);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
var other = obj as DbSettingKey;
return other != null && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
return (_scope.GetHashCode()*397) ^ _key.GetHashCode();
}
}
public static DbSettingKey For(DbSetting setting)
{
return new DbSettingKey(setting.Scope, setting.Key);
}
}
}
using System.ComponentModel;
using Castle.Core;
using Castle.MicroKernel;
using Castle.MicroKernel.Context;
using Ram.Series5.Data.Settings;
using Ram.Series5.Shared.LocalData;
namespace Ram.Series5.Spa.Infrastructure.DI
{
public class DbSettingsConvention : ISubDependencyResolver
{
private const string ThreadStorageKey = "DbSettingsConvention_SettingsCache";
private readonly object locker = new object();
private DbSettingsManager PerRequestSettingsCache
{
get
{
lock (locker)
{
var cache = Local.Data[ThreadStorageKey] as DbSettingsManager;
if (cache == null)
{
Local.Data[ThreadStorageKey] = new DbSettingsManager();
}
return Local.Data[ThreadStorageKey] as DbSettingsManager;
}
}
}
public bool CanResolve(
CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model,
DependencyModel dependency)
{
// Question: why are we using two DbSettingsManager - one returned by DbSettingsManager.Instance and another
// returned by PerRequestSettingsCache?
// Short Answer: To make calls to *Resolve* thread-safe
// Long Answer: When Windsor needs to resolve one of our DbSetting values it will first call this method
// (CanResolve) to establish whether our DbSettingsManager has the setting it's looking for. Assuming the
// setting is found (this method returns true), it then immediately calls Resolve with the
// expectation that the setting will be resolved. The problem is that in the meantime the setting could
// have been removed from DbSettingsManager.Instance. To combat this we add the setting into a per-request
// cache which the Resolve method will use to retrieve the setting
DbSetting setting;
bool isFound = PerRequestSettingsCache.TryGetSetting(null, dependency.DependencyKey, out setting) ||
DbSettingsManager.Instance.TryGetSetting(null, dependency.DependencyKey, out setting);
if (isFound)
{
// note: don't care if the setting is already in collection
PerRequestSettingsCache.TryAdd(setting);
}
return isFound && TypeDescriptor.GetConverter(dependency.TargetType).CanConvertFrom(typeof(string));
}
public object Resolve(
CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model,
DependencyModel dependency)
{
// todo: remove setting from PerRequestSettingsCache so that the next time it's resolved we get the
// latest value
string settingValue = PerRequestSettingsCache.Get(null, dependency.DependencyKey).Value;
return TypeDescriptor.GetConverter(dependency.TargetType).ConvertFrom(settingValue);
}
}
}
using System.Collections.Concurrent;
using System.Collections.Generic;
using Ram.Series5.Shared.Collections;
namespace Ram.Series5.Data.Settings
{
/// <summary>
/// Thread-safe collection of <see cref="DbSetting" />
/// </summary>
public class DbSettingsManager
{
private readonly object _locker = new object();
private readonly ConcurrentDictionary<DbSettingKey, DbSetting> _settings =
new ConcurrentDictionary<DbSettingKey, DbSetting>();
static DbSettingsManager()
{
Instance = new DbSettingsManager();
}
public static DbSettingsManager Instance { get; set; }
public DbSetting Get(DbSettingKey settingKey)
{
return _settings[settingKey];
}
public DbSetting Get(string key)
{
return Get(string.Empty, key);
}
public DbSetting Get(string scope, string key)
{
return Get(new DbSettingKey(scope, key));
}
public bool TryGetSetting(string key, out DbSetting setting)
{
return TryGetSetting(string.Empty, key, out setting);
}
public bool TryGetSetting(string scope, string key, out DbSetting setting)
{
var settingsKey = new DbSettingKey(scope, key);
return _settings.TryGetValue(settingsKey, out setting);
}
public bool TryAdd(DbSetting setting)
{
var settingsKey = DbSettingKey.For(setting);
return _settings.TryAdd(settingsKey, setting);
}
public void SetSettings(IEnumerable<DbSetting> settings)
{
var toAdd = settings.SafeToList();
lock (_locker)
{
_settings.Clear();
foreach (var setting in toAdd)
{
_settings[DbSettingKey.For(setting)] = setting;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment