Conventions - maurosampietro/UltraMapper GitHub Wiki
In order to automatically associate members between source and target objects (ie: identify a mapping), UltraMapper relies on conventions.
UltraMapper provides the following built-in conventions:
-
DefaultConvention:
Each source object's member is tested against each target object's member.
Each member pair is tested against a set of rules.
A mapping is formed when a pair of members satisfies the set of rules.By default only public properties are taken into account for mappings resolution, both on source and target objects and the only matching rule is to find members having the same name, ignoring text-casing.
The default configuration looks like this:
this.Conventions = new MappingConventions( cfg =>
{
cfg.GetOrAdd<DefaultConvention>( conv =>
{
conv.SourceMemberProvider.IgnoreProperties = false;
conv.SourceMemberProvider.IgnoreFields = true;
conv.SourceMemberProvider.IgnoreNonPublicMembers = true;
conv.SourceMemberProvider.IgnoreMethods = true;
conv.TargetMemberProvider.IgnoreProperties = false;
conv.TargetMemberProvider.IgnoreFields = true;
conv.TargetMemberProvider.IgnoreNonPublicMembers = true;
conv.TargetMemberProvider.IgnoreMethods = true;
conv.MatchingRules.GetOrAdd<ExactNameMatching>( rule => rule.IgnoreCase = true );
} );
} );
-
ProjectionConvention:
Enables flattening. Consider the following classes:
private class Customer
{
public string Name { get; set; }
}
private class Product
{
public decimal Price { get; set; }
public string Name { get; set; }
}
private class Order
{
public Customer Customer { get; set; }
public Product Product { get; set; }
}
private class OrderDto
{
public string CustomerName { get; set; }
public decimal ProductPrice { get; set; }
}
This convention will flatten and resolve like this:
var order = new Order
{
Customer = new Customer
{
Name = "George Costanza"
},
Product = new Product
{
Name = "Bosco",
Price = 4.99m
}
};
var mapper = new Mapper( cfg =>
{
cfg.Conventions.GetOrAdd<ProjectionConvention>();
} );
var orderDto = mapper.Map<OrderDto>( order );
Assert.IsEqual( order.Customer.Name == orderDto.CustomerName );
Assert.IsEqual( order.Product.Price == orderDto.ProductPrice );
You can edit this conventions by adding other matching rules or built your own convention from the ground up implementing IMappingConvention interface.
A matching rule determines if two class members can be paired to form a mapping.
UltraMapper provides the following built-in matching rules:
-
ExactNameMatching:
Matches if two properties have the same name
-
PrefixMatching:
Matches if (sourceName == prefix + targetName) or (targetName == prefix + sourceName)
-
SuffixMatching:
Matches if (sourceName == targetName + suffix) or (targetName == sourceName + suffix)
-
MethodMatching:
This is a basically a specialized PrefixMatching used to map between different member types (fields, properties, methods).
Matches, for example, a property named PropertyA in the source object with a method called Set_PropertyA in the target object -
TypeMatching:
Two properties match if the source type is of the same type or (optionally) implicitly/explicitly convertible to the target type.
This rule is usually used in combination with at least one name-checking rule to make sense.
Suppose you have the following to objects and you wanted to resolve mappings having specific prefixes or suffixes like 'DTO' you can do the following:
public class Person
{
public DateTime Birthday { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
}
public class PersonDto
{
public DateTime birthday { get; set; }
public string FirstNameDTO { get; set; }
public string DTOLastName { get; set; }
public string EmailAddress { get; set; }
}
var config = new Configuration( cfg =>
{
cfg.Conventions.GetOrAdd<DefaultConvention>( conventionConfig =>
{
conventionConfig.MatchingRules
.GetOrAdd<PrefixMatching>( ruleConfig => ruleConfig.IgnoreCase = true );
.GetOrAdd<SuffixMatching>( ruleConfig => ruleConfig.IgnoreCase = true );
} );
} );
var mapper = new Mapper( config );
mapper.Map( source, target );
To be considered a match, at least one rule must match.
Of course, you can provide your custom matching rules. Tipically, depending on your custom rule, you should implement either INameMatchingRule or ITypeMatchingRule.
A convention can be composed of many MatchingRules, each one checking a different feature of a class member.
In order to determine if a group of rules identifies a match, and thus, a mapping, we need a mechanism capable of putting in relation the outcome of each rule.
Consider the following configuration:
var config = new Configuration( cfg =>
{
cfg.Conventions.GetOrAdd<DefaultConvention>( conventionConfig =>
{
conventionConfig.MatchingRules
.GetOrAdd<ExactNameMatching>() //name-checking rule
.GetOrAdd<PrefixNameMatching>() //name-checking rule
.GetOrAdd<SuffixNameMatching>() //name-checking rule
.GetOrAdd<TypeMatching>(); //type-checking rule
} );
} );
} );
We can consider two members compliant to the convention if **ANY **of the three name-cheking rules match **AND **the type-checking rule matches.
The default matching rule evaluator groups matching rules by the feature they check and then consider the convention respected if for all groups at least one rule matches.