Injecting a service into a AutoMapper Mapping Profile - c#

I have the following setup and its working fine
Startup.cs
public static IServiceCollection AddServices(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
}
MappingProfile class
public class MyDtoMappingProfile: Profile
{
public MyDtoMappingProfile()
{
CreateMap<MyDtoMappingSource<MyContentType>, MyDestinationObject>()
.ForMember(d => d.BatchFeedDate, opt => opt.MapFrom(s => DateTime.UtcNow));
}
}
But I would like to do the following by inject the following service in the mapping profile class
Startup.cs
public static IServiceCollection AddServices(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddAutoMapper(builder => {
builder.AddProfile(new MyDtoMappingProfile(serviceCollection.BuildServiceProvider().GetService<ILookupCodeMapper>()));
});
}
MappingProfile
public class MyDtoMappingProfile: Profile
{
private readonly ILookupCodeMapper lookupCodeMapper;
public MyDtoMappingProfile(ILookupCodeMapper lookupCodeMapper)
{
this.lookupCodeMapper = lookupCodeMapper;
CreateMap<MyDtoMappingSource<MyContentType>, MyDestinationObject>()
.ForMember(d => d.BatchFeedDate, opt => opt.MapFrom(s => DateTime.UtcNow))
.ForMember(d => d.Strand, opt => opt.MapFrom(s => lookupCodeMapper.Code(s.StringValue)));
}
}
But when i do the above im getting this error,
Missing type map configuration or unsupported mapping.

Related

Create Maptser Map config depending on the conditions

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);
}
}

AutoMapper doesn't work in asp net core 5.0.2

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;
}

How to use Automapper IMemberValueResolver custom resolver in CreateMap?

I'm trying to convert an object's member from a short? to a bool using AutoMapper CreateMap.. using a resolver interface.
I created an IMemberValueResolver interface, class to the interface with a resolve function, and then tried using CreateMap with opt.MapFrom and opt.ResolveUsing - nothing works..
Interface and class compiles without error but only the CreateMap is in error.
Tried following documentation:
http://docs.automapper.org/en/stable/Custom-value-resolvers.html
http://docs.automapper.org/en/stable/5.0-Upgrade-Guide.html
Looked for 2 days now and tried EVERYTHING I could find, nothing works.. :(
This is what I have now..
Interface (IMemberValueResolver.cs):
public interface IMemberValueResolver<in TSource, in TDestination, TSourceMember, TMember>
{
TMember Resolve(TSource source, TDestination destination, TSourceMember sourceMember, TMember destinationMember, ResolutionContext context);
}
Mapper configuration (AutoMapperConfig.cs):
public class IsBootstrapResolver : IMemberValueResolver<PageTemplateDataContract, PageTemplateViewModel, short?, bool>
{
public bool Resolve(PageTemplateDataContract source, PageTemplateViewModel destination, short? sourceMember, bool destMember, ResolutionContext context)
{
return sourceMember == 1 ? true : false;
}
}
//NONE OF THESE WORK!!!!!
public class DataContractToViewModelProfile : Profile
{
public DataContractToViewModelProfile()
{
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing<IsBootstrapResolver>().FromMember(x => x.IsBootstrapEnable));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing(IsBootstrapResolver));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.MapFrom<IsBootstrapResolver, short?>(!?!?!?!));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.MapFrom(new IsBootstrapResolver()));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing<IsBootstrapResolver>().FromMember(x => x.IsBootstrapEnable));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing<IsBootstrapResolver, bool>(src => src.IsBootstrapEnable));
}
}
I'm expecting the object mapped with a short? gets converted to a bool using a custom resolver.
Thank you!

Where to place mappings using Automapper static initialization API

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));
}
}

AutoMapper Migrating from static API

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
--

Categories