Using Automapper with ASP.NET Core - c#

I´m trying to use Automapper with Dependency Injection configuration on a n-layer application.
public class ApplicationMapping : Profile
{
public ApplicationMapping()
{
RegisterMappings();
Mapper.AssertConfigurationIsValid();
}
private void RegisterMappings()
{
CreateMap<IEnumerable<App>, ListAppsDto>()
.ForMember(dest => dest.Apps,
opt => opt.MapFrom(src =>
Mapper.Map<IEnumerable<App>, List<App>>(src.ToList())
)
);
}
}
This class is inside my Application dll, where I put my services and DTOs. Also in this dll, I have an extension method to register the mapping:
public static class MappingServiceExtension
{
public static void AddApplicationMappings(this IServiceCollection services)
{
var mapperConfig = new MapperConfiguration(config =>
{
config.AddProfile<ApplicationMapping>();
});
IMapper mapper = mapperConfig.CreateMapper();
services.AddSingleton(mapper);
}
}
Then in my WebAPI project, on the Startup.cs class I put:
services.AddApplicationMappings();
And I use it normally with DI in my services:
public class AppService : IAppService
{
private readonly IAppRepository _appRepository;
private readonly IMapper _mapper;
public TruckService(IAppRepository appRepository, IMapper mapper)
{
_appRepository = appRepository;
_mapper = mapper;
}
}
I would like to use like this. But I'm getting an exception when the Mapper.AssertConfigurationIsValid(); line runs, saying that:
'Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.'
What am I missing here? The problem seems to be in the Mapper.Map<IEnumerable<App>, List<App>>(src.ToList()) line of code.
But how can I get an instance of the Mapper there without using the static Mapper?

Mapper.AssertConfigurationIsValid();
This calls the static IMapper instance which is used in situations where you don’t use dependency injection. Since you never set up the static mapper, using it there will fail.
What you want to do instead is call AssertConfigurationIsValid on the actual mapper instance, the one that you are registering as a singleton. So you should remove the assert from the mapper profile and instead call it within your AddApplicationMappings method:
IMapper mapper = mapperConfig.CreateMapper();
mapper.AssertConfigurationIsValid();
services.AddSingleton(mapper);

Try using AddAutoMapper from AutoMapper.Extensions.Microsoft.DependencyInjection which you can add as a NuGet package.
So, you'd completely remove the MappingServiceExtension class, and then in Startup.cs add these two lines:
AutoMapper.Mapper.Reset();
services.AddAutoMapper(typeof(ApplicationMapping).Assembly);
I forgot the exact reason, but when using AutoMapper in across multiple projects/assemblies, you need to register it for DI this way. Read more here.

Similar to what #johnluke.laue suggested. In AddApplicationMappings simply replace the code with the following:
services.AddAutoMapper(config =>
{
config.AddProfile<ApplicationMapping>();
});
The above will automatically add the IMapper to the DI. In addition, modify the RegisterMappings function as below. You don't need to explicitly map the IEnumerable<T>. It will be mapped implicitly if the source/destination mappings exist.
private void RegisterMappings()
{
CreateMap<IEnumerable<App>, ListAppsDto>()
.ForMember(dest => dest.Apps, opt => opt.MapFrom(src => src.ToList());
}
It would be helpful to see the actual App and ListAppDto classes, as you don't explicitly need the above mappings. I hope this helps

Related

How to use Automapper configuration in ASP.NET MVC controllers

I am using AutoMapper to convert my models into view models. I have the configuration all setup, tested, and working. For reference, this is what my configure method looks like:
public static MapperConfiguration Configure()
{
MapperConfiguration mapperConfiguration = new MapperConfiguration(cfg => {
cfg.CreateMap<Ebill, EbillHarvesterTaskVM>()
cfg.CreateMap<Note, NoteVM>();
cfg.CreateMap<LoginItem, LoginCredentialVM>()
cfg.CreateMap<Login, ProviderLoginVM>()
});
mapperConfiguration.CreateMapper();
return mapperConfiguration;
}
This is what my test looks like:
public void ValidConfigurationTest()
{
var config = AutoMapperConfig.Configure();
config.AssertConfigurationIsValid();
}
What I don't understand is how to access it to actually map one object to another from within my Controller. I know I can call this config method when my app starts up, I have an application configuration class that is called from global.asax that calls my automapper configuration method. I'm not sure how to access all of this from within the controller though. I've read things that say dependency injection, but I'm not familiar enough with what that means to know how to apply it.
I've used Automapper in the past, but I think I implemented the now unavailable static API. Where the config method looks like this:
public static void RegisterMappings()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<ManagementCompany, ManagementCompanyViewModel>();
cfg.CreateMap<ManagementCompanyViewModel, ManagementCompany>();
});
}
The configuration is called in Global.asax
AutoMapperConfig.RegisterMappings();
And where you can call this within a controller to utilize mapping:
AutoMapper.Mapper.Map(managementCompany, managementCompanyVM);
This way doesn't work anymore. When I type AutoMapperMapper there is no Map method to call. What do I need to do to be able to access my mappings and use them?
public static MapperConfiguration Configure() {
MapperConfiguration mapperConfiguration = new MapperConfiguration(cfg => {
cfg.CreateMap<Ebill, EbillHarvesterTaskVM>()
cfg.CreateMap<Note, NoteVM>();
cfg.CreateMap<LoginItem, LoginCredentialVM>()
cfg.CreateMap<Login, ProviderLoginVM>()
});
return mapperConfiguration;
}
build the mapper and register it with the dependency container used by your application.
global.asax
MapperConfiguration config = AutoMapperConfig.Configure();;
//build the mapper
IMapper mapper = config.CreateMapper();
//..register mapper with the dependency container used by your application.
myContainer.Register<IMapper>(mapper); //...this is just an oversimplified example
Update your controllers to explicitly depend on the mapper via constructor injection
private readonly IMapper mapper;
public MyController(IMapper mapper, ...) {
this.mapper = mapper;
//...
}
And call the mapper as needed in the controller actions.
//...
Note model = mapper.Map<Note>(noteVM);
//...

AutoMapper does not work with defined rules

I'm developing a REST api. I implement automapper packets to compare the post model object with a correct user model object.
The problem is that I configured and use like service in the dependency injection, but ignore the defined rules.
IMapper mapper = CreateMapperStartUp(services);
Here I created the IMapper object:
private IMapper CreateMapperStartUp(IServiceCollection services)
{
var mapperConfiguration = new MapperConfiguration(conf =>
{
conf.AddProfile<MappingKey>();
});
IMapper mapper = mapperConfiguration.CreateMapper();
services.AddAutoMapper(typeof(Startup));
return mapper;
}
I'm using a profile "MappingKey".
public class MappingKey : Profile
{
public MappingKey()
{
CreateMap<CreateKeyViewModel, Key>().ReverseMap();
CreateMap<UpdateKeyViewModel, Key>().ReverseMap();
}
}
There I created the map method with post object models and key user objects. And finally my validator class with mapping rules.
public class CreateKeyValidator : AbstractValidator<CreateKeyViewModel>
{
private readonly MysqlDAO _mysqlDAO;
public CreateKeyValidator(MysqlDAO mysqlDAO)
{
_mysqlDAO = mysqlDAO;
RuleFor(x => x.Id).NotNull().Must(x => !_mysqlDAO.ExistId(x));
RuleFor(x => x.Colour).NotNull().Length(2, 3);
RuleFor(x => x.Date).NotNull().Length(2, 50);
}
}
What is the problem and why the mapper pass object with null parameters?
If I correctly understand. You create mapper. And do not use it anywhere (unfortunately ther is no using in code that you provide).
You call services.AddAutoMapper(typeof(Startup));
But mapper that you created IMapper mapper and init it's profile, not used here. So, how your application will knows about mapper instance.
Is I correct? If not please provide more context.
I suppose you should write something like this:
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
Ok, finally found the mistake. In this case the validation class wasn't defined by service on the StartUp. So using the fluentValidation method to create his own instance:
services.AddMvc().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
And that's it. It works fine all the rules.
Yes, in the post I don't wrote the AddSingleton, but in the real pipeline it's like that.
private IMapper CreateMapperStartUp(IServiceCollection services) {
var mapperConfiguration = new MapperConfiguration(conf =>
{
// conf.CreateMap<Key, CreateKeyViewModel>();
conf.AddProfile<MappingKey>();
});
IMapper mapper = mapperConfiguration.CreateMapper();
services.AddAutoMapper(typeof(Startup));
// services.AddSingleton(mapper);
return mapper;
}
And when I'm adding the instance to the services, I need to access the registered instance of IMapper.
private void AddMysqlService(IServiceCollection services)
{
ILoggerFactory loggerFactory = CreateLoggerFactory(services.BuildServiceProvider());
IMapper mapper = CreateMapperStartUp(services);
services.AddSingleton<IConnection<Key>>(new ConnectionMannager(Configuration.GetSection("mysqlDb"), loggerFactory));
var connectionMnamagerInstance = services.BuildServiceProvider().GetService<IConnection<Key>>();
services.AddSingleton<IService<Key>>(new MysqlService(mapper, connectionMnamagerInstance));
}

ForMember setting does not working when using AutoMapper in .Net Core project

I'm using AutoMapper in my .NET Core project. The default mapper function is working well, but when I use .ForMember() in myProfile.cs class, it doesn't work.
myProfile.cs just like :
public class ServiceProfile : Profile
{
public ServiceProfile()
{
CreateMap<Organization, OrganizationDto>()
.ForMember(x => x.Active, opt => opt.MapFrom(src => src.Disabled));
}
}
The configuration in startup.cs like this:
public void ConfigureServices(IServiceCollection services)
{
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new ServiceProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}
Then
input.Disabled=0;
var output = _mapper.Map<Organization>(input);
I expect the output.Active to be 0, but the actual output is null.
UPDATE:
I'm sorry, the method has no problem, it's because I'm using dapper and skip the map step.
It could be because the mapping method is CreateMap<TSource, TDestination>, source is the first generics parameter, but in the code below your mapping is from the DTO class to organization class (in the other direction) for which the mapping is probably not specified.
You may need to create mapping also for the other direction from OrganizationDto to Organization.
Update: there now is an easy way to add mapping for the other direction by adding .ReverseMap() to the end of CreateMap call chain.

Where to validate AutoMapper Configuration in ASP.Net Core application?

Building an ASP.Net Core 2.0 web application and can't figure out where to validate the AutoMapper configuration.
In my ConfigureServices() method, I have
services.AddAutoMapper();
And I'm registering my mappings in an Automapper Profile
public class MyAutomapperProfile : Profile
{
public MyAutomapperProfile()
{
CreateMap<FooDto, FooModel>();
CreateMap<BarDto, BarModel>();
}
}
But it's not clear where to call
Mapper.Configuration.AssertConfigurationIsValid();
After digging around in the IMapper interface (and thanks to the documentation link provided by #LucianBargaoanu), I found exactly what I needed.
In ConfigureServices():
// Adds AutoMapper to DI configuration and automagically scans the
// current assembly for any classes that inherit Profile
// and registers their configuration in AutoMapper
services.AddAutoMapper();
The secret sauce is to add IMapper mapper as a parameter to Configure() - the parameter list is dependency-injected so you can reference any service registered in ConfigureServices()
public void Configure(IApplicationBuilder app, ... , IMapper mapper)
{
...
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
Works exactly as expected.
The recommended approach (see JBogard's response) is to move this test into a unit test:
public class MappingTests
{
private readonly IMapper _sut;
public MappingTests() => _sut = new MapperConfiguration(cfg => { cfg.AddProfile<MyAutomapperProfile>(); }).CreateMapper();
[Fact]
public void All_mappings_should_be_setup_correctly() => _sut.ConfigurationProvider.AssertConfigurationIsValid();
}

Mock AutoMapper Mapper.Map call using Moq

Whats the best way to setup a mock expection for the Map function in AutoMapper.
I extract the IMapper interface so I can setup expects for that interface. My mapper has dependencies, so I have to pass those in to the mapper.
What happens when I create 2 instances of my mapper class, with 2 different dependency implementations? I asume that both mappers will use the same dependency instance, since the AutoMapper map is static. Or AutoMapper might even throw an exception because I try to setup 2 different maps with the same objects.?
Whats the best way to solve this?
public interface IMapper {
TTarget Map<TSource, TTarget>(TSource source);
void ValidateMappingConfiguration();
}
public class MyMapper : IMapper {
private readonly IMyService service;
public MyMapper(IMyService service) {
this.service = service
Mapper.CreateMap<MyModelClass, MyDTO>()
.ForMember(d => d.RelatedData, o => o.MapFrom(s =>
service.getData(s.id).RelatedData))
}
public void ValidateMappingConfiguration() {
Mapper.AssertConfigurationIsValid();
}
public TTarget Map<TSource, TTarget>(TSource source) {
return Mapper.Map<TSource, TTarget>(source);
}
}
You don't need to mock AutoMapper, you can just inject the real one as explained here:
var myProfile = new MyProfile();
var configuration = new MapperConfiguration(cfg => cfg.AddProfile(myProfile));
IMapper mapper = new Mapper(configuration);
You can inject this mapper in your unit tests. The whole point of using tools like AutoMapper is for you not having to write a lot of mapping code. If you mock AutoMapper you'll end up having to do that.
Whats the best way to setup a mock expection for the Map function in AutoMapper[?]
Here's one way:
var mapperMock = new Mock<IMapper>();
mapperMock.Setup(m => m.Map<Foo, Bar>(It.IsAny<Foo>())).Returns(new Bar());
http://richarddingwall.name/2009/05/07/mocking-out-automapper-with-dependency-injection/
Points out another way of handling dependencies to AutoMapper, which basically will replace my attempt to extract my own IMapper interface. Instead I will attempt to use the existing IMappingEngine as dependency for my classes, to see if that works.
What you need to do is setup AutoMapper like this (StructureMap is IoC). Then you can make your services dependent on IMappingEngine. From there mocking it should be very easy.
public class AutoMapperConfigurationRegistry : Registry
{
public AutoMapperConfigurationRegistry()
{
ForRequestedType<Configuration>()
.CacheBy(InstanceScope.Singleton)
.TheDefault.Is.OfConcreteType<Configuration>()
.CtorDependency<IEnumerable<IObjectMapper>>().Is(expr => expr.ConstructedBy(MapperRegistry.AllMappers));
ForRequestedType<ITypeMapFactory>().TheDefaultIsConcreteType<TypeMapFactory>();
ForRequestedType<IConfigurationProvider>()
.TheDefault.Is.ConstructedBy(ctx => ctx.GetInstance<Configuration>());
ForRequestedType<IConfiguration>()
.TheDefault.Is.ConstructedBy(ctx => ctx.GetInstance<Configuration>());
}
}
The reason you have to invoke automapper config is because, the UNIT Test cases instance runs outside of main application start up files/configs. Hence the auto mapper configuration has to be called and setup before any unit tests start to run. Ideally you place it in TestInitialize methods.
Here is an example of how I provide an instance of AutoMapper as default Mock for IMapper, using AutoFixture and Moq:
Thanks Lucian Bargaoanu for this hint: Actually you can use cfg.AddMaps(params Assembly[]) and Automapper will search for profiles
Create an ICustomization
public class MapperCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register<IMapper>(() =>
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddMaps(
Assembly.Load("BookSharing.Application"),
Assembly.Load("BookSharing.Infrastructure"));
});
return new Mapper(configuration);
});
}
}
Register the customization
fixture.Customize(new MapperCustomization());
following #Dorin Baba answer, I created a useful generic class that can be used to inject any custom mapper in the unit tests
public class MapperCustomization<T> : ICustomization
where T : class
{
public void Customize(IFixture fixture)
{
fixture.Register<IMapper>(() =>
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddMaps(
typeof(T).Assembly);
});
return new Mapper(config);
});
}
}

Categories

Resources