I want to create Attribute based AutoMapper functionality. I need to map domain model class to DTO and reverse, but some properties from DTO should never be mapped back to domain class. As read only property. Only direction from server to client is allowed.
Ok, so I have this
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PersonDTO
{
[MapperDirection(Direction.ClientToServer)]
public string Name { get; set; }
public string Metadata { get; set; }
}
Because I want to set direction using attributes, I have this one
[Flags]
public enum Direction
{
None = 0,
ServerToClient = 1,
ClientToServer = 2,
Both = 4
}
[AttributeUsage(AttributeTargets.Property)]
public class MapperDirectionAttribute : Attribute
{
public Direction Direction { get; }
public MapperDirectionAttribute(Direction direction = Direction.Both)
{
Direction = direction;
}
}
I decorated Name property on DTO class to allow only server to client direction for name.
As helper for mapper I have this extension to copy all values from source to target based on attribute setting
public static class MapperExpressions
{
public static IMappingExpression<TSource, TDestination> OnlyIfClientToServer<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var props = sourceType.GetMembers().Where(prop => prop.IsDefined(typeof(MapperDirectionAttribute), false));
foreach (var property in props)
{
var attribute = property.GetCustomAttribute<MapperDirectionAttribute>();
var allow = attribute.Direction == Direction.ClientToServer || attribute.Direction == Direction.Both;
if (!allow)
{
// THIS DOESN'T WORK FOR SOURCE MEMBER
// AND CAN'T BE USED FOR DESTINATION MEMBER (BECAUSE OF MISSING PROPERTIES)
expression.ForSourceMember(property.Name, opt => opt.Ignore());
}
}
return expression;
}
}
So Mapper init is
// Init mapper
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Person, PersonDTO>();
cfg.CreateMap<PersonDTO, Person>()
.OnlyIfClientToServer();
//.ForSourceMember(src => src.Name, src => src.Ignore()) // DOESN'T WORK
//.ForMember(dst => dst.Name, dst => dst.Ignore()); // WORKS, BUT DON'T WANT EXPLICIT DEFINITION
});
And some part of console application
// Map Domain -> ViewModel
var person = new Person { Id = 1, Name = "Default Name From Domain" };
var personDto = Mapper.Map<PersonDTO>(person);
Console.WriteLine($"Person: Id = '{person.Id}', Name = '{person.Name}'");
Console.WriteLine($"PersonDTO: Name = '{personDto.Name}', Metadata = '{personDto.Metadata}'");
Console.WriteLine();
// Map ViewModel -> Domain
personDto.Name = "Changed from DTO";
personDto.Metadata = "Custom metadata from DTO";
Mapper.Map(personDto, person);
Console.WriteLine($"PersonDTO: Name = '{personDto.Name}', Metadata = '{personDto.Metadata}'");
Console.WriteLine($"Person: Id = '{person.Id}', Name = '{person.Name}'");
Console.ReadLine();
And now problems.
I want to know, why ForSourceMember is not working as expected? How to prevent mapping using source member?
I can't use ForMember mapping, because for me Source is decorated, Destination is just domain model. Other problem is, that not all properties in DTO class are in domain class, so my extension raises exception of course.
Only solution I found, how to maintain copied propertie, is in CreateMap<>, but I don't want to add all properties to configuration code. Attributes are better to setup.
Anyone knows how to fix my solution to keep all logic in AutoMapper extension?