Last active
August 29, 2015 14:22
-
-
Save SCullman/4739e2aa14c089424ba4 to your computer and use it in GitHub Desktop.
Typed Settings for DNN
This file contains hidden or 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 DotNetNuke.Common; | |
using DotNetNuke.Entities.Modules; | |
using DotNetNuke.Entities.Portals; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; { get { return Get(); } set { Set(value) } } | |
using System.Globalization; | |
using System.Runtime.CompilerServices; | |
namespace AnyNamespace | |
{ | |
public class ModuleScopedSettings : StringBasedSettings | |
{ | |
public ModuleScopedSettings(int moduleId, Hashtable moduleSettings) | |
: base( | |
name => moduleSettings[name] as string, | |
(name, value) => new ModuleController().UpdateModuleSetting(moduleId, name, value) | |
) { } | |
} | |
public class TabModuleScopedSettings : StringBasedSettings | |
{ | |
internal static void UpdateSetting(int moduleId, int tabModuleId, string name, string value) | |
{ | |
if (IsTabModuleSetting (name)) | |
new ModuleController().UpdateTabModuleSetting(tabModuleId, SettingName(name), value); | |
else | |
new ModuleController().UpdateModuleSetting(moduleId, name, value); | |
} | |
internal static bool IsTabModuleSetting(string name) | |
{ | |
return name != "Tab" && name.StartsWith("Tab") && Char.IsUpper(name[3]); | |
} | |
internal static string SettingName(string name) | |
{ | |
return IsTabModuleSetting (name) ? name.Substring(3) : name); | |
} | |
public TabModuleScopedSettings(int moduleId, int tabModuleId, Hashtable moduleSettings) | |
: base( | |
name => moduleSettings[SettingName(name)] as string, | |
(name, value) => UpdateSetting(moduleId, tabModuleId, name, value) | |
) { } | |
} | |
public class PortalScopedSettings : StringBasedSettings | |
{ | |
public PortalScopedSettings(int portalId) | |
: base( | |
name => PortalController.GetPortalSetting(name, portalId, ""), | |
(name, value) => PortalController.UpdatePortalSetting(portalId, name, value, true) | |
) | |
{ } | |
} | |
} |
This file contains hidden or 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 DotNetNuke.Common; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Runtime.CompilerServices; | |
namespace AnyNameSpace | |
{ | |
public interface ISettingsStore | |
{ | |
T Get<T>(T @default = default(T), [CallerMemberName] string name = null); | |
void Set<T>(T value, [CallerMemberName] string name = null); | |
void Save(); | |
} | |
public class StringBasedSettings : ISettingsStore | |
{ | |
Dictionary<string, string> _cache; | |
Dictionary<string, Action> _dirty; | |
Func<string, string> _get; | |
Action<string, string> _set; | |
/// <param name="get">Function to get a value out of your setting store</param> | |
/// <param name="set">Action to save a value back to the setting store</param> | |
public StringBasedSettings(Func<string, string> get, Action<string, string> set) | |
{ | |
Requires.NotNull("Get", get); | |
Requires.NotNull("Set", set); | |
_get = get; | |
_set = set; | |
_cache = new Dictionary<string, string>(); | |
_dirty = new Dictionary<string, Action>(); | |
} | |
// All changes to the settings are recorded and get only executed on demand | |
public void Save() | |
{ | |
foreach (var save in _dirty.Values) save(); | |
_dirty.Clear(); | |
} | |
// CallerMemberName is used for the name of the setting. No more magic strings | |
public string Get(string @default = default(string), [CallerMemberName] string name = null) | |
{ | |
Requires.NotNull("Name", name); | |
if (_cache.ContainsKey(name)) | |
{ | |
return _cache[name]; | |
} | |
else | |
{ | |
var value = _get(name) ?? @default; | |
_cache[name] = value; | |
return value; | |
} | |
} | |
public T Get<T>(T @default = default(T), [CallerMemberName] string name = null) | |
{ | |
//required to behave Get and Get<string> the same | |
if (typeof(T) == typeof(string)) return (T)(object)Get((string)(object)@default, name); | |
var converter = TypeDescriptor.GetConverter(typeof(T)); | |
Requires.NotNull("Converter for T", converter); | |
try | |
{ | |
var defaultValueAsString = converter.ConvertToInvariantString(@default); | |
string value = Get(defaultValueAsString, name); | |
return (T)converter.ConvertFromInvariantString(value); | |
} | |
catch | |
{ | |
return @default; | |
} | |
} | |
public void Set(string value, [CallerMemberName] string name = null) | |
{ | |
Requires.NotNull("Name", name); | |
var modified = !(_cache.ContainsKey(name) && _cache[name] == value); | |
if (modified) | |
{ | |
_cache[name] = value; | |
_dirty[name] = () => _set(name, value); | |
}; | |
} | |
public void Set<T>(T value, [CallerMemberName] string name = null) | |
{ | |
//required to behave Set and Set<string> the same | |
if (typeof(T) == typeof(string)) | |
Set((string)(object)value, name); | |
else | |
{ | |
var converter = TypeDescriptor.GetConverter(typeof(T)); | |
Requires.NotNull("Converter for T", converter); | |
Set(converter.ConvertToInvariantString(value), name); | |
} | |
} | |
} | |
} |
This file contains hidden or 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 dgzfp.tagung.dnn; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Xunit; | |
using FluentAssertions; | |
public class StringBasedSettingsTests | |
{ | |
class SutClass : AnyNamespace.StringBasedSettings | |
{ | |
public SutClass(Func<string, string> get, Action<string, string> set) : base(get, set) { } | |
public SutClass() : base(_get_dummy, _set_dummy) { } | |
public string AString { get { return Get(); } set { Set(value); } } | |
public int AnInt { get { return Get<int>(); } set { Set(value); } } | |
public bool ABool { get { return Get<bool>(); } set { Set(value); } } | |
public DateTime ADate { get { return Get<DateTime>(); } set { Set(value); } } | |
} | |
static Func<string, string> _get_dummy = name => null; //returns null | |
static Action<string, string> _set_dummy = (name, value) => { };//does nothing | |
[Fact] | |
public void Properties_can_be_initialized_from_external_getter() | |
{ | |
Func<string, string> identity = name => name; | |
var sut = new SutClass(identity, _set_dummy); | |
sut.AString.Should().Be("AString"); | |
} | |
[Fact] | |
public void External_setter_is_called_when_a_property_was_changed_and_saved() | |
{ | |
var counter = 0; | |
Action<string, string> _set_counter = (name, value) => counter++; | |
var sut = new SutClass(_get_dummy, _set_counter); | |
sut.AString = ""; | |
counter.Should().Be(0); | |
sut.Save(); | |
counter.Should().Be(1); | |
} | |
[Fact] | |
public void Changing_multiple_properties_a_few_times_to_different_values_should_only_execute_setter_the_last_ones_on_save() | |
{ | |
var counter = 0; | |
Action<string, string> _set_counter = (name, value) => counter++; | |
var sut = new SutClass(_get_dummy, _set_counter); | |
sut.AString = "A"; | |
sut.AnInt = 1; | |
sut.AString = "B"; | |
sut.AnInt = 2; | |
counter.Should().Be(0); | |
sut.Save(); | |
counter.Should().Be(2); | |
} | |
[Fact] | |
public void Updates_to_properties_without_modifications_are_getting_ignored() | |
{ | |
var counter = 0; | |
Action<string, string> _set_counter = (name, value) => counter++; | |
var sut = new SutClass(_get_dummy, _set_counter); | |
sut.AString = "A"; | |
sut.AnInt = 1; | |
sut.Save(); | |
counter.Should().Be(2); | |
sut.AString = "A"; | |
sut.AnInt = 2; | |
sut.Save(); | |
counter.Should().Be(3); | |
} | |
[Fact] | |
public void External_setter_is_called_only_once_per_change() | |
{ | |
var counter = 0; | |
Action<string, string> _set_counter = (name, value) => counter++; | |
var sut = new SutClass(_get_dummy, _set_counter); | |
sut.AString = ""; | |
sut.Save(); | |
counter = 0; | |
sut.Save(); | |
counter.Should().Be(0); | |
} | |
[Fact] | |
public void It_can_set_and_read_string_properties() | |
{ | |
var input = "dnn-connect"; | |
var sut = new SutClass(); | |
sut.AString = input; | |
sut.AString.Should().Be(input); | |
} | |
[Fact] | |
public void It_can_set_and_read_integer_properties() | |
{ | |
var input = 1; | |
var sut = new SutClass(); | |
sut.AnInt = input; | |
Assert.Equal(input, sut.AnInt); | |
} | |
[Fact] | |
public void It_can_set_and_read_boolean_properties() | |
{ | |
var input = true; | |
var sut = new SutClass(); | |
sut.ABool = input; | |
Assert.Equal(input, sut.ABool); | |
} | |
[Fact] | |
public void It_can_set_and_read_date_properties() | |
{ | |
var input = DateTime.Parse("06/06/2016"); | |
var sut = new SutClass(); | |
sut.ADate = input; | |
sut.ADate.Should().Be(input); | |
} | |
[Fact] | |
public void It_returns_default_values_if_external_getter_returns_null() | |
{ | |
var sut = new SutClass(); | |
sut.ADate.Should().Be(default(DateTime)); | |
sut.AString.Should().Be(default(string)); | |
sut.AnInt.Should().Be(default(int)); | |
sut.ABool.Should().Be(default(bool)); | |
} | |
} |
This file contains hidden or 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 DotNetNuke.Common; | |
using DotNetNuke.Entities.Modules; | |
using DotNetNuke.Entities.Portals; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Runtime.CompilerServices; | |
namespace AnyNamespace | |
{ | |
// No more magic strings, setting names are retrieved directly from property names | |
// By convention, TabModuleScopedSettings stores properties beginning with Tab as TabModuleSetting | |
// | |
// Here we are inheriting from a base class. Quicker to write but harder to test | |
public class Settings : TabModuleScopedSettings | |
{ | |
public Settings(int moduleId, Hashtable moduleSettings) : base(moduleId, moduleSettings) { } | |
//normal module setting | |
public bool ShowProjects { get { return Get(true); } set { Set(value); } } //Get<bool> not required as the dype can be retrieved from the default value | |
public string Project { get { return Get<string>(); } set { Set(value) } } | |
public string Description { get { return Get(); } set { Set(value) } } // Get() is a shortcut for Get<string>() | |
public string TableName { get { return Get(); } set { Set(value) } } // Not a TabModuleSetting as the fouth letter isn't uppercase | |
//tabmodulesetting | |
//TabProjects is saved as Project in TabModuleSettings and overrides the module setting Project. | |
public string TabProject { get { return Get(); } set { Set(value) } } | |
} | |
public class MyModule : PortalModuleBase | |
{ | |
Settings _settings; | |
public new Settings Settings | |
{ | |
get | |
{ | |
if (_settings == null) _settings = new Settings(ModuleId, TabModuleId, base.Settings); | |
return _settings; | |
} | |
} | |
// Change Settings | |
public void ToggleShowProjects() | |
{ | |
var current = Settings.ShowProjects; | |
Settings.ShowProjects = !current; | |
// Will only update the setting ShowProjects | |
Settings.Save(); | |
} | |
} | |
} |
This file contains hidden or 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
// Please compare to | |
// https://github.com/DNN-Connect/Map/blob/master/Common/ModuleSettings.cs | |
// | |
// uses object composition, no inheritance from a base class is required | |
using System; | |
using System.Collections; | |
using DotNetNuke.Collections; | |
using DotNetNuke.Common.Utilities; | |
using DotNetNuke.Entities.Modules; | |
using AnyNamespace; | |
namespace Connect.DNN.Modules.Map.Common | |
{ | |
public class ModuleSettings | |
{ | |
internal ISettingsStore _store; | |
public ModuleSettings(int moduleId, Hashtable settings) | |
{ | |
_store = new ModuleScopedSettings(moduleId,settings); | |
} | |
public string View { get { return _store.Get("Home"); } set { _store.Set(value) } } | |
public double MapOriginLat { get { return _store.Get(44.1); } set { _store.Set(value) } } | |
public double MapOriginLong { get { return _store.Get(3.07); } set { _store.Set(value) } } | |
public int Zoom { get { return _store.Get(8); } set { _store.Set(value) } } | |
public string MapWidth { get { return _store.Get("100%"); } set { _store.Set(value) } } | |
public string MapHeight { get { return _store.Get("500px"); } set { _store.Set(value) } } | |
public string Version = typeof(ModuleSettings).Assembly.GetName().Version.ToString(); | |
public void SaveSettings() | |
{ | |
_store.Save(); | |
} | |
public static ModuleSettings GetSettings(ModuleInfo ctlModule) | |
{ | |
return new ModuleSettings(ctlModule.ModuleId, clModule.ModuleSettings); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I've added this code as a branch in DNN's repo, so we can iterate on it via pull requests and try it out: https://github.com/dnnsoftware/Dnn.Platform/tree/research/typed-settings-API