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());
Related
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.
I am trying to map from a db-model to a view model. For one property I need a custom value resolver.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Model.Db.Kontoauszug, KontoauszugDetailViewModel>()
.ForMember(dest => dest.IsTeamleiter, opt => opt.MapFrom<KontoauszugIsTeamleiterResolver>());
});
var mapper = new Mapper(config);
return mapper.Map<KontoauszugDetailViewModel>(kontoauszug);
The custom value resolver is dependent on a service as you can see here:
public class KontoauszugIsTeamleiterResolver : IValueResolver<Model.Db.Kontoauszug, KontoauszugDetailViewModel, bool>
{
private readonly ISysParamService sysParamService;
public KontoauszugIsTeamleiterResolver(ISysParamService sysParamService)
{
this.sysParamService = sysParamService;
}
public bool Resolve(Model.Db.Kontoauszug source, KontoauszugDetailViewModel destination, bool destMember, ResolutionContext context)
{
var teamleiter = this.sysParamService.GetParamValueAs<string>(KontoauszugSysParamConst.KONTOAUSZUG_TEAMLEITER_MANUMMERN).Split(";").ToList();
return teamleiter.Contains(source.MitarbeiterNr);
}
}
Unfortuantely, when running this code throws a exception that the valueresolver does not have a parameterless constructor.
I'm using the standard .net core dependency injection and in my Startup.cs I'm registering the automappers via
services.AddAutoMapper(typeof(Startup));
I've also tried to explicitly register the value resolver:
services.AddScoped<IValueResolver<Model.Db.Kontoauszug, KontoauszugDetailViewModel, bool>, KontoauszugIsTeamleiterResolver>();
But it does not work.
What am I doing wrong that this exception is thrown?
Thank in advance
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));
}
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/
I'm trying to use automapper in combination with ninject and a generic interface, abstract class. However it doesn't seem to work.
Below you will find the code which I'm trying to use. What am I missing?
IMapper
public interface IMapper<in TTypeFrom, TTypeTo>
{
TTypeTo Map(TTypeFrom typeFrom);
List<TTypeTo> Map(IEnumerable<TTypeFrom> itemToMap);
}
public abstract class Mapper<TTypeFrom, TTypeto> : IMapper<TTypeFrom, TTypeto>
{
private readonly IMappingEngine _mappingEngine;
private readonly IConfiguration _configuration;
protected Mapper(IMappingEngine mappingEngine, IConfiguration configuration)
{
_mappingEngine = mappingEngine;
_configuration = configuration;
_configuration.CreateMap<TTypeFrom, TTypeto>();
}
public TTypeto Map(TTypeFrom typeFrom)
{
return Map<TTypeFrom, TTypeto>(typeFrom);
}
protected TTo Map<TFrom, TTo>(TFrom itemToMap)
{
return _mappingEngine.Map<TFrom, TTo>(itemToMap);
}
public List<TTypeto> Map(IEnumerable<TTypeFrom> itemToMap)
{
return Map<TTypeFrom, TTypeto>(itemToMap);
}
protected List<TTo> Map<TFrom, TTo>(IEnumerable<TFrom> itemsToMap)
{
return itemsToMap.Select(Map<TFrom, TTo>).ToList();
}
}
CategoryRepresentationMapper
public interface ICategoryRepresentationMapper : IMapper<CategoryRepresentation, CategoryRepresentationDto>
{
}
public class CategoryRepresentationMapper : Mapper<CategoryRepresentation, CategoryRepresentationDto>, ICategoryRepresentationMapper
{
public CategoryRepresentationMapper(IMappingEngine mappingEngine, IConfiguration configuration) : base(mappingEngine, configuration)
{
}
}
Setting things up with ninject
IKernel kernel = new StandardKernel();
Mapper.Initialize(map =>
{
map.ConstructServicesUsing(f => kernel.Get(f));
});
kernel.Bind<IMappingEngine>().ToMethod(x => Mapper.Engine);
kernel.Bind<IConfigurationProvider>().ToMethod(x => Mapper.Engine.ConfigurationProvider);
kernel.Bind<IConfiguration>().ToMethod(x => Mapper.Configuration);
kernel.Bind(
x =>
x.FromAssemblyContaining(typeof(IMapper<,>))
.SelectAllClasses()
.InheritedFrom(typeof(IMapper<,>))
.BindAllInterfaces());
var categoryRepresentationMapper = kernel.Get<ICategoryRepresentationMapper>();
I get the following error:
Ninject.ActivationException occurred
HResult=-2146233088
Message=Error activating ICategoryRepresentationMapper
No matching bindings are available, and the type is not self-bindable.
Activation path:
1) Request for ICategoryRepresentationMapper
Suggestions:
1) Ensure that you have defined a binding for ICategoryRepresentationMapper.
2) If the binding was defined in a module, ensure that the module has been loaded into the kernel.
3) Ensure you have not accidentally created more than one kernel.
4) If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.
5) If you are using automatic module loading, ensure the search path and filters are correct.
Edit
If i do this, it will work, BUT I don't want to bind ICategoryRepresentationMapper explicitly. It must be generic since I will have numerous mappers.
IKernel kernel = new StandardKernel();
Mapper.Initialize(map =>
{
map.ConstructServicesUsing(f => kernel.Get(f));
});
kernel.Bind<IMappingEngine>().ToMethod(x => Mapper.Engine);
kernel.Bind<IConfigurationProvider>().ToMethod(x => Mapper.Engine.ConfigurationProvider);
kernel.Bind<IConfiguration>().ToMethod(x => Mapper.Configuration);
// This line below will actualy work but it isn't generic
kernel.Bind(
x =>
x.FromAssemblyContaining< ICategoryRepresentationMapper>()
.SelectAllClasses()
.InheritedFrom(typeof(IMapper<,>))
.BindAllInterfaces());
It seems I found an answer to this problem. The assemblies were to look at where wrong.
Instead of doing this:
kernel.Bind(
x =>
x.FromAssemblyContaining(typeof(IMapper<,>))
.SelectAllClasses()
.InheritedFrom(typeof(IMapper<,>))
.BindAllInterfaces());
I needed to do this and It works:
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new UriBuilder(codeBase);
var path = Uri.UnescapeDataString(uri.Path);
_kernel.Bind(
x =>
x.FromAssembliesInPath(Path.GetDirectoryName(path))
.SelectAllClasses()
.InheritedFrom(typeof(IMapper<,>))
.BindAllInterfaces());