I have a method which returns IQueryable and in the method it calls the IQueryable.ProjectTo extension method. I am able to map fields with different names from my entity/database objects to the DTO but only when an instance of the configuration mapping is created within the method. However, when I try to inject a profile class with the same configuration mapping through IMapper, the code runs without error but the fields with different names are not mapped i.e. SellCcyRate.
I have looked through the automapper documentation and cannot see where the issue is. Would someone be able to advise?
I am using
AutoMapper v11.0.1
AutoMapper.Extensions.Microsoft.DependencyInjection v11.0.0
EF Core 6
Please find my setup below:
TradeListDtoProfile.cs
public class TradeListDtoProfile : Profile
{
public TradeListDtoProfile()
{
CreateMap<Deal, TradeListDto>()
.ForMember(dest => dest.SellCCYRate, opt => opt.MapFrom(src => src.RateValueFrom));
}
}
Startup.cs - ConfigureServices method
public void ConfigureServices(IServiceCollection services) {
services.AddAutoMapper(typeof(TradeListDtoProfile));
}
Controller.cs
public class TradeController: ControllerBase {
private readonly EfCoreContext _efCoreContext;
private readonly IMapper _mapper;
public TradeController(EfCoreContext efCoreContext, IMapper mapper) {
_efCoreContext = efCoreContext;
_mapper = mapper
}
public List < TradeListDto > RetrieveTrade() {
var tradeService = new DisplayTradesService(_efCoreContext);
return tradeService.FilterSortPage(_mapper);
}
}
DisplayTradesService.cs
public class DisplayTradesService
{
private readonly EfCoreContext _context;
public DisplayTradesService(EfCoreContext context)
{
_context = context;
}
public List<TradeListDto> FilterSortPage(SortFilterPageOptions options, IMapper mapper)
{
return _context.Deal
.AsNoTracking()
.MapTradeToDto(mapper).ToList();
}
}
TradelistDtoSelect.cs
public static class TradeListDtoSelect
{
public static IQueryable<TradeListDto> MapTradeToDto(this IQueryable<Deal> deals, IMapper mapper)
{
//var configuration = new MapperConfiguration(cfg => cfg.CreateMap<Deal, TradeListDto>()
//.ForMember(dest => dest.SellCCYRate, opt => opt.MapFrom(src => src.RateValueFrom)));
//deals.ProjectTo<TradeListDto>(configuration); // This works
return deals.ProjectTo<TradeListDto>(mapper.ConfigurationProvider); // This doesn't
}
}
You cannot mix attribute mapping with the fluent API for the same map. Remove the attribute from your DTO class.
It seems that the attribute overwritten your fluent configuration.
Related
I'm using Mapster with DI and I'm trying to map objects that I receive from WS. I was following this guide https://github.com/MapsterMapper/Mapster/wiki/Dependency-Injection#mapping
I register TypeAdapterConfig, and ServiceMapper
var config = new TypeAdapterConfig();
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
Blacklist class contains collection of Cards but webservice returns array of long, that I remap to object.
public class BlacklistMapper : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
config.NewConfig<long, Internal.BlacklistCard>()
.Map(dest => dest.Cuid, source => source);
config.NewConfig<SzWebService.BlackList, Internal.Blacklist>()
.Map(dest => dest.Id, source => source.iBlacklistId)
.Map(dest => dest.Crc, source => source.iBlackListCRC)
.Map(dest => dest.Cards, source => source.lCuid);
}
}
Inject mapper in the constuctor
private readonly IMapper _mapper;
public Service(IMapper mapper)
{
_logger = logger;
}
And finally call it like so
_mapper.Map<Blacklist>(response.mBlackListData)
The result is always object with default values
Step 1 - Create the configuration via implementing IRegister
public class BlacklistMapper : IRegister
{
void Register(TypeAdapterConfig config)
{
config.NewConfig<SzWebService.BlackList, Internal.Blacklist>()
.Map(...)
.Map(...);
}
}
Step 2 - Register the configuration
You can either register the configuration explicitly:
var config = new TypeAdapterConfig();
// Explicitly apply a specific configuration
config.Apply(new BlackListMapper());
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();
or let Mapster scan your assemblies for IRegister implementations:
// Scan & apply IRegisters automatically.
config.Scan(Assembly.GetExecutingAssembly());
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));
}
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SomeSourceModel, SomeDestinationModel>();
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
I am repeating these code in the project. Thinking to create a common interface IMapper so that I can invoke whenever it is needed to be used.
The solution I create is
private IMapper Mapper(TSource source, TDestination dest)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<source, dest>();
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
It doesn't work. The problem is I can't pass source model and destination model as parameters in this way. How to solve this?
Update 1:
As #12seconds mentioned, I start initializing MapperConfigration in Global.asax.cs
In App_Start folder, I created
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<SourceModel1, DestinationModel1>();
CreateMap<SourceModel2, DestinationModel2>();
CreateMap<SourceModel3, DestinationModel3>();
CreateMap<SourceModel4, DestinationModel4>();
CreateMap<SourceModel5, DestinationModel5>();
Mapper.AssertConfigurationIsValid();
}
}
In Global.asax.cs
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
}
}
And then I tried to call AutoMapperConfiguration.Configure(); in several places. When I start running the App, I got same error messages:
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.
Where I suppose to call AutoMapperConfiguration.Configure();? Did I miss something?
Version 5.0.x +
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
Mapper.AssertConfigurationIsValid();
}
}
The problem solved. Mapper.AssertConfigurationIsValid(); should be executed after Mapper initialized.
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
Mapper.Configuration.AssertConfigurationIsValid();
}
}
I am using AutoMapper profiles for mapping entities. In one of the mapping profiles I need to call a service method. I m trying to inject the service using profile constructor but then I m not able to register/add it. Any ideas how can I handle this?
public class HistoryProfile : Profile
{
private readonly MappingService _mappingService;
public HistoryProfile(MappingService mappingService)
{
_mappingService = mappingService;
this.CreateMap<HistoryCHR, History>()
.ForMember(h => h.BirthDate, hisChr => hisChr.MapFrom(x => x.DateOfBirth))
.....................
}
}
private static void InitializeSpecificProfiles()
{
Mapper.Initialize(
cfg =>
{
cfg.AddProfile(new HistoryProfile());
});
}
I have the following AutoMapper profile:
public class AutoMapperBootstrap : Profile
{
protected override void Configure()
{
CreateMap<Data.EntityFramework.RssFeed, IRssFeed>().ForMember(x => x.NewsArticles, opt => opt.MapFrom(y => y.RssFeedContent));
CreateMap<IRssFeedContent, Data.EntityFramework.RssFeedContent>().ForMember(x => x.Id, opt => opt.Ignore());
}
}
And I am initializing it like this:
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new AutoMapperBootstrap());
});
container.RegisterInstance<IMapper>("Mapper", config.CreateMapper());
When I try to inject it in my constructor:
private IMapper _mapper;
public RssLocalRepository(IMapper mapper)
{
_mapper = mapper;
}
I recieve the following error:
The current type, AutoMapper.IMapper, is an interface and cannot be
constructed. Are you missing a type mapping?
How can I initialize the AutoMapper profile properly with Unity, so that I can use the mapper anywhere through DI?
In your example you are creating named mapping:
// named mapping with "Mapper name"
container.RegisterInstance<IMapper>("Mapper", config.CreateMapper());
But how your resolver will know about this name?
You need to register you mapping without name:
// named mapping with "Mapper name"
container.RegisterInstance<IMapper>(config.CreateMapper());
It will map your mapper instance to IMapper interface and this instance will be returned on resolving interface
You can register it like so:
container.RegisterType<IMappingEngine>(new InjectionFactory(_ => Mapper.Engine));
Then you can inject it as IMappingEngine.
private IMappingEngine_mapper;
public RssLocalRepository(IMappingEnginemapper)
{
_mapper = mapper;
}
More information found here:
https://kalcik.net/2014/08/13/automatic-registration-of-automapper-profiles-with-the-unity-dependency-injection-container/