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
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 have a .NET Core 3.1 API with the following Nuget packages:
AutoMapper (10.1.1)
AutoMapper.Extensions.Microsoft.DependencyInjection (8.1.1)
I'm trying to map a value from an entity to a dto using a ValueResolver and I'm having an exception:
AutoMapperMappingException: Cannot create an instance of type TestAutomapperResolver.Mapping.CustomResolver
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAutoMapper(cfg => cfg.AddMaps(typeof(TestProfile).Assembly));
}
TestProfile.cs
public class TestProfile : Profile
{
public TestProfile()
{
CreateMap<TestEntity, TestDto>()
.ForMember(src => src.PropertyToBeMappedByResolver, opts => opts.MapFrom<CustomResolver>());
}
}
public class CustomResolver : IValueResolver<TestEntity, TestDto, string>
{
public string Resolve(TestEntity source, TestDto destination, string destMember, ResolutionContext context)
{
return "String generated with resolver";
}
}
When doing mapper.CreateMap<TestDto>(entity); I'm receiving that exception.
By the way, using this resolver as opts => opts.MapFrom(CustomResolver()) is not an option because I want to inject some service into that resolver.
Any idea?
You're using AddMaps when you shouldn't be. AddMaps just adds profiles and mappings, but doesn't add all the extra services that the DI package does.
This will do it properly:
services.AddAutoMapper(typeof(TestProfile).Assembly);
Now, AutoMapper gives you a very unhelpful error here, but the issue goes back to Microsoft Dependency injection. The DI doesn't know about your custom resolver type, so it doesn't even try.
Since you didn't use the DI package's extension method, the resolver doesn't get added to the service collection. You can manually add these services if needed:
services.AddAutoMapper(cfg => cfg.AddMaps(typeof(TestProfile).Assembly));
services.AddTransient<CustomResolver>();
I've just turned to using AutoMapper via dependency injections opposed to manual configuration. My profiles are all coming through fine now, but my custom type converters are not.
I was under the impression that using services.AddAutoMapper() along with implementations of ITypeConverter<,> would do the trick, as below in Startup.cs. I can see the Types are added in as Transient to the services but I haven't tracked down how this is meant to translate into creating a map.
public class DateTimeToDateConverter : ITypeConverter<DateTime, Date>
{
public Date Convert(DateTime source, Date destination, ResolutionContext context)
{
return Date.FromDateTime(source);
}
}
public class DateToDateTimeConverter : ITypeConverter<Date, DateTime>
{
public DateTime Convert(Date source, DateTime destination, ResolutionContext context)
{
return source.ToDateTime();
}
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAutoMapper(Assembly.GetAssembly(typeof(Startup)));
My original code for manual configuration is below. I assume there must be something obvious that I'm missing as to how to get the dependency injection doing the same thing!
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<DateTime, Date>().ConvertUsing(s => Date.FromDateTime(s));
cfg.CreateMap<Date, DateTime>().ConvertUsing(s => s.ToDateTime());
cfg.AddMaps(Assembly.GetAssembly(typeof(AutoMappingConfiguration)));
});
After previous question I have a simple implementation of IValueResolver
public class FileLinkResolver : IValueResolver<Configuration, ConfigurationDto, string>
{
private readonly IFileStorage _fileStorage;
public FileLinkResolver(IFileStorage fileStorage)
{
_fileStorage = fileStorage;
}
public string Resolve(Configuration source, ConfigurationDto destination, string destMember, ResolutionContext context)
{
return _fileStorage.GetShortTemporaryLink(source.Path);
}
}
and simple mapping profile
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Configuration, ConfigurationDto>()
.ForMember(dest => dest.FilePath, opt => opt.MapFrom<FileLinkResolver>());
}
}
For production it works as expected when the following setup
services.AddTransient<IFileStorage>(...);
services.AddAutoMapper();
is used and then in controller IMapper is injected.
In unit test I try to stub the mapper
var mapperStub = new Mapper(new MapperConfiguration(map => map.AddProfile(new MappingProfile())));
and when I run tests for method witch should return mapped dto, I got
AutoMapper.AutoMapperMappingException : Error mapping types.
Mapping types:
Configuration -> ConfigurationDto
DataAccess.Models.Configuration -> Dto.ConfigurationDto
Type Map configuration:
Configuration -> ConfigurationDto
DataAccess.Models.Configuration -> Dto.ConfigurationDto
Destination Member:
FilePath
---- System.MissingMethodException : No parameterless constructor defined for this object.
I've tried to add parameterless constructor to FileLinkResolver but then, NullReferenceException
This is the question: how to resolve dependencies for ValueResolver
In the current example, the mapper is unable to resolve IFileStorage dependency when testing.
Change the way the mapper is created to more closely match how it is done at run-time.
IServiceCollection services = new ServiceCollection();
//mocking service using MOQ
var mock = Mock.Of<IFileStorage>(_ =>
_.GetShortTemporaryLink(It.IsAny<string>()) == "fake link"
);
//adding mock to service collection.
services.AddTransient<IFileStorage>(sp => mock);
//adding auto mapper with desired profile;
services.AddAutoMapper(typeof(MappingProfile));
//...add other dependencies as needed to service collection.
//...
//build provider
IServiceProvider serviceProvider = service.BuilderServiceProvider();
//resolve mapper
IMapper mapperStub = serviceProvider.GetService<IMapper>();
//Or resolve subject under test where mapper is to be injected
SubjectClass subject = serviceProvider.GetService<SubjectClass>();
//assuming ctr: public SubjectClass(IMapper mapper, .....) { ... }
Now technically this is not mocking the value resolver. It mocks the dependencies of the resolver, and uses an actual resolver from the profile. But this should provide the desired behavior when testing the target.
I have a problem with Autofac and Automapper.
In my WebApi I have this code to register my classes:
public static void Initialize(HttpConfiguration config, IContainer container)
{
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
_container = container;
}
private static IContainer RegisterServices(ContainerBuilder builder)
{
//Register your Web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterModule<MapperInstaller>();
builder.RegisterType<VurpEntities>();
builder.RegisterType<EntityFrameworkUnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<AppService>().As<IAppService>();
builder.RegisterType<AppRepository>().As<IAppRepository>();
builder.RegisterType<BusinessService>().As<IBusinessService>();
builder.RegisterType<BusinessRepository>().As<IBusinessRepository>();
//...
//all my types are registered here
//...
//Set the dependency resolver to be Autofac.
_container = builder.Build();
return _container;
}
I created a Resolver to map a special field of my object:
public class ProductTypeResolver : IMemberValueResolver<ProductDto, Product, string, Enumeration.ProductType>
{
public ProductType Resolve(ProductDto source, Product destination, string sourceMember, ProductType destMember, ResolutionContext context)
{
Enum.TryParse(sourceMember, out destMember);
return destMember;
}
}
I have a Profile that map DTO object in Entity object:
CreateMap<ProductDto, Product>()
.ForMember(dest => dest.Category, opt => opt.Ignore())
.ForMember(dest => dest.Feature, opt => opt.Ignore())
opts.ResolveUsing<ProductTypeResolver, string>(src => src.type));
When I try to map the object Product p = _mapper.Map<Product>(productDto); I receive the error:
An exception of type 'System.ObjectDisposedException' occurred in
Autofac.dll but was not handled in user code
Additional information: This resolve operation has already ended. When
registering components using lambdas, the IComponentContext 'c'
parameter to the lambda cannot be stored. Instead, either resolve
IComponentContext again from 'c', or resolve a Func<> based factory to
create subsequent components from.
Anyone can help me?
I solved with this:
builder.Register(c =>
{
//This resolves a new context that can be used later.
var context = c.Resolve<IComponentContext>();
var config = context.Resolve<MapperConfiguration>();
return config.CreateMapper(context.Resolve);
})
.As<IMapper>()
.InstancePerLifetimeScope();