how to inject a service inside an automapper profile in c# - c#

I am using AutoMapper profiles for mapping entities. In one of the mapping profiles I need to call a service method. I m trying to inject the service using profile constructor but then I m not able to register/add it. Any ideas how can I handle this?
public class HistoryProfile : Profile
{
private readonly MappingService _mappingService;
public HistoryProfile(MappingService mappingService)
{
_mappingService = mappingService;
this.CreateMap<HistoryCHR, History>()
.ForMember(h => h.BirthDate, hisChr => hisChr.MapFrom(x => x.DateOfBirth))
.....................
}
}
private static void InitializeSpecificProfiles()
{
Mapper.Initialize(
cfg =>
{
cfg.AddProfile(new HistoryProfile());
});
}

Related

Injected profile not used in IQueryable.ProjectTo extension method

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.

Error on initializing AutoMapper v6 in ASP.NET MVC App

var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SomeSourceModel, SomeDestinationModel>();
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
I am repeating these code in the project. Thinking to create a common interface IMapper so that I can invoke whenever it is needed to be used.
The solution I create is
private IMapper Mapper(TSource source, TDestination dest)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<source, dest>();
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
It doesn't work. The problem is I can't pass source model and destination model as parameters in this way. How to solve this?
Update 1:
As #12seconds mentioned, I start initializing MapperConfigration in Global.asax.cs
In App_Start folder, I created
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<SourceModel1, DestinationModel1>();
CreateMap<SourceModel2, DestinationModel2>();
CreateMap<SourceModel3, DestinationModel3>();
CreateMap<SourceModel4, DestinationModel4>();
CreateMap<SourceModel5, DestinationModel5>();
Mapper.AssertConfigurationIsValid();
}
}
In Global.asax.cs
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
}
}
And then I tried to call AutoMapperConfiguration.Configure(); in several places. When I start running the App, I got same error messages:
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.
Where I suppose to call AutoMapperConfiguration.Configure();? Did I miss something?
Version 5.0.x +
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
Mapper.AssertConfigurationIsValid();
}
}
The problem solved. Mapper.AssertConfigurationIsValid(); should be executed after Mapper initialized.
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MappingProfile>();
});
Mapper.Configuration.AssertConfigurationIsValid();
}
}

Using Automapper static API in the latest release?

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.

AutoMapper 4.2 and Ninject 3.2

I'm updating a project of mine to use AutoMapper 4.2, and I'm running into breaking changes. While I seem to have resolved said changes, I'm not entirely convinced I've done so in the most appropriate way.
In the old code, I have a NinjectConfiguration, and an AutoMapperConfiguration class that are each loaded by WebActivator. In the new version the AutoMapperConfiguration drops out and I instead instance a MapperConfiguration directly in the NinjectConfiguration class where the bindings are happening, like so:
private static void RegisterServices(
IKernel kernel) {
var profiles = AssemblyHelper.GetTypesInheriting<Profile>(Assembly.Load("???.Mappings")).Select(Activator.CreateInstance).Cast<Profile>();
var config = new MapperConfiguration(
c => {
foreach (var profile in profiles) {
c.AddProfile(profile);
}
});
kernel.Bind<MapperConfiguration>().ToMethod(
c =>
config).InSingletonScope();
kernel.Bind<IMapper>().ToMethod(
c =>
config.CreateMapper()).InRequestScope();
RegisterModules(kernel);
}
So, is this the appropriate way of binding AutoMapper 4.2 using Ninject? It seems to be working so far, but I just want to make sure.
In before IMapper interface didn't existed in the library so you had to implement interface and class below and bound them as a singleton pattern.
public interface IMapper
{
T Map<T>(object objectToMap);
}
public class AutoMapperAdapter : IMapper
{
public T Map<T>(object objectToMap)
{
//Mapper.Map is a static method of the library!
return Mapper.Map<T>(objectToMap);
}
}
Now you simply bind library's IMapper interface to single instance of mapperConfiguration.CreateMapper()
The Problem with your code tho, you should use a single instance(or as Ninject says, a constant) bind.
// A reminder
var config = new MapperConfiguration(
c => {
foreach (var profile in profiles) {
c.AddProfile(profile);
}
});
// Solution starts here
var mapper = config.CreateMapper();
kernel.Bind<IMapper>().ToConstant(mapper);

How to configure Auto mapper in class library project?

I am using auto mapping first time.
I am working on c# application and I want to use auto mapper.
(I just want to know how to use it, so I don't have asp.net app neither MVC app.)
I have three class library projects.
I want to write transfer process in the service project.
So I want to know how and where should I configure the Auto Mapper ?
So based on Bruno's answer here and John Skeet's post about singletons I came up with the following solution to have this run only once and be completely isolated in class library unlike the accepted answer which relies on the consumer of the library to configure the mappings in the parent project:
public static class Mapping
{
private static readonly Lazy<IMapper> Lazy = new Lazy<IMapper>(() =>
{
var config = new MapperConfiguration(cfg => {
// This line ensures that internal properties are also mapped over.
cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.GetMethod.IsAssembly;
cfg.AddProfile<MappingProfile>();
});
var mapper = config.CreateMapper();
return mapper;
});
public static IMapper Mapper => Lazy.Value;
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Source, Destination>();
// Additional mappings here...
}
}
Then in your code where you need to map one object to another you can just do:
var destination = Mapping.Mapper.Map<Destination>(yourSourceInstance);
NOTE: This code is based on AutoMapper 6.2 and it might require some tweaking for older versions of AutoMapper.
You can place the configuration anywhere:
public class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<MyMappings>();
});
}
}
public class MyMappings : Profile
{
public override string ProfileName
{
get { return "MyMappings"; }
}
protected override void Configure()
{
......
}
But it has to be called by the application using the libraries at some point:
void Application_Start()
{
AutoMapperConfiguration.Configure();
}
Nobody outside of your library has to configure AutoMapper
I recommend that you use the instance based approach using an IMapper. That way no one outside your library has to call any configuration method. You can define a MapperConfiguration and create the mapper from there all inside the class library.
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<AppProfile>();
cfg.CreateMap<Source, Dest>();
});
IMapper mapper = config.CreateMapper();
// or
IMapper mapper = new Mapper(config);
var dest = mapper.Map<Source, Dest>(new Source());
Marko's answer is correct.
We can also go by a below simple solution.
public static class ObjectMapper
{
public static IMapper Mapper
{
get
{
return AutoMapper.Mapper.Instance;
}
}
static ObjectMapper()
{
CreateMap();
}
private static void CreateMap()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<SourceClass, DestinationClass>();
});
}
}
And we can use it like.
public class SourceClass
{
public string Name { get; set; }
}
public class DestinationClass
{
public string Name { get; set; }
}
SourceClass c1 = new SourceClass() { Name = "Mr.Ram" };
DestinationClass c2 = ObjectMapper.Mapper.Map<DestinationClass>(c1);
I have used the Patel Vishal's solution and customized it to my needs.
It's a generic class which makes sure only one instance of mapping is saved in memory per object mapping.
TModel - is a DTO object
TData - is a Database table object in Entity Framework
DTO.IBaseModel - is a base class for DTO object which has one property: ID
IBaseModel - is a base class for the entity framework database entity with ID property only
public static class ObjectMapper<TModel, TData>
where TModel : class, DTO.IBaseModel, new()
where TData : class, IBaseModel, new()
{
private static readonly MapperConfiguration _mapperConfiguration;
public static IMapper Mapper => new Mapper(_mapperConfiguration);
static ObjectMapper()
{
_mapperConfiguration ??= CreateMap();
}
private static MapperConfiguration CreateMap()
{
return new (cfg =>
{
cfg.CreateMap<TData, TModel>();
});
}
}
I am using this class in a BaseService<TData, TModel> (Service/Repository pattern) as such:
public virtual TModel Convert(TData t)
{
return ObjectMapper<TModel, TData>.Mapper.Map<TModel>(t);
}
As you can see, it's a virtual method. Mapping can be overwritten, if customization required by the inheriting Service.
I have come across this kind of requirement as well. What I have done in .Net 6.0 is, I create a library project and create the profile class:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Entity, Dto>();
CreateMap<Dto, Entity>();
......
}
}
while in the api or web project, I just create a child class to inherit from the profile above, and register it in startup.cs services.AddAutoMapper(typeof(Startup));.

Categories

Resources