Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Configure AutoMapper using custom attributes

2.60/5 (2 votes)
6 Jun 2018CPOL1 min read 21.1K  
Dynamicly configure IMapper object using custom attributes annotations.

Introduction

In this tip we gonna build a custom AutoMap builder that will configure a mapper instance based on custom attributes instead of profiles.  

Background

Recently in our project, we had to map many objects from an old version to the new pdo’s  and because of refactoring making profiles for each object type wasn’t the best idea.    

In order to achieve the following goal “The Less Time Spent, The more work done” we decided to use a custom attribute for mapping source to destination objects so at any time when we had to swap classes we just need to replace attribute for the class.

Using the code 

Source example available on github:  

So, first of all, let’s take a look at example models     

C#
public class Source
{
    public string CustomerName { set; get; }

    public int Salary { set; get; }

    public string WorkAddress { set; get; }
}

[MapTarget(typeof(Source))]
public class Destination
{
    public string CustomerName { set; get; }

    public int Salary { set; get; }

    [MapFieldName("Address","WorkAddress")]
    public string Address { set; get; }

}

As you can see they are pretty much similar except one field, we will get back to it later.

Now let’s make couple custom attributes that we are gonna use to build our mapper  

C#
[AttributeUsage(AttributeTargets.Class)]
public class MapTargetAttribute : Attribute
{
    public Type Source;
    public MapTargetAttribute(Type source)
    {
        Source = source;
    }

}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class MapFieldName : Attribute
{

    public string Name { get; }

    public string From { get; }

    public MapFieldName(string ToField, string FromField)
    {
        Name = ToField;
        From = FromField;

    }
}

The first Attribute will be used to mark a class as Target For the Source object

The Second Attribute optional and might be used for the fields with different names.

Now we have attributes time to build Custom Mapper Builder 

    public interface IMapperFactory<T1>
    {
        IMapper BuildMapper(Type targetExplicit = null);

    }


public class AutoMapperAttributeBuilder<T1>
                        : IMapperFactory<T1> where T1 : class
    {
        public AutoMapperAttributeBuilder()
        {
        }

        public IMapper BuildMapper(Type targetExplicit = null)
        {
            var types = Assembly.GetExecutingAssembly().GetTypes()
                .Union(Assembly.GetCallingAssembly().GetTypes()).AsQueryable();
            types = types.Where(t => t.IsClass && t.GetCustomAttributes<MapTargetAttribute>().Count() > 0)
            .Where(t => t.GetCustomAttribute<MapTargetAttribute>().Source == typeof(T1));
            if (targetExplicit != null)
                types = types.Where(e => e.GetType() == targetExplicit);
            if (types.Count() == 0)
                throw new InvalidOperationException("Target Type Not Found For given source");
            var target = types.FirstOrDefault();
            var map = new MapperConfiguration(x => x.CreateMap(typeof(T1), target));
            if (target.GetProperties().Any(p => p.GetCustomAttributes<MapFieldName>().Count() > 0))
            {
                var expression = new MapperConfigurationExpression();
                var exp = expression.CreateMap(typeof(T1), target);
                foreach (var pro in target.GetProperties().Where(p => p.GetCustomAttributes<MapFieldName>().Count() > 0))
                {
                    foreach (var attrDescriptor in pro.GetCustomAttributes<MapFieldName>())
                    {
                        var sourceField = attrDescriptor.From;
                        var targetField = attrDescriptor.Name;
                        exp.ForMember(targetField, m => m.MapFrom(sourceField));
                    }
                }
                map = new MapperConfiguration(expression);
            }
            return map.CreateMapper();
        }

    }

Here we are using reflection and MapperConfigurationExpression in order to make magic happen.  

And finally, let's make an extension so we can get an instance to our builder in a more convenient way

C#
public static class TypeEx
{

    public static IMapper GetTypeMapper<T>(this Type type) where T : class
    {
        var builder = new AutoMapperAttributeBuilder<T>();
        var mapper = builder.BuildMapper();
        return mapper;
    }

}

Test and Run   

C#
static void Main(string[] args)
    {
        Console.WriteLine(" AutoMapper CustomAttributes Example ");

        var source = new Source {

             CustomerName = "test",
             Salary = 5000,
             WorkAddress = "Some address"
        };

        var mapper = typeof(Source).GetTypeMapper<Source>();

        var target = mapper.Map<Destination>(source);

        Console.WriteLine($"{target.CustomerName}");
        Console.WriteLine($"{target.Salary}");
        Console.WriteLine($"{target.Address}");

        Console.ReadLine();
    }

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)