I am using Automapper 6.2.2 and I'm trying to set it up in a Web App. I am trying to use the static Automapper.Initialize() method placed directly in my Global.asax file.
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<LoadArea, LoadAreaWithoutPlannedSlotDto>();
cfg.CreateMap<LoadArea, LoadAreaDto>();
cfg.CreateMap<LoadAreaForCreationDto, LoadArea>().Ignore(d => d.Slots);
cfg.CreateMap<LoadArea, LoadAreaForUpdateDto>();
cfg.CreateMap<LoadAreaForUpdateDto, LoadArea>().ForMember(m => m.Code, i => i.UseDestinationValue());
cfg.CreateMap<PlannedSlot, PlannedSlotDto>();
cfg.CreateMap<PlannedSlotForCreationDto, PlannedSlot>().Ignore(d => d.Area);
cfg.CreateMap<PlannedSlotForUpdateDto, PlannedSlot>();
cfg.CreateMap<UserToReturnDto, User>();
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>();
});
AreaRegistration.RegisterAllAreas();
UnityConfig.RegisterComponents();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
The strange issue is that while this code runs at startup, the mappings are created but none of them are actually configured.
So if I try to Ignore a property in the Mapper.Initialize(...) method, it doesn't work and I get an error when the unmapped property is run into when mapping occurs.
I tried using:
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>().ForMember(d => d.LoadArea, opt => opt.Ignore());
Also tried:
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>(MemberList.None);
And a few other combinations, including an extension method that would ignore all unmapped members:
public static IMappingExpression<TSource, TDestination> Ignore<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map,
Expression<Func<TDestination, object>> selector)
{
map.ForMember(selector, config => config.Ignore());
return map;
}
But what does work is if I try to Ignore the property Inline in my controller as follows:
[HttpPost]
[Route("{loadAreaId}/details")]
public IHttpActionResult AddLoadAreaSlotDetails([FromUri] string loadAreaId, [FromBody] LoadAreaSlotDetailForAddDto loadAreaSlotDetails)
{
var loadAreaSlotDetailEntity = Mapper.Map<LoadAreaSlotDetailForAddDto, LoadAreaSlotDetail>(loadAreaSlotDetails, opt => opt.ConfigureMap().ForMember(d => d.LoadArea, o => o.Ignore()));
_repo.AddLoadAreaSlotDetail(loadAreaSlotDetailEntity);
return Ok();
}
This proves to me that the Ignore works but at the same time I assume that I'm Initializing and configuring my mappings wrongly but I don't know why because many other examples are Initializing in the same way using the static API. I'm doing the same in a .NET Core project (in the ConfigureServices method) and mappings work, it also ignores unmapped properties by default.
Why does this happen ?
Have you tried using AutoMapper Profiles?
AutoMapper Configuration
I was then able to configure this in the Startup.cs of my WebApi application. I was using SimpleInjector as my Container:
var profiles =
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => typeof(Profile).IsAssignableFrom(t))
.ToList();
Mapper.Initialize(
mp =>
{
var mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfiles(profiles));
var mapper = mapperConfiguration.CreateMapper();
container.Register(() => mapper, Lifestyle.Scoped);
});
You then need to define one or more profiles depending on how you want to split out your auto mapper config.
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<UserDetails, UserTransferObject>();
CreateMap<UserAndAccountDetails, UserAndAccountTransferObject>();
CreateMap<User, UserAndAccountTransferObject>()
.ForMember(
dest => dest.DifferentPropertyName,
orig => orig.MapFrom(src => src.OriginalPropertyName));
}
}
Related
Is it possible using Maptser to configure the Map configuration by condition?
puplic static GetCustomConfig(condition)
{
var config = new TypeAdapterConfig();
config.NewConfig<BookEntity, BookModel>()
.Map(dest => dest.Name, src => src.Title)
.Map(dest => dest.Address, src => $"{src.Street} {src.House}")
// other Map
if (condition)
{
// add several Map to the config
}
return config;
}
It is planned that these named configurations will be contained in some static class, from where they will be returned differently depending on the conditions.
For some types, I have a Mapster configured via DI, but I needed custom configurations for some cases.
public static class MapsterConfig
{
public static void MapsterSetup()
{
TypeAdapterConfig<Type1, Type2>
.NewConfig()
.Map(dest => dest.Name, src => src.Title);
}
}
I get an error which says I didn't register AutoMapper, but I did, and such configuration as listed below was used in another projects successfully:
System.InvalidOperationException: 'Unable to resolve service for type 'AutoMapper.Configuration.IConfiguration' while attempting to activate 'PromoAction.Services.Identity.API.Controllers.AccountController'
Please help me to figure out how to make it work in asp net core 5.
AutoMapperConfiguration.cs
public class AutoMapperConfiguration
{
public MapperConfiguration Configure() => new(cfg =>
{
cfg.CreateMap<User, ClientDTO>();
cfg.CreateMap<UserForRegistrationDto, User>()
.ForMember(u => u.UserName, opt => opt.MapFrom(x => x.Email))
.ForMember(u => u.FirstName, opt => opt.MapFrom(x => x.Name))
.ForMember(u => u.LastName, opt => opt.MapFrom(x => x.Surname));
});
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var config = new AutoMapperConfiguration().Configure().CreateMapper();
services.AddSingleton(sp => config);
}
AccountController.cs
public AccountController(IMapper mapper)
{
this._mapper = mapper;
}
Recomended way of registering Autommaper is described in their docs: https://docs.automapper.org/en/stable/Dependency-injection.html#asp-net-core
Create mapping profile and register it using
services.AddAutoMapper(profileAssembly1, profileAssembly2 /*, ...*/);
In your case looks like you registered mapper instance, your example says that you injected mapper instance but exception says that you want to resolve IConfiguration. Check your code, if you don't try to inject IConfiguration (which is not registered).
The problem is that you are not injecting automapper in a good way.
follow the following steps:
Install the nuget package AutoMapper.Extensions.Microsoft.DependencyInjection
Create a new automapper profile inherited from Profile class ( remember to add using AutoMapper), example:
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<User, ClientDTO>();
CreateMap<UserForRegistrationDto, User>()
.ForMember(u => u.UserName, opt => opt.MapFrom(x => x.Email))
.ForMember(u => u.FirstName, opt => opt.MapFrom(x => x.Name))
.ForMember(u => u.LastName, opt => opt.MapFrom(x => x.Surname));
}
}
In your startup class in the method configure services use AddAutoMapper and provide the typeof your startup
services.AddAutoMapper(typeof(Startup));
After that you can inject it normally
public AccountController(IMapper mapper)
{
this._mapper = mapper;
}
I'm using:
AutoMapper 6.1.1
AutoMapper.Extensions.Microsoft.DependencyInjection
3.0.1
It seems my profiles are not being loaded, every time I call the mapper.map I get AutoMapper.AutoMapperMappingException: 'Missing type map configuration or unsupported mapping.'
Here my Startup.cs class ConfigureServices method
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//register automapper
services.AddAutoMapper();
.
.
}
In another project called xxxMappings I have my mapping profiles.
Example class
public class StatusMappingProfile : Profile
{
public StatusMappingProfile()
{
CreateMap<Status, StatusDTO>()
.ForMember(t => t.Id, s => s.MapFrom(d => d.Id))
.ForMember(t => t.Title, s => s.MapFrom(d => d.Name))
.ForMember(t => t.Color, s => s.MapFrom(d => d.Color));
}
public override string ProfileName
{
get { return this.GetType().Name; }
}
}
And call the map this way in a service class
public StatusDTO GetById(int statusId)
{
var status = statusRepository.GetById(statusId);
return mapper.Map<Status, StatusDTO>(status); //map exception here
}
status has values after calling statusRepository.GetById
For my Profile classes, if instead of inherit from Profile I inherit from MapperConfigurationExpression I got a unit test like the one below saying the mapping is good.
[Fact]
public void TestStatusMapping()
{
var mappingProfile = new StatusMappingProfile();
var config = new MapperConfiguration(mappingProfile);
var mapper = new AutoMapper.Mapper(config);
(mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}
My guess is that my mappings are not being initialized.
How can I check that? Am I missing something?
I saw an overload for AddAutoMapper() method
services.AddAutoMapper(params Assembly[] assemblies)
Should I pass all the assemblies in my xxxMappings project. How can I do that?
I figure it out. Since my mappings are in a different project, I did two things
From my API project (where Startup.cs is located, added a reference to my xxxMapprings project)
in ConfigureServices I used the overload AddAutoMapper that gets an Assembly:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//register automapper
services.AddAutoMapper(Assembly.GetAssembly(typeof(StatusMappingProfile))); //If you have other mapping profiles defined, that profiles will be loaded too.
Another solution for fixing the issue when we have mapping profiles in different projects in a solution is:
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
1. Create AutoMapperProfile inherit Profile Class
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
ConfigureMappings();
}
private void ConfigureMappings()
{
// DriverModel and Driver mapping classes
CreateMap<DriverModel, Driver>().ReverseMap();
}
}
2. Register this profile in Configuration service
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(AutoMapperProfile));
}
I have tried various permutations of this but my current configuration (as it relates to AutoMapper) is like this:
builder.RegisterAssemblyTypes().AssignableTo(typeof(Profile)).As<Profile>();
builder.Register(c => new MapperConfiguration(cfg =>
{
foreach (var profile in c.Resolve<IEnumerable<Profile>>())
{
cfg.AddProfile(profile);
}
})).AsSelf().SingleInstance();
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>().As<IMappingEngine>();
I have a constructor using IMapper mapper, however I continue to get the YSOD:
None of the constructors found with'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'
on type '' can be invoked with the available services and parameters:
Cannot resolve parameter 'AutoMapper.IMapper mapper' of constructor
'Void .ctor(...,...,..., AutoMapper.IMapper)'.
This class works perfectly without the automapper reference so I'm certain that the trouble lies with my automapper configuration.
I'm not sure what I'm missing here as I'm very new to both AutoFac and AutoMapper.
Edit:
I've also tried:
builder.Register(c => new MapperConfiguration(cfg =>
{
cfg.CreateMap<IdentityUser, AspNetUser>().ReverseMap();
})).AsSelf().SingleInstance();
builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>();
//I've tried both of these lines separately, neither work
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
I've also tried manually adding the profiles per the suggestion in the comments
As I mentioned in a comment, your AutoFac code appears to be correct (except for the assembly scanning portion).
I created the following test app, and it does in fact run without any exceptions and puts a 3 into the Output window (as intended):
using System.Diagnostics;
using Autofac;
using AutoMapper;
namespace Sandbox
{
public partial class App
{
public App()
{
var builder = new ContainerBuilder();
builder.Register(
c => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TestProfile());
}))
.AsSelf()
.SingleInstance();
builder.Register(
c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>()
.As<IMappingEngine>();
builder.RegisterType<Test>().AsSelf();
var container = builder.Build();
container.Resolve<Test>();
}
}
public class TestProfile : Profile
{
protected override void Configure()
{
CreateMap<Source, Destination>();
}
}
public class Test
{
public Test(IMapper mapper)
{
var source = new Source { Id = 3 };
var destination = mapper.Map<Destination>(source);
Debug.Print(destination.Id.ToString());
}
}
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
}
}
I would suggest creating a new branch of your app in version control and stripping things out until it works.
This is worked for me...
builder = new ContainerBuilder();
builder.Register(
c => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TestProfile());
}))
.AsSelf()
.SingleInstance();
builder.Register(
c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>()
.As<IMappingEngine>();
builder.RegisterType<Test>().AsSelf();
var container = builder.Build();
container.Resolve<Test>();
https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API
this change breaks my system.
Before update, I use:
===> Startup.cs
public class Startup
{
public Startup(IHostingEnvironment env)
{
...
MyAutoMapperConfiguration.Configure();
}
}
===> MyAutoMapperConfiguration.cs
public class MyAutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(a =>
{
a.AddProfile<AbcMappingProfile>();
a.AddProfile<XyzMappingProfile>();
a.AddProfile<QweMappingProfile>();
});
}
}
===> AbcMappingProfile.cs
public class AbcMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<AbcEditViewModel, Abc>();
Mapper.CreateMap<Abc, AbcEditViewModel>();
...
}
}
ERROR:
'Mapper.CreateMap()' is obsolete: 'The static API will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed. Use CreateMapper to create a mapper instanace.'
I can use Mapper.Map. Now How can I use it
Instead of:
Mapper.CreateMap<AbcEditViewModel, Abc>();
The new syntax is:
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<AbcEditViewModel, Abc>();
});
Then:
IMapper mapper = config.CreateMapper();
var source = new AbcEditViewModel();
var dest = mapper.Map<AbcEditViewModel, Abct>(source);
(Source with more examples)
Instead of Automapper Profile use IMapperConfigurationExpression extension:
Mapping configuration:
public static class AutoMapperConfig
{
public static IMapperConfigurationExpression AddAdminMapping(
this IMapperConfigurationExpression configurationExpression)
{
configurationExpression.CreateMap<Job, JobRow>()
.ForMember(x => x.StartedOnDateTime, o => o.PreCondition(p => p.StartedOnDateTimeUtc.HasValue))
.ForMember(x => x.StartedOnDateTime, o => o.MapFrom(p => p.StartedOnDateTimeUtc.Value.DateTime.ToLocalTime()))
.ForMember(x => x.FinishedOnDateTime, o => o.PreCondition(p => p.FinishedOnDateTimeUtc.HasValue))
.ForMember(x => x.FinishedOnDateTime, o => o.MapFrom(p => p.FinishedOnDateTimeUtc.Value.DateTime.ToLocalTime()));
return configurationExpression;
}
}
Integration (Startup.cs etc.):
var mappingConfig = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.AddAdminMapping();
});
services.AddSingleton(x => mappingConfig.CreateMapper());
Dependency injection added a whole level of complexity to my legacy project that I just didn't want to deal with. As the same library is called with many different technologies, Webforms, MVC, Azure Service, etc...
Also dependency injection would of forced me to rewrite several methods or pass an IMapper around.
So I just reverse engineered what it was doing in 8.0 and wrote a wrapper for it.
public static class MapperWrapper
{
private const string InvalidOperationMessage = "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.";
private const string AlreadyInitialized = "Mapper already initialized. You must call Initialize once per application domain/process.";
private static IConfigurationProvider _configuration;
private static IMapper _instance;
private static IConfigurationProvider Configuration
{
get => _configuration ?? throw new InvalidOperationException(InvalidOperationMessage);
set => _configuration = (_configuration == null) ? value : throw new InvalidOperationException(AlreadyInitialized);
}
public static IMapper Mapper
{
get => _instance ?? throw new InvalidOperationException(InvalidOperationMessage);
private set => _instance = value;
}
public static void Initialize(Action<IMapperConfigurationExpression> config)
{
Initialize(new MapperConfiguration(config));
}
public static void Initialize(MapperConfiguration config)
{
Configuration = config;
Mapper = Configuration.CreateMapper();
}
public static void AssertConfigurationIsValid() => Configuration.AssertConfigurationIsValid();
}
Initialize it just like you did in previous versions
public static class AutoMapperConfig
{
public static void Configure()
{
MapperWrapper.Initialize(cfg =>
{
cfg.CreateMap<Foo1, Foo2>();
});
MapperWrapper.AssertConfigurationIsValid();
}
}
And just call it in your startup, (Global.asax etc..)
AutoMapperConfig.Configure();
Then all you have to do is add MapperWrapper before all your static calls. And everything works as it did before.
MapperWrapper.Mapper.Map<Foo2>(Foo1);
Ben Walters: Dependency injection added a whole level of complexity to
my legacy project that I just didn't want to deal with...
HI
Furthermore, you can apply the class alias using statement
and no need to change the code, just change the using statement.
Define a using directive and a using alias for a class:
https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/using-directive#example-2
--
.Your class implementation for compatibility.
namespace AutoMappers
{
public class Mapper
{
public static void Initialize(Action<AutoMapper.IMapperConfigurationExpression> config)
{
...
}
}
}
.Change "using AutoMapper" to "using Mapper = AutoMappers.Mapper".
using Mapper = AutoMappers.Mapper; <-- using statement changed
namespace ...
{
public class ...
{
public ...(...)
{
Mapper.Initialize(cfg => cfg.CreateMap<TSource1, TDestination1>()); <-- other code line kept originally
--