How to use Automapper configuration in ASP.NET MVC controllers - c#

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);
//...

Related

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));
}

Using Automapper with ASP.NET Core

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

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();
}

Telling injected automapper to use specific mapping profile in map function

In some cases one of my application services has to generate DTOs with anonymized data for the frontend. The idea was to use different AutoMapper profiles to either map the domain object to the DTO with all properties mapped or the anonymized DTO.
I generated these two profiles and injected them into the service. The AutoMapper is also injected into the service as IMapper and contains all mapping profiles of the application.
What I need now is to tell the mapper to use one specific profile in a call of the Map-function.
Something like this:
var anonymizedDto = _autoMapper.Map<SourceType, DestinationType>
(sourceObject, ops => ops.UseMappingProfile(_anonymizedMapingProfile));
var normalDto = _autoMapper.Map<SourceType, DestinationType>
(sourceObject, ops => ops.UseMappingProfile(_normalMappingProfile));
Is this possible and if yes: how?
As far as i know, you can not change the profile when you call Map.
What you can do is inject two mappers that have been configured with different profiles.
public class MyService : IService {
private readonly IMappingEngine _defaultMapper;
private readonly IMappingEngine _anonymousMapper;
public MyService(IMappingEngine defaultMapper, IMappingEngine anonymousMapper) {
_defaultMapper = defaultMapper;
_anonymousMapper = anonymousMapper;
}
public MyDto GetDefault() {
return _defaultMapper.Map<MyDto>(sourceObject);
}
public MyDto GetAnonymous() {
return _anonymousMapper.Map<MyDto>(sourceObject);
}
}
In your dependency container, set up the constructor injection to respect the name of the ctor parameter. For example with StructureMap:
public void ConfigureAutoMappers(ConfigurationExpression x) {
// register default mapper (static mapping configuration)
Mapper.Configuration.ConstructServicesUsing(t => container.GetInstance(t));
Mapper.Configuration.AddProfile<DefaultProfile>();
var defaultAutomapper = Mapper.Engine
x.For<IMappingEngine>().Use(() => defaultAutoMapper).Named("DefaultAutoMapper");
// register anonymous mapper
var anonConfig = new AnonConfigurationStore( // class derived from ConfigurationStore
new TypeMapFactory(),
AutoMapper.Mappers.MapperRegistry.AllMappers()
);
anonConfig.ConstructServicesUsing(container.GetInstance);
var anonAutoMapper = new MappingEngine(anonConfig);
x.For<IMappingEngine>().Add(anonAutoMapper).Named("AnonAutoMapper");
// Inject the two different mappers into our service
x.For<IService>().Use<MyService>()
.Ctor<IMappingEngine>("defaultMapper").Named("DefaultAutoMapper")
.Ctor<IMappingEngine>("anonymousMapper").Named("AnonAutoMapper");
}

Configuring the latest version of AutoMapper at the Global.asax level in an ASP.NET MVC Application

I am trying to figure out how to configure the new AutoMapper at the Global.asax level.
I used to do the following with old AutoMapper:
Create a class in App_Start folder called MappingProfile.cs and in the constructor I would add my mappings like this:
public MappingProfile()
{
Mapper.CreateMap<Product, ProductDto>();
Mapper.CreateMap<ApplicationUser, UserDto>();
}
Then in Global.asax call:
Mapper.Initialize(cfg => cfg.AddProfile<MappingProfile>());
Can someone please tell me how to achieve the above with the new version of AutoMapper? I have been reading the docs but can't seem to get it.
I believe I do something like this in my MappingProfile.cs file:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Product, ProductDto>();
cfg.CreateMap<ApplicationUser, UserDto>();
});
but what do I do with the config variable?
This is how I do it.
public abstract class AutoMapperBase
{
protected readonly IMapper _mapper;
protected AutoMapperBase()
{
var config = new MapperConfiguration(x =>
{
x.CreateMap<Product, ProductDto>();
x.CreateMap<ApplicationUser, UserDto>();
});
_mapper = config.CreateMapper();
}
}
Then inherit AutoMapperBase from any class which needs to use it, and call it like this:
var foo = _mapper.Map<ProductDto>(someProduct);
You no longer need it declared or configured in Global.asax
What you need to do is the following:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.AddProfile<MappingProfile>();
});
This will configure the static mapper instance that was exposed in the old AutoMapper so that you can do:
Mapper.Map<SomeType>(fromObject);
If you don't want to use the static instance then you need to look at the IntoNET answer.
The "CreateMissingTypeMaps" line ensures that missing mappings are created "on the fly". It's the equivalent of the old:
Mapper.DynamicMap<SomeType>(fromObject)
If you want to define each mapping by hand you can remove this line.
There was a version of AutoMapper where author tried to get rid of the exposed static for some reason but he brought it back after public outcry from what I understand. So now you can do it any way you find suitable. You can read about it here:
https://lostechies.com/jimmybogard/2016/02/24/automapper-4-2-1-released-static-is-back-limited-edition/

Categories

Resources