Skip to content

Configuring mappings

edpollitt edited this page Sep 25, 2019 · 15 revisions

Simple default value configuration with attributes

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 attribute

More complex configuration with fluent API

AutoConfig.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);
       });

Custom Mappers

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);
}

Clone this wiki locally