Skip to content

Commit

Permalink
Added AlwaysSerialize and NeverSerialize options
Browse files Browse the repository at this point in the history
for properties.
  • Loading branch information
nemec committed Mar 26, 2013
1 parent fab01bb commit 2574900
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -2,6 +2,8 @@
bin
obj

*.psess

# mstest test results
TestResults
packages/
Expand Down
12 changes: 11 additions & 1 deletion README.md
Expand Up @@ -53,7 +53,17 @@ Features
* Since configuration objects are plain objects, you can define instance
methods, custom getters/setters, and more.
* Validation can be performed on the final configuration object to ensure that
all of its values are valid.
all of its values are valid.
* The configuration manager can mark a property that should be always
serialized or never serialized (`ConfigManager.AlwaysSerialize` and
`ConfigManager.NeverSerialize` respectively). A property marked as
`AlwaysSerialize` will be serialized regardless of whether or not it
changed. A property marked as `NeverSerialize` will never be serialized,
even if it's changed. The latter is useful for properties like passwords.
The password may be stored in a protected configuration file that's only
visible to the ConfigurationManager, but the source where changed
properties are written may be world-readable. This way, changes to the
password within the application are not persisted.

Existing Sources
================
Expand Down
4 changes: 2 additions & 2 deletions SmartConf.Examples/Properties/AssemblyInfo.cs
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
11 changes: 9 additions & 2 deletions SmartConf.UnitTest/Mocks/DummyConfigurationSource.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;

namespace SmartConf.UnitTest.Mocks
{
public class DummyConfigurationSource<T> : IConfigurationSource<T> where T : class
public class DummyConfigurationSource<T> : IConfigurationSource<T> where T : class, new()
{
public bool PrimarySource { get; set; }

Expand Down Expand Up @@ -36,7 +37,13 @@ public void Save(T obj)

public void PartialSave(T obj, IEnumerable<string> propertyNames)
{
SavedObject = obj;
var mrg = new T();
foreach (var prop in propertyNames
.Select(propertyName => typeof (T).GetProperty(propertyName)))
{
prop.SetValue(mrg, prop.GetValue(obj, null), null);
}
SavedObject = mrg;
}
}
}
65 changes: 60 additions & 5 deletions SmartConf.UnitTest/SmartConfUnitTest.cs
Expand Up @@ -191,9 +191,10 @@ public void ChangedProperties_WhereOutHasUpdatedOccupation_ReturnsUpdatedOccupat
{
PrimarySource = true
},
new DummyConfigurationSource<Config>(source3));

configManager.Out.Occupation = "Cool";
new DummyConfigurationSource<Config>(source3))
{
Out = {Occupation = "Cool"}
};

var expected = new Dictionary<string, object>
{
Expand Down Expand Up @@ -235,8 +236,10 @@ public void SaveChanges_WherePrimarySourceIsNotLast_MergesPrimarySourceIntoOutpu
// Act
var newConf = new ConfigurationManager<Config>(
primarySource,
new DummyConfigurationSource<Config>(secondary));
newConf.Out.Age = 88;
new DummyConfigurationSource<Config>(secondary))
{
Out = {Age = 88}
};
newConf.SaveChanges();

// Assert
Expand Down Expand Up @@ -306,5 +309,57 @@ public void Validation_WithMultipleSourcesWhereValidationIsFailingInFirstButOver
new ConfigValidator(), primarySource, secondarySource);
// ReSharper restore ObjectCreationAsStatement
}

[TestMethod]
public void AlwaysSerialize_WithUnchangedProperty_SerializesPropertyAnyway()
{
// Arrange
var secondary = new Config
{
Age = 12,
Name = "Timothy"
};

var primary = new Config();

var expected = new Config
{
Name = "Timothy"
};

var configManager = new ConfigurationManager<Config>(
new DummyConfigurationSource<Config>(secondary),
new DummyConfigurationSource<Config>(primary));

configManager.AlwaysSerialize(t => t.Name);

var actualManager = new DummyConfigurationSource<Config>(new Config());
configManager.SaveChanges(actualManager);

Assert.IsTrue(new ConfigComparer().Equals(
expected, actualManager.SavedObject));
}

[TestMethod]
public void NeverSerialize_WithChangedProperty_DoesNotSerializeProperty()
{
var config = new Config
{
Name = "Timothy"
};

var manager = new ConfigurationManager<Config>(
new DummyConfigurationSource<Config>(config));

manager.NeverSerialize(p => p.Occupation);

manager.Out.Occupation = "Top Secret";

var actualManager = new DummyConfigurationSource<Config>(new Config());
manager.SaveChanges(actualManager);

Assert.IsTrue(new ConfigComparer().Equals(
config, actualManager.SavedObject));
}
}
}
3 changes: 3 additions & 0 deletions SmartConf.sln
Expand Up @@ -29,4 +29,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal
55 changes: 53 additions & 2 deletions SmartConf/ConfigurationManager.cs
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using SmartConf.Sources;
using SmartConf.Validation;
Expand All @@ -25,6 +26,10 @@ public class ConfigurationManager<T> where T : class, new()

private readonly IConfigurationSource<T> _primarySource;

private readonly HashSet<string> _propertiesAlwaysSerialized;

private readonly HashSet<string> _propertiesNeverSerialized;

/// <summary>
/// This object contains the merged configuration settings.
/// It will track all changes and save them out to a file
Expand Down Expand Up @@ -80,6 +85,9 @@ public ConfigurationManager(params IConfigurationSource<T>[] sources)
/// <param name="sources"></param>
public ConfigurationManager(IValidator<T> validator, params IConfigurationSource<T>[] sources)
{
_propertiesAlwaysSerialized = new HashSet<string>();
_propertiesNeverSerialized = new HashSet<string>();

var primarySources = sources.Where(s => s.PrimarySource);
if (primarySources.Count() > 1)
{
Expand Down Expand Up @@ -126,6 +134,38 @@ public ConfigurationManager(IValidator<T> validator, params IConfigurationSource
EnableChangeTracking();
}

/// <summary>
/// Tell the configuration manager to always serialize this property.
/// If the property is currently marked as "never serialize", that
/// status will be removed.
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="prop"></param>
/// <returns></returns>
public ConfigurationManager<T> AlwaysSerialize<TProp>(Expression<Func<T, TProp>> prop)
{
var name = ((MemberExpression) prop.Body).Member.Name;
_propertiesAlwaysSerialized.Add(name);
_propertiesNeverSerialized.Remove(name);
return this;
}

/// <summary>
/// Tell the configuration manager to never serialize this property.
/// If the property is currently marked as "always serialize", that
/// status will be removed.
/// </summary>
/// <typeparam name="TProp"></typeparam>
/// <param name="prop"></param>
/// <returns></returns>
public ConfigurationManager<T> NeverSerialize<TProp>(Expression<Func<T, TProp>> prop)
{
var name = ((MemberExpression)prop.Body).Member.Name;
_propertiesNeverSerialized.Add(name);
_propertiesAlwaysSerialized.Remove(name);
return this;
}

/// <summary>
/// Return a configuration object composed of
/// all sources except the primary source
Expand Down Expand Up @@ -180,9 +220,20 @@ public void SaveChanges(IConfigurationSource<T> source)
var @base = CreateBaseConfig();
var mrg = new T();
mrg.MergeWith(_primarySource.Config);
mrg.MergeWith(Out, @base);
mrg.MergeWith(Out, defaultObject: @base);

// Copy over the values for all "always serialized" properties
foreach (var prop in _propertiesAlwaysSerialized
.Select(propName => typeof (T).GetProperty(propName)))
{
prop.SetValue(mrg, prop.GetValue(Out, null), null);
}

source.PartialSave(mrg,
GetProperties(@base, Out, PropertyStatus.Changed).Select(p => p.Name));
GetProperties(@base, _primarySource.Config, PropertyStatus.Changed)
.Select(p => p.Name)
.Concat(_propertiesAlwaysSerialized)
.Except(_propertiesNeverSerialized));
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions SmartConf/Properties/AssemblyInfo.cs
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.1")]
[assembly: AssemblyFileVersion("1.0.1.1")]
[assembly: AssemblyVersion("1.0.2.0")]
[assembly: AssemblyFileVersion("1.0.2.0")]
2 changes: 1 addition & 1 deletion SmartConf/SmartConf.nuspec
Expand Up @@ -4,7 +4,7 @@
<id>smartconf</id>
<authors>Dan Nemec</authors>
<owners>Dan Nemec</owners>
<version>1.0.2.0</version>
<version>1.0.3.0</version>
<licenseUrl>http://www.opensource.org/licenses/mit-license.php</licenseUrl>
<projectUrl>https://github.com/nemec/smartconf</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
Expand Down

0 comments on commit 2574900

Please sign in to comment.