I'm trying to map one complex object to another using instance API:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Student, PersonType>();
cfg.CreateMap<Professor, PersonType>();
cfg.CreateMap<Branch, BranchType>()
.ForMember(x => x.Departments, opt => opt.MapFrom(src =>
new DepartmentType[] {
new DepartmentType
{
Students = Mapper.Map<Student[], PersonType[]> (src.Students),
Professors = Mapper.Map<Professor[], PersonType[]> (src.Professors),
Name = src.DepartmentName
}
}))
.ForMember(x => x.Name, opt => opt.MapFrom(src => src.Name))
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
});
var mapper = config.CreateMapper();
var test = mapper.Map<BranchType>(source);
The problem is I don't know how to achieve this without mixing instance and static API which is not working. Here is the error:
InvalidOperationException: 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.
Apparently mixing of the static and instance based approaches is not allowed:
Students = Mapper.Map<Student[], PersonType[]> (src.Students)
How to use existing map to apply it to a property of the complex object with instance API?
Related
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.
I have the following mapping profile
public class DomainProfile : Profile
{
private FootballPredictionsContext m_Context;
public DomainProfile(FootballPredictionsContext context)
{
m_Context = context;
}
public DomainProfile()
{
CreateMap<TipModel, Tip>()
.ForMember(tip => tip.BetType, m => m.MapFrom(x => m_Context.BetTypes.First(y => y.Name == x.BetType)))
.ForMember(tip => tip.BetCategory, m => m.MapFrom(x => m_Context.BetCategories.First(y => y.Name == x.BetCategory)))
.ForMember(tip => tip.Sport, m => m.MapFrom(x => m_Context.Sports.First(y => y.Name == x.Sport)))
.ForMember(tip => tip.Tipster, m => m.MapFrom(model => m_Context.Tipsters.First(y => y.Username == model.Tipster)));
}
}
As you can see, some of the mappings are using the DbContext, so I have to somehow inject it in the DomainProfile
In the Startup class I am initializing the Automapper normally
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IUnificator), typeof(Unificator));
services.AddDbContext<FootballPredictionsContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database")));
services.AddDbContext<UnificationContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Database")));
services.AddSingleton(provider => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new UserProfile(provider.GetService<IUserManager>()));
}).CreateMapper());
services.AddMvc();
}
I tried this solution, but I am receiving 'Cannot resolve scoped service 'FootballPredictions.DAL.FootballPredictionsContext' from root provider.'
I've come across a similar problem recently and it was down to the fact that I was trying to inject a service into a service with a longer lifetime (e.g. transient and scoped). What lifetime is associated with the DomainProfile class? Have you tried changing that to Scoped or Transient to see if that helps?
As implemented by #DimitarTsonev:
So, changing the mapper scope to
services.AddScoped(provider => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new DomainProfile(provider.GetService<FootballPredictionsContext>()));
}).CreateMapper());
fixed the issue
Assuming you've actually registered you context (services.AddDbContext, for example), the most likely cause of that exception is that you're registering AutoMapper before you're registering the context. Make sure your context is registered first, before you do something like services.AddAutoMapper.
I know that using automapper's static API I can do this:
Mapper.Initialize(cfg =>
cfg.CreateMap<Source, Destination>());
Mapper.Configuration.AssertConfigurationIsValid();
but now I've switched to the instance API:
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<AppProfile>();
cfg.CreateMap<Source, Dest>();
});
var mapper = config.CreateMapper();
How/where can I check if the configuration is valid using the instance API?
You can also do the validation using:
mapper.ConfigurationProvider.AssertConfigurationIsValid();
Given the following code:
public static IMapper ConfigureMapper()
{
var config = new MapperConfiguration(cfg => {
cfg.ShouldMapField = fi => false;
cfg.ShouldMapProperty = pi => pi.GetMethod != null && (pi.GetMethod.IsPublic || pi.GetMethod.IsVirtual);
cfg.CreateMap<ServiceModel.Types.NonRiskRequirement, RequiredSignature>()
.ForMember(dest => dest.ApplicantFlag, opt => opt.MapFrom(src => src.RequiredSignatureApplicantFlag))
.ForMember(dest => dest.InsuredFlag, opt => opt.MapFrom(src => src.RequiredSignatureInsuredFlag));
cfg.CreateMap<ServiceModel.Types.NonRiskRequirement, NonRiskWorkItem>()
.ForMember(dest => dest.RequiredSignature, opt => opt.MapFrom(
src => Mapper.Map<ServiceModel.Types.NonRiskRequirement, RequiredSignature>(src)))
.ForMember(dest => dest.WorkType, opt => opt.MapFrom(src => src.WorkType));
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
What is the proper syntax to get rid of the static "Mapper.Map"?, I'm using AutoMapper 5.1.1 with Autofac 4.1.1 and getting this exception back.
System.InvalidOperationException: 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.
Thank you,
Stephen
Don't use MapFrom, use ResolveUsing. It gives you a ResolutionContext object that includes a Mapper property that you use to map, not this static way, which is not recommended, and also now broken.
I've upgraded Automapper from 4.2.1 to 5.0.0. I'm using the static API in a WebApi2 project and I'm trying to get the mapping to work, so I tried following this SO answer.
So I changed the code to the following:
public static class AutoMapping
{
public static void Config()
{
Mapper.Initialize(main =>
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<MyModel, MyDto>().ReverseMap();
});
config.AssertConfigurationIsValid();
});
}
}
The above is called from Global.asax.
However, I get exception:
Mapper not initialized. Call Initialize with appropriate configuration.
What is the correct way to initialize Automapper, and do I need to change all my controllers now for mapping?
EDIT1
Firstly, the code above must be:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<MyModel, MyDto>().ReverseMap();
});
Mapper.Configuration.AssertConfigurationIsValid();
Secondly, the problem might be in the following method which I use to ignore missing properties:
public static IMappingExpression<TSource, TDestination> IgnoreUnmapped<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var typeMap = Mapper.Configuration.FindTypeMapFor<TSource, TDestination>();
if (typeMap != null)
{
foreach (var unmappedPropertyName in typeMap.GetUnmappedPropertyNames())
{
expression.ForMember(unmappedPropertyName, opt => opt.Ignore());
}
}
return expression;
}
I'm assuming 'Mapper.Configuration' is not yet configured because the above method is called within Initialize which configures the mapping.
Is there an existing method within Automapper itself which I can use instead of the above?
EDIT2
Would the following syntax work?
cfg.CreateMap<MyModel, MyDto>().ReverseMap().ForAllMembers(opt => opt.Ignore());
Actually your code does nothing now. You have to change it like this:
public static class AutoMapping
{
public static void Config()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<MyModel, MyDto>().ReverseMap();
});
Mapper.AssertConfigurationIsValid();
}
}
UPD (after EDIT1):
Try to use expression.TypeMap instead of Mapper.Configuration.FindTypeMapFor<TSource, TDestination>()
Maybe this helps:
Setup the Configuration:
var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>());
Then where the mapping should take place:
var mapper = new Mapper(config);
OrderDto dto = mapper.Map<OrderDto>(order);
You could also expose the 'config' as a static property, and use that in your project. There's also an alternative to create a static 'Mapper' property that is configured. Then you can use that static 'Mapper' property in your project.