Click here to Skip to main content
13,772,614 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

4.6K views
7 bookmarked
Posted 6 Jun 2018
Licenced CPOL

Configure AutoMapper using custom attributes

, 6 Jun 2018
Rate this:
Please Sign up or sign in to vote.
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     

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  

[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

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   

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)

Share

About the Author

Denis Pashkov
United States United States
No Biography provided

You may also be interested in...

Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.181119.1 | Last Updated 6 Jun 2018
Article Copyright 2018 by Denis Pashkov
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid