-
Notifications
You must be signed in to change notification settings - Fork 8
Configuring mappings
edpollitt edited this page Sep 25, 2019
·
15 revisions
Note: configuring default values with the DefaultValue attribute is available since version 1.4.0
Adding the DefaultValue attribute automatically makes the property optional in the configuration file.
Code:
using System.ComponentModel;
public interface IMyServiceConfiguration
{
string Endpoint { get; }
[DefaultValue(443)]
int Port { get; }
[DefaultValue(true)]
bool UseSSL { get; }
}Configuration:
<configuration>
<configSections>
<section name="myService" type="Nerdle.AutoConfig.Section, Nerdle.AutoConfig" />
</configSections>
<myService endpoint="https://localhost" port="8443" />
</configuration>Resulting configuration once mapped with default attributes:
var configuration = AutoConfig.Map<IMyServiceConfiguration>();
Assert.AreEqual(configuration.Endpoint, "https://localhost");
Assert.AreEqual(configuration.Port, 8443); // default value overridden in configuration file
Assert.AreEqual(configuration.UseSSL, true); // from default value attributeAutoConfig.WhenMapping<IExampleConfiguration>(
mapping =>
{
// override the default camelCasing
mapping.UseMatchingCase();
// map property 'Foo' from config element/attribute named 'foofoo'
mapping.Map(x => x.Foo).From("foofoo");
// mark property 'Bar' as optional, no exception will be thrown if
// the element/attribute is missing
mapping.Map(x => x.Bar).Optional();
// specify a default value when the element/attribute is missing
mapping.Map(x => x.Baz).OptionalWithDefault("baz");
// register a custom mapper
mapping.Map(x => x.Qux).Using<MyCustomMapper>();
// register a custom mapper instance - use this overload if your
// custom mapper doesn't implement a parameterless constructor
mapping.Map(x => x.Quux).Using(myCustomMapperInstance);
// chaining everything together, it's a fluent interface
mapping.Map(x => x.Corge)
.From("wherever")
.Using<MyCustomMapper>()
.OptionalWithDefault(DateTime.Now);
});A custom mapper can be specified by implementing the IMapper interface.
public interface IMapper
{
object Map(XElement element, Type type);
}For example, imagine we wanted to map values from comma delimited strings:
<configuration>
<configSections>
<section name="customMapperExample"
type="Nerdle.AutoConfig.Section, Nerdle.AutoConfig" />
</configSections>
<customMapperExample>
<foo>1,2,3,4,5</foo>
<bar>one,two,three</bar>
<baz>monday,tuesday</baz>
</customMapperExample>
</configuration>We could create a custom mapper such as
class CommaSeparatedListMapper : IMapper
{
public object Map(XElement element, Type type)
{
var isEnumerable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
if (!isEnumerable)
throw new InvalidOperationException("Expected an IEnumerable<> but got " + type);
var enumerableType = type.GetGenericArguments().First();
var converter = TypeDescriptor.GetConverter(enumerableType);
if (converter.CanConvertFrom(typeof(string)))
{
var values = element.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(value => converter.ConvertFromString(value))
.ToList();
var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(enumerableType));
var listAdd = list.GetType().GetMethod("Add");
values.ForEach(value => listAdd.Invoke(list, new[] { value }));
return list;
}
throw new InvalidOperationException("Cannot convert string to type " + enumerableType);
}
}public interface ICustomMapperExample
{
IEnumerable<int> Foo { get; }
IEnumerable<string> Bar { get; }
IEnumerable<DayOfWeek> Baz { get; }
}[Test]
public void Using_a_custom_mapper()
{
AutoConfig.WhenMapping<ICustomMapperExample>(
mapping =>
{
mapping.Map(x => x.Foo).Using<CommaSeparatedListMapper>();
mapping.Map(x => x.Bar).Using<CommaSeparatedListMapper>();
mapping.Map(x => x.Baz).Using<CommaSeparatedListMapper>();
});
var result = AutoConfig.Map<ICustomMapperExample>();
result.Foo.Should().Equal(1, 2, 3, 4, 5);
result.Bar.Should().Equal("one", "two", "three");
result.Baz.Should().Equal(DayOfWeek.Monday, DayOfWeek.Tuesday);
}