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?