Wednesday, November 23, 2016

Adding Auto Mapper 5.0.0 Profile Dynamically using Autofac

Introduction.

We would create profile dynamically using reflection and using Autofac we would inject the auto-mapper.

Problem

When we create a lot of profiles. We need to add them in the configuration file as shown below.


Example.

public IMapper ConfigureMapper()
{
   var config = new MapperConfiguration(cfg =>
   {
     cfg.AddProfile<Employee>();
     cfg.AddProfile<Author>();
     cfg.AddProfile<Title>();
                       .
                       .
                       .
                       .
                       .
     cfg.AddProfile<Book>();
                       .
                       .
     //30 more profiles

   });
   return config.CreateMapper();
 }

As the project size grows adding the profiles become tedious and maintainability takes a hit.

Solution 

We would create the profile and them dynamically using reflection code and use Autofac to inject them.

By creating auto-mapper profiles dynamically what do we achieve.

1.Open Closed Principle is not violated.
2.Developers would not add any profiles manually.
3.Code is neatly maintained.

So let’s achieve the same.

Step 1.Create a Common Library in the solution which would be referred by all the other projects in the application.

Step 2.Create a class in Common Library called Auto Mapper Profile which extends a base class “Profile” which is under the auto-mapper dll 

  public class AutoMapperProfile: Profile
  {

  }

3.Now the next step we would create required profiles which extends “AutoMapperProfile” so you would create profiles as given below. 

   public class EmployeeProfile : AutoMapperProfile
    {
       public EmployeeProfile()
       {
           CreateMap<Employee, EmployeeModel>();
       }
    }

Note : If any Profiles that do not extend AutoMapperProfile class then the code would not work.

Step 3.We then need to add the AutoMapperConfiguration class. Where we create a Method that returns IMapper.

public class AutoMapperConfiguration
    {
        public IMapper Configure()
        {
            var profiles =AppDomain.CurrentDomain.GetAssemblies()
              .SelectMany(s => s.GetTypes())
              .Where(a => typeof(AutoMapperProfile).IsAssignableFrom(a));

            // Initialize AutoMapper with each instance of the profiles found.
            var mapperConfiguration = new MapperConfiguration(a =>                                                                  profiles.ForEach(a.AddProfile));

            return mapperConfiguration.CreateMapper();
        }
        
    }


In the above code we have used the reflection to find all the profiles where "AutoMapperProfile" class is extended

If you closely observe in the above code we have used ForEach. We have created an Enumerable extension which create an actionable item for each found profile.

public static class EnumerableExtensions
    {
        public static void ForEach<T>(this IEnumerable<T> enumerable,
                Action<T> action)
        {
            foreach (T item in enumerable) { action(item); }
        }
    }


Step 4: Once we are done with the Creating the Mapper we will return the IMapper to the Autofac object where we create instance for the mapper.

            //Creating an Instance for the Mapper
            builder.RegisterInstance(new AutoMapperConfiguration().Configure()).As<IMapper>();

Step 5: After doing the above steps register the Auto-mapper in the Autofac.

            //Creating an Instance for the Mapper
            builder.RegisterInstance(new AutoMapperConfiguration().Configure()).As<IMapper>();

Step 6:To use the Automapper in the Project just Inject the IMapper in the constructor and it works automatically.
public EmployeeManager(IEmployeeLogic employeelogic, IMapper mapper)
        {
            if (employeelogic== null) throw new ArgumentNullException(nameof(employeelogic));
            _employeelogic = employeelogic;

            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            _mapper = mapper;
        }

     private async Task<EmployeeModel> GetEmployeeModel(Employee employee)
        {
            var employeeInfoModel = await _exampleCode.GetEmployeeRepo(employee);
            return _mapper.Map<EmployeeModel>(employeeInfoModel );
        }


Just follow the above steps and everything would work seemelesly.

Code is available in the below repository.
https://github.com/BatIronMan007/AutoMapperReflection

6 comments:

  1. Hi Vaibhav,

    I got error System.Reflection.ReflectionTypeLoadException. Did i miss something to do ?

    ReplyDelete
  2. If you have followed the above steps. It should work just fine. I will create a git hub repository and publish the code. It would take some time.

    ReplyDelete
  3. Hi Hendi,

    Did you happen to resolve the issue..?

    Thanks

    ReplyDelete
  4. Hi Hendi,

    I have uploaded the sample code in the below repository. Please let me know your thoughts.

    https://github.com/BatIronMan007/AutoMapperReflection

    ReplyDelete
  5. Thank you so much. Thank you for the Motivation

    ReplyDelete
  6. Hello Vaibhav, I am extending Profile class for createMap , than I am registering automapper as "services.AddAutoMapper();" in configureService method of startup.cs. than I am injecting IMapper _mapper in my controller for web project it is working fine but while unit testing project I am creating mock object of IMapper whereas I don't need mock object I want to create object of IMapper because using mock object I need to explicitly map objects just like:

    var _mapper = new Mock();
    _mapper.Setup(m => m.Map(It.IsAny())).Returns((Student student) =>
    {
    var studentView = new StudentViewModel()
    {
    Id = student.Id,
    CollegeId = student.CollegeId,
    FirstName = student.person.FirstName,
    LastName = student.person.LastName,
    StudentRegId = student.StudentRegId
    };
    return studentView;
    });
    I want to use the object of IMapper that i injected in Startup.cs is there any way that i can do it? please help me with this issue.

    ReplyDelete