Automapper | Only Map if not default / initilized value - c#

I would like to have a generic way to map properties only if they are changed and not the default value.
So let's say you have these two classes
public class Source
{
public string Foo { get; set; } = "Foo";
public string Bar { get; set; } = "Bar";
}
public class Destination
{
public string Foo { get; set; } = "Foo of Destination";
public string Bar { get; set; } = "Bar of Destination";
}
Now I want to map Source to Destination and if the property of Source.Foo is still "Foo" it should change to "Foo of Destination", but when the value of Source.Foo is different from "Foo" (the default) it should really map the property.
I could not find a way to do this with automapper.
[TestMethod]
public void ChangeAllProperties()
{
var source = new Source();
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, defaultDestination.Bar); // Should be the Default Value of "Destination"
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void ChangeDefaultProperty()
{
var source = new Source();
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void AlLChanged()
{
var source = new Source();
source.Foo = "Custom Foo!";
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, source.Foo); // Should be Value of Source
}

Check out Automapper conditional mapping - link
Basically, you can do something like this (from the samples):
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
});
opt.Condition() makes sure your condition is met before actually doing any mapping of that particular member.

The opt.Condition() idea from Plamen Yordanov was the right path. I was able to create something more generic, so i dont need to define this for every member and checks the default value of the properties.
This is the solution i came up with. It only checks the default value if the destination member also exists in the source with the same name and type.
public class FooBarProfile: Profile
{
public FooBarProfile()
{
CreateMap<Source, Destination>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
// And if you need reversed...
CreateMap<Destination, Source>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
}
private static bool OnlyIfChanged<TSource>(TSource src, MemberInfo destinationMember) where TSource : new()
{
// Check if DestinationMember exists in TSource (same Name & Type)
var sourceMember = typeof(TSource).GetMembers().FirstOrDefault(e => e.Name == destinationMember.Name && e.MemberType == destinationMember.MemberType);
if (sourceMember != null)
{
var defaultOfSource = new TSource();
// Map only if Value of Source is differnent than the default soruce value
return (GetValue(sourceMember,src)?.Equals(GetValue(sourceMember,defaultOfSource)) == false);
}
return true;
}
// Helper-Method to get Value from a MemberInfo
private static object GetValue(MemberInfo memberInfo, object forObject)
{
switch (memberInfo.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberInfo).GetValue(forObject);
case MemberTypes.Property:
return ((PropertyInfo)memberInfo).GetValue(forObject);
default:
throw new ArgumentException("Member is not Field or Property.", nameof(memberInfo));
}
}
}

Related

MongoDB mapping C#

How can you map Value object that has different data type in constructor than property type.
public class Information : ValueObject
{
private const string Delimiter = ",";
public Information(string value)
{
Value = SetValueAsReadOnlyList(value);
}
public ReadOnlyCollection<int> Value { get; }
private ReadOnlyCollection<int> SetValueAsReadOnlyList(string value)
{
var collection = value
.Split(Delimiter)
.Select(x =>
{
if(int.Parse(x, out var result))
{
return result;
}
throw new ParseStringToIntDomainException(x);
}).ToList();
return collection.AsReadOnly();
}
}
Mongo map will look like this, which is not working because x.Value is of type string and constructor is expecting ReadOnlyCollection
public class InformationMap : IBsonClassMap
{
public void Map()
{
if (BsonClassMap.IsClassMapRegistered(typeof(Information)))
{
return;
}
BsonClassMap.RegisterClassMap<Information>(map =>
{
map.MapCreator(x => new Information(x.Value));
map.MapProperty(x => x.Value);
});
}
}
I don't think it's possible. I can wrap Value to another nested data type with it's own mapping. But I would need to define transformation inside InformationMap class.

Transform property before Fluent Validation

I need to transform my property (remove white space on a string) first before applying validation.
Specifically I want to check if a string is part of a enum but the string may contain whitespaces (which enums don't allow far as I know)
Something like...
RuleFor(x => x.Value).IsEnumName(typeof(EnumType))
(x.Value should have whitespaces removed first)
The FluentValidation method Transform is designed for this case. The following uses the basic white space remover from Everson's answer:
RuleFor(x => x.Value)
.Transform(x => x.Replace(" ", string.Empty))
.IsEnumName(typeof(EnumType));
I'd opt to go with a stronger white space remover that catches tabs etc
RuleFor(x => x.Value)
.Transform(x => new string(x.Where(c => !Char.IsWhiteSpace(c)).ToArray()))
.IsEnumName(typeof(EnumType));
No need for a custom validator (e.g., Must) and personally I'd avoid writing one unless there was no other way.
I'd pop that white space remover into an extension (MVP, you should handle the null case; my preference would be a null guard but that's another topic):
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target){
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
Then the rule is a lot more readable:
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
Something to be aware of: I found that if Transform returned null the IsEnumName rule will pass. Personally I don't like that so I'd include a When rule builder option to only test when Value is provided, or a not empty rule to ensure it is provided.
Working LINQPad sample:
public enum EnumType
{
Value1,
Value2,
Value3
}
public class Foo
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType));
.When(x => x != null);
}
}
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target)
{
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
void Main()
{
var validator = new FooValidator();
var foo1 = new Foo { Id = Guid.NewGuid(), Value = "Value 1" };
var result1 = validator.Validate(foo1);
Console.WriteLine(result1.IsValid);
var foo2 = new Foo { Id = Guid.NewGuid(), Value = "Value2" };
var result2 = validator.Validate(foo2);
Console.WriteLine(result2.IsValid);
var foo3 = new Foo { Id = Guid.NewGuid(), Value = "Value 3" };
var result3 = validator.Validate(foo3);
Console.WriteLine(result3.IsValid);
var foo4 = new Foo { Id = Guid.NewGuid(), Value = "This is not a valid enum value" };
var result4 = validator.Validate(foo4);
Console.WriteLine(result4.IsValid);
var foo5 = new Foo { Id = Guid.NewGuid(), Value = null };
var result5 = validator.Validate(foo5);
Console.WriteLine(result5.IsValid);
}
EDIT:
As per your additional comment about wrapping all of this into an extension:
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<T, string> IsEnumTypeMember<T>(this IRuleBuilderInitial<T, string> target)
{
return target
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
}
}
Then update the rule to use it:
RuleFor(x => x.Value).IsEnumTypeMember();
This is just an MVP, I don't really know all of your use cases; you may want to make it more generic so you could apply it to other enums.
You can use a custom method
RuleFor(x => x.Valor).Must(BeEnumType);
...
private bool BeEnumType(string v)
{
return Enum.IsDefined(typeof(EnumType), v.Replace(" ", string.Empty));
}

AutoMapper Mapping one property to two properties by condition

There are two classes
class A
{
public string ProductionDivision { get; set; }
}
class B
{
private object _productionDivision;
public enum ProductionDivisionOneofCase
{
None = 0,
IsNullproductionDivision = 15,
ProductionDivisionValue = 16,
}
private ProductionDivisionOneofCase _productionDivisionCase = ProductionDivisionOneofCase.None;
public bool IsNullProductionDivision
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.IsNullproductionDivision ? (bool)_productionDivision : false; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.IsNullproductionDivision;
}
}
public string ProductionDivisionValue
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.ProductionDivisionValue ? (string)_productionDivision : ""; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.ProductionDivisionValue;
}
}
}
I would like to map the ProductionDivision property to one of properties of class B depending on the condition - null(map to IsNullProductionDivision) or not null(map to ProductionDivisionValue) of the source property value. I can achieve it like as below.
CreateMap<A, B>()
.ForMember(g => g.IsNullProductionDivision, m =>
{
m.PreCondition(s => s.ProductionDivision == null);
m.MapFrom(b => true);
})
.ForMember(g => g.ProductionDivisionValue, m =>
{
m.PreCondition(s => s.ProductionDivision != null);
m.MapFrom(b => b.ProductionDivision);
});
If the value of {source property name} is null then the value of IsNull{source property name} is true.
Else if the value of {source property name} is not null then the value of {source property name}Value is the value of {source property name}.
I have many properties that respond this mapping rule. So, I don't want to write mapping rule for each properties like above. I want to configurate a rule for such mapping globally.
How can I configure AutoMapper so that it can handle such complex mapping?
I have found solution. The solution is pretty simple and clear. It turns out as follow:
Full code:
public class Program
{
class A
{
public string ProductionDivision { get; set; }
}
class B
{
private object _productionDivision;
public enum ProductionDivisionOneofCase
{
None = 0,
IsNullproductionDivision = 15,
ProductionDivisionValue = 16,
}
private ProductionDivisionOneofCase _productionDivisionCase = ProductionDivisionOneofCase.None;
public bool IsNullProductionDivision
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.IsNullproductionDivision ? (bool)_productionDivision : false; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.IsNullproductionDivision;
}
}
public string ProductionDivisionValue
{
get { return _productionDivisionCase == ProductionDivisionOneofCase.ProductionDivisionValue ? (string)_productionDivision : ""; }
set
{
_productionDivision = value;
_productionDivisionCase = ProductionDivisionOneofCase.ProductionDivisionValue;
}
}
}
public class StrinToBoolCustomResolver
: IValueConverter<string, bool>
{
public bool Convert(string sourceMember, ResolutionContext context)
{
return sourceMember == null;
}
}
public class MyAutoMapperProfile
: Profile
{
public MyAutoMapperProfile()
{
// add post and pre prefixes to add corresponding properties in the inner property map
RecognizeDestinationPostfixes("Value");
RecognizeDestinationPrefixes("IsNull");
// add mapping for "value" property
this.ForAllPropertyMaps(map => map.SourceMember.Name + "Value" == map.DestinationName,
(map, expression) =>
{
expression.Condition((source, dest, sMem, destMem) =>
{
return sMem != null;
});
expression.MapFrom(map.SourceMember.Name);
});
// add mapping for "IsNull" property
this.ForAllPropertyMaps(map => "IsNull" + map.SourceMember.Name == map.DestinationName,
(map, expression) =>
{
expression.Condition((source, dest, sMem, destMem) =>
{
return (bool)sMem;
});
//expression.MapFrom(map.SourceMember.Name);
expression.ConvertUsing<string, bool>(new StrinToBoolCustomResolver(), map.SourceMember.Name);
});
CreateMap<A, B>();
//.ForMember(g => g.IsNullProductionDivision, m =>
//{
// m.PreCondition(s => s.ProductionDivision == null);
// m.MapFrom(b => true);
//});
//.ForMember(g => g.ProductionDivisionValue, m =>
//{
// m.PreCondition(s => s.ProductionDivision != null);
// m.MapFrom(b => b.ProductionDivision);
//});
}
}
public static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg => {
cfg.AddProfile(new MyAutoMapperProfile());
});
var mapper = new Mapper(configuration);
mapper.ConfigurationProvider.AssertConfigurationIsValid();
var a = new A()
{
ProductionDivision = null
};
// b._productionDivisionCase will be equal IsNullproductionDivision
var b = mapper.Map<B>(a);
var c = new A()
{
ProductionDivision = "dsd"
};
// d._productionDivisionCase will be equal ProductionDivisionValue
var d = mapper.Map<B>(c);
}
}
Clarification:
add (post/pre)fixes to add corresponding properties to inner property map. It need to do here because our properties should be catched by AutoMapper. Otherwise properties will be abandoned and mapper configuration will be failed.
After that, we configurate how these properties need to be mapping. We call ForAllPropertyMaps method, filtering all properties and setting a rule to mapping properties suitable with our filter. When the mapper object is creating, the execution plan will be built taking look the specified filters.
For "Value" property we add a condition to check whether the source property is not null. If it is null, then mapping will be missed.
For "IsNull" property First of all, we add a converter to convert string type to bool type. The converter just compares the source string property with null value. If the source property equals null, then the converter returns true. So, the condition receives a true value, returns true, and mapping will be done. Otherwise the converter returns false, so the condition returns false and mapping will be missed.
Thus, the mapping of source property will occur to different destination properties depending on whether the source property is null value. Besides that, corresponding set methods of corresponding destination properties will be not called if it not must to be called.

Trying to get MetadataType by reflect not giving same results if I call the class directly

I'm trying to setup a T4 template that will loop through entity objects and ignore certain navigation properties based on an custom attribute I setup in the class' metdata data.
Here's the setup of the metadata tag
[MetadataType(typeof(ApplicationIntegrationMetadata))]
public partial class ApplicationIntegration
{
}
public class ApplicationIntegrationMetadata
{
[NotToCrud]
public ICollection<Profile> Profiles { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class NotToCrud : System.Attribute
{
}
I've created an extension method based that checks if the class has a specific attribute on the property
public static bool CheckIfPropertyIsAnnotated<T>(this object ctx, string propName)
{
var returnValue = false;
if (!string.IsNullOrWhiteSpace(propName))
{
var thisType = ctx.GetType();
var metadataTypes = thisType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
var metadata = metadataTypes.FirstOrDefault();
if (metadata != null)
{
var properties = metadata.MetadataClassType.GetProperties();
var found = properties.FirstOrDefault(d => d.Name == propName);
if (found != null)
{
returnValue = Attribute.IsDefined(found, typeof(T));
}
}
}
return returnValue;
}
I've created two separate unit tests to find my issue.
This one works.
[TestCase("Profiles", Result = true, TestName = "Valid property")]
public bool HasAttribute(string propName)
{
var test = new ApplicationIntegration();
var actual = test.CheckIfPropertyIsAnnotated<NotToCrud>(propName);
return actual;
}
This one returns false
[Test]
public void HasAttributeWithAssembly()
{
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (var item in myTypes)
{
var actual = item.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");
Console.WriteLine($"{item.Name} - {actual.ToString()}");
Assert.AreEqual(true, actual);
}
}
the problem seems to be with (var thisType = ctx.GetType())
On the second test, it returns
object.GetType returned
{Name = "RuntimeType" FullName = "System.RuntimeType"} System.RuntimeType
instead of
{Name = "ApplicationIntegration" FullName = "Apm.Model.ApplicationIntegration"}
Any clue how to get around this?
Because item is a Type (with value typeof(ApplicationIntegration)), not an ApplicationIntegration. You can create an object with Activator.CreateInstance. Like:
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (Type type in myTypes)
{
object obj = Activator.CreateInstance(type);
var actual = obj.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");

Custom Mapping with AutoMapper

I have two very simple objects:
public class CategoryDto
{
public string Id { get; set; }
public string MyValueProperty { get; set; }
}
public class Category
{
public string Id { get; set; }
[MapTo("MyValueProperty")]
public string Key { get; set; }
}
When mapping a Category to a CategoryDto with AutoMapper, I would like the following behavior:
The properties should be mapped as usual, except for those that have the MapTo attribute. In this case, I have to read the value of the Attribute to find the target property. The value of the source property is used to find the value to inject in the destination property (with the help of a dictionary). An example is always better that 1000 words...
Example:
Dictionary<string, string> keys =
new Dictionary<string, string> { { "MyKey", "MyValue" } };
Category category = new Category();
category.Id = "3";
category.Key = "MyKey";
CategoryDto result = Map<Category, CategoryDto>(category);
result.Id // Expected : "3"
result.MyValueProperty // Expected : "MyValue"
The Key property is mapped to the MyValueProperty (via the MapTo Attribute), and the assigned value is "MyValue", because the source property value is "MyKey" which is mapped (via dictionary) to "MyValue".
Is this possible using AutoMapper ? I need of course a solution that works on every object, not just on Category/CategoryDto.
I finally (after so many hours !!!!) found a solution.
I share this with the community; hopefully it will help someone else...
Edit: Note that it's now much simpler (AutoMapper 5.0+), you can do like I answered in this post: How to make AutoMapper truncate strings according to MaxLength attribute?
public static class Extensions
{
public static IMappingExpression<TSource, TDestination> MapTo<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
Type sourceType = typeof(TSource);
Type destinationType = typeof(TDestination);
TypeMap existingMaps = Mapper.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType);
string[] missingMappings = existingMaps.GetUnmappedPropertyNames();
if (missingMappings.Any())
{
PropertyInfo[] sourceProperties = sourceType.GetProperties();
foreach (string property in missingMappings)
{
foreach (PropertyInfo propertyInfo in sourceProperties)
{
MapToAttribute attr = propertyInfo.GetCustomAttribute<MapToAttribute>();
if (attr != null && attr.Name == property)
{
expression.ForMember(property, opt => opt.ResolveUsing(new MyValueResolve(propertyInfo)));
}
}
}
}
return expression;
}
}
public class MyValueResolve : IValueResolver
{
private readonly PropertyInfo pInfo = null;
public MyValueResolve(PropertyInfo pInfo)
{
this.pInfo = pInfo;
}
public ResolutionResult Resolve(ResolutionResult source)
{
string key = pInfo.GetValue(source.Value) as string;
string value = dictonary[key];
return source.New(value);
}
}
This should be fairly straightforward using an implementation of IValueResolver and the ResolveUsing() method. You basically just need to have a constructor for the resolver that takes in the property info (or if you wanna be fancy that takes in a lambda expression and resolves the property info similar to How to get the PropertyInfo of a specific property?. Though I haven't tested it myself I imagine the following would work:
public class PropertyBasedResolver : IValueResolver
{
public PropertyInfo Property { get; set; }
public PropertyBasedResolver(PropertyInfo property)
{
this.Property = property;
}
public ResolutionResult Resolve(ResolutionResult source)
{
var result = GetValueFromKey(property, source.Value); // gets from some static cache or dictionary elsewhere in your code by reading the prop info and then using that to look up the value based on the key as appropriate
return source.New(result)
}
}
Then to set up that mapping you need to do:
AutoMapper.Mapper.CreateMap<Category, CategoryDto>()
.ForMember(
dest => dest.Value,
opt => opt.ResolveUsing(
src =>
new PropertyBasedResolver(typeof(Category.Key) as PropertyInfo).Resolve(src)));
Of course that is a pretty gross lamda and I would suggest that you clean it up by having your property resolver determine the property on the source object it should look at based on the attribute/property info so you can just pass a clean new PropertyBasedResolver(property) into the ResolveUsing() but hopefully this explains enough to put you on the right track.
Lets assume I have the following classes
public class foo
{
public string Value;
}
public class bar
{
public string Value1;
public string Value2;
}
You can pass a lambda to ResolveUsing:
.ForMember(f => f.Value, o => o.ResolveUsing(b =>
{
if (b.Value1.StartsWith("A"));)
{
return b.Value1;
}
return b.Value2;
}
));

Categories