Created
February 15, 2017 12:50
-
-
Save christianacca/5024324ac1a011ccd41b727e773e6dd0 to your computer and use it in GitHub Desktop.
Custom Castle.Windsor ISubDependencyResolver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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