0

I'm trying to remake an application I made a little while ago using ASP.NET MVC, but the problem I'm running into is caused by using the latest version of Automapper. I followed a tutorial the first time I made the application, and in this tutorial, the version of Automapper I used was 4.1.0.

However, this version was released back in around 2016 - the tutorial is quite old - and a lot of changes have happened to Automapper since then i.e. many things are now obsolete.

In the previous versions, you could use the static CreateMap method from Mapper class, but this is now obsolete.

Here's how I did it using the old way. First, I created a class that derives from Profile to store my configurations.

public class MappingProfile : Profile
{
    public MappingProfile()
    {
       // Domain to DTO
       Mapper.CreateMap<Customer, CustomerDto>();
       Mapper.CreateMap<Movie, MovieDto>();
       Mapper.CreateMap<MembershipType, MembershipTypeDto>();
       Mapper.CreateMap<MembershipTypeDto, MembershipType>();
       Mapper.CreateMap<Genre, GenreDto>();
       Mapper.CreateMap<GenreDto, Genre>();

       // Dto to Domain 
       Mapper.CreateMap<CustomerDto, Customer>()
          .ForMember(c => c.Id, opt => opt.Ignore());

       Mapper.CreateMap<MovieDto, Movie>()
          .ForMember(c => c.Id, opt => opt.Ignore());
     }
}

Next, I initialized the Mapper class and added the profile inside Global.asax.cs.

public class MvcApplication : System.Web.HttpApplication
{
     protected void Application_Start()
     {
       // Here's the line of code I added
       Mapper.Initialize(c => c.AddProfile<MappingProfile>());

       GlobalConfiguration.Configure(WebApiConfig.Register);
       AreaRegistration.RegisterAllAreas();
       FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
       RouteConfig.RegisterRoutes(RouteTable.Routes);
       BundleConfig.RegisterBundles(BundleTable.Bundles);
     }
}

Finally, I used the static Map method from Mapper to map my domain objects to DTO objects and vice versa inside my API controllers. This all worked fine, by the way.

public IHttpActionResult CreateCustomer(CustomerDto customerDto)
{
     if (!ModelState.IsValid)
        {
           BadRequest();
        }

     var customer = Mapper.Map<CustomerDto, Customer>(customerDto);

     _context.Customers.Add(customer);
     _context.SaveChanges();

     customerDto.Id = customer.Id;

     return Created(new Uri(Request.RequestUri + "/" + customer.Id ), customerDto);
}

The latest Automapper documentation suggests that you create a map for the domain and DTO objects using a MapperConfiguration and CreateMap, and that you only need one MapperConfiguration instance per AppDomain that should be instantiated during startup.

I went ahead and created a MappingProfile again to store my configurations (as recommended by the documentation) and included this configuration inside Application_Start() of Global.asax.cs which is the startup for MVC applications.

public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Customer, CustomerDto>();
            CreateMap<CustomerDto, Customer>();
        }
    }
public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // This is the new way of creating a MapperConfiguration 
            var configuration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<MappingProfile>();
            });

            IMapper mapper = configuration.CreateMapper();

            GlobalConfiguration.Configure(WebApiConfig.Register);
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            UnityConfig.RegisterComponents();
        }
    }

At this point, the documentation says you can use dependency injection to inject the created IMapper instance. This is where I get stuck and run into errors.

The documentation covers ASP.NET Core very well in explaining how to use dependency injection, so I tried using this approach with UnityConfig in which I created the MapperConfiguration there and registered the instance of IMapper then tried to inject this in my web API controller like so:

public static class UnityConfig
    {
        public static void RegisterComponents()
        {
            var container = new UnityContainer();

            var configuration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<MappingProfile>();
            });

            IMapper mapper = configuration.CreateMapper();

            container.RegisterInstance(mapper);
            // register all your components with the container here
            // it is NOT necessary to register your controllers
            
            // e.g. container.RegisterType<ITestService, TestService>();
            
            DependencyResolver.SetResolver(new UnityDependencyResolver(container));
        }
    }
 public class MvcApplication : System.Web.HttpApplication
    {
        
        protected void Application_Start()
        {            
            GlobalConfiguration.Configure(WebApiConfig.Register);
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // I registered the components here inside Global.asax.cs
            UnityConfig.RegisterComponents();
        }
    }

This is where I tried using the instance via dependency injection.

public class CustomersController : ApiController
    {
        private ApplicationDbContext _context;

        private readonly IMapper _mapper;

        public CustomersController()
        {
            _context = new ApplicationDbContext();
        }

        public CustomersController(IMapper mapper)
        {
            _mapper = mapper;
        }

        // GET /api/customers
        public IHttpActionResult GetCustomers()
        {
            var customersDto = _context.Customers
                .Include(c => c.MembershipType)
                .ToList()
                .Select(_mapper.Map<Customer, CustomerDto>);

            return Ok(customersDto);
        }
        // Remaining code omitted because it's unnecessary

The error I get when debugging is a NullReferenceException. In the Watch window, I notice that _mapper is null, however, the objects do get loaded from the database. The issue is _mapper still being null even after using DI.

Could someone kindly explain why it's null and potential fixes/tips to using this version of Automapper this way in an ASP.NET MVC Web API controller?

Vicktor
  • 169
  • 2
  • 10
  • You can use the MS DI in any app, not just ASP.NET Core, and then you can use the built in DI package in AM. – Lucian Bargaoanu May 25 '21 at 04:03
  • The documentation on Microsoft's official website only covers .NET Core and Console applications, not MVC. Do you have any links to documentation for Automapper DI in MVC specifically @LucianBargaoanu? – Vicktor May 25 '21 at 07:52
  • There is no need for such documentation. The MS DI package (and the AM DI package) can be used in MVC the same way as any other DI package. Nothing particular about it. – Lucian Bargaoanu May 25 '21 at 07:57
  • Okay. Based on the approach provided in my question, where are the areas I went wrong? – Vicktor May 25 '21 at 09:01

1 Answers1

2

After a lot of reading and research, I finally figured out where I went wrong. I believe Unity.Mvc5 is meant for regular controllers and not API controllers.

For this reason, I added a separate package, Unity.AspNet.WebApi, which came with a different Unity.Config.cs. A prompt appeared when adding the package asking if I wanted to overwrite the previous Unity.Config.cs (from Unity.Mvc5), and I chose yes.

From here, I added my configuration to this file, registered the instance, and I was good to go.

This video does a great job explaining the whole thing: https://youtu.be/c38krTX0jeo

Vicktor
  • 169
  • 2
  • 10