Telling injected automapper to use specific mapping profile in map function - c#

In some cases one of my application services has to generate DTOs with anonymized data for the frontend. The idea was to use different AutoMapper profiles to either map the domain object to the DTO with all properties mapped or the anonymized DTO.
I generated these two profiles and injected them into the service. The AutoMapper is also injected into the service as IMapper and contains all mapping profiles of the application.
What I need now is to tell the mapper to use one specific profile in a call of the Map-function.
Something like this:
var anonymizedDto = _autoMapper.Map<SourceType, DestinationType>
(sourceObject, ops => ops.UseMappingProfile(_anonymizedMapingProfile));
var normalDto = _autoMapper.Map<SourceType, DestinationType>
(sourceObject, ops => ops.UseMappingProfile(_normalMappingProfile));
Is this possible and if yes: how?

As far as i know, you can not change the profile when you call Map.
What you can do is inject two mappers that have been configured with different profiles.
public class MyService : IService {
private readonly IMappingEngine _defaultMapper;
private readonly IMappingEngine _anonymousMapper;
public MyService(IMappingEngine defaultMapper, IMappingEngine anonymousMapper) {
_defaultMapper = defaultMapper;
_anonymousMapper = anonymousMapper;
}
public MyDto GetDefault() {
return _defaultMapper.Map<MyDto>(sourceObject);
}
public MyDto GetAnonymous() {
return _anonymousMapper.Map<MyDto>(sourceObject);
}
}
In your dependency container, set up the constructor injection to respect the name of the ctor parameter. For example with StructureMap:
public void ConfigureAutoMappers(ConfigurationExpression x) {
// register default mapper (static mapping configuration)
Mapper.Configuration.ConstructServicesUsing(t => container.GetInstance(t));
Mapper.Configuration.AddProfile<DefaultProfile>();
var defaultAutomapper = Mapper.Engine
x.For<IMappingEngine>().Use(() => defaultAutoMapper).Named("DefaultAutoMapper");
// register anonymous mapper
var anonConfig = new AnonConfigurationStore( // class derived from ConfigurationStore
new TypeMapFactory(),
AutoMapper.Mappers.MapperRegistry.AllMappers()
);
anonConfig.ConstructServicesUsing(container.GetInstance);
var anonAutoMapper = new MappingEngine(anonConfig);
x.For<IMappingEngine>().Add(anonAutoMapper).Named("AnonAutoMapper");
// Inject the two different mappers into our service
x.For<IService>().Use<MyService>()
.Ctor<IMappingEngine>("defaultMapper").Named("DefaultAutoMapper")
.Ctor<IMappingEngine>("anonymousMapper").Named("AnonAutoMapper");
}

Related

How to use Automapper configuration in ASP.NET MVC controllers

I am using AutoMapper to convert my models into view models. I have the configuration all setup, tested, and working. For reference, this is what my configure method looks like:
public static MapperConfiguration Configure()
{
MapperConfiguration mapperConfiguration = new MapperConfiguration(cfg => {
cfg.CreateMap<Ebill, EbillHarvesterTaskVM>()
cfg.CreateMap<Note, NoteVM>();
cfg.CreateMap<LoginItem, LoginCredentialVM>()
cfg.CreateMap<Login, ProviderLoginVM>()
});
mapperConfiguration.CreateMapper();
return mapperConfiguration;
}
This is what my test looks like:
public void ValidConfigurationTest()
{
var config = AutoMapperConfig.Configure();
config.AssertConfigurationIsValid();
}
What I don't understand is how to access it to actually map one object to another from within my Controller. I know I can call this config method when my app starts up, I have an application configuration class that is called from global.asax that calls my automapper configuration method. I'm not sure how to access all of this from within the controller though. I've read things that say dependency injection, but I'm not familiar enough with what that means to know how to apply it.
I've used Automapper in the past, but I think I implemented the now unavailable static API. Where the config method looks like this:
public static void RegisterMappings()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<ManagementCompany, ManagementCompanyViewModel>();
cfg.CreateMap<ManagementCompanyViewModel, ManagementCompany>();
});
}
The configuration is called in Global.asax
AutoMapperConfig.RegisterMappings();
And where you can call this within a controller to utilize mapping:
AutoMapper.Mapper.Map(managementCompany, managementCompanyVM);
This way doesn't work anymore. When I type AutoMapperMapper there is no Map method to call. What do I need to do to be able to access my mappings and use them?
public static MapperConfiguration Configure() {
MapperConfiguration mapperConfiguration = new MapperConfiguration(cfg => {
cfg.CreateMap<Ebill, EbillHarvesterTaskVM>()
cfg.CreateMap<Note, NoteVM>();
cfg.CreateMap<LoginItem, LoginCredentialVM>()
cfg.CreateMap<Login, ProviderLoginVM>()
});
return mapperConfiguration;
}
build the mapper and register it with the dependency container used by your application.
global.asax
MapperConfiguration config = AutoMapperConfig.Configure();;
//build the mapper
IMapper mapper = config.CreateMapper();
//..register mapper with the dependency container used by your application.
myContainer.Register<IMapper>(mapper); //...this is just an oversimplified example
Update your controllers to explicitly depend on the mapper via constructor injection
private readonly IMapper mapper;
public MyController(IMapper mapper, ...) {
this.mapper = mapper;
//...
}
And call the mapper as needed in the controller actions.
//...
Note model = mapper.Map<Note>(noteVM);
//...

How to mock dependencies for IValueResolver from Automapper in unit tests

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.

Automapper - Value Resolver with generics problem

I am using Automapper with our asp net core application. For the mappings from type A -> B and A -> C, I need an external service to inject some additional data. Therefore I have written a ValueResolver<T>, where T is either B or C. As C# doesn't have OR operator for types, I use a generic T here.
For clarity, my value resolver looks like this:
public class MyValueResolver<T>: IValueResolver<A, T, string>
{
private readonly IMyService _service;
public MyValueResolver(IMyService service)
{
_service = service;
}
public string Resolve(A source, T destination, string destMember, ResolutionContext context)
{
// do something using only source and _service.
return ...;
}
}
Now, in my startup I register automapper like this services.AddAutoMapper();. Therefore, when I use it anywhere in my application, I just have to inject IMapper in the constructor and it will resolve MyValueResolver with IMyService dependency automatically.
The problem arises in tests when I don't use DI and I need both MyValueResolver<B> and MyValueResolver<C>. I tried the following:
var mappingConfig = new MapperConfiguration(cfg => {
cfg.AddProfile(new MappingProfile());
cfg.ConstructServicesUsing(MyValueResolver =>
new MyValueResolver<B>(service));
cfg.ConstructServicesUsing(MyValueResolver =>
new MyValueResolver<C>(service));
});
var mapper = new Mapper(mappingConfig);
But this doesn't work, as it sees tries to use the latter statement for both cases and fails with an error that it cannot cast MyValueResolver<C> to MyValueResolver<B>. But the method doesn't accept types with generics defined (I assume it is a C# limitation?) like this cfg.ConstructServicesUsing(MyValueResolver<C> => new MyValueResolver<C>(service));
Is there any workaround for this, or maybe a different pattern I could use?
I was using ConstructServicesUsing in a completely wrong way.
It takes a function , where Type - is a type of a ValueResolver I want to resolve and Object is the resolved instance. You should return null for the types you don't want to resolve.
So it looks along the lines:
cfg => cfg.ConstructServicesUsing(type => type == typeof(MyValueResolver<B>) ? new MyValueResolver<B>(service) : null));
Credit goes to #LucianBargaoanu for this answer.

How and where to implement automapper in WPF application

I haveBusinessLayer, DTO library,DataService, EntityModel(wher EDMX sits), DTO library refers to both business and data layer. I am trying to implement automapper in data layer, want to map entity object to DTO object and return DTO from the dataService library.
Currently am doing this way
public class DataService
{
private MapperConfiguration config;
public DataService()
{
IMapper _Mapper = config.CreateMapper();
}
public List<Dto.StudentDto> Get()
{
using(var context = new DbContext().GetContext())
{
var studentList = context.Students.ToList();
config = new MapperConfiguration(cfg => {
cfg.CreateMap<Db.Student, Dto.StudentDto>();
});
var returnDto = Mapper.Map<List<Db.Student>, List<Dto.StudentDto>>(studentList);
return returnDto;
}
}
}
How can I move all the mappings to one class and automapper should initialize automatically when call to dataserive is made?
Is it good practice to use AutoMapper in data layer?
Yes.
How can I move all the mappings to one class and automapper should initialize automatically when call to dataserive is made?
You could just create a static class that creates the mappings once:
public static class MyMapper
{
private static bool _isInitialized;
public static Initialize()
{
if (!_isInitialized)
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Db.Student, Dto.StudentDto>();
});
_isInitialized = true;
}
}
}
Make sure that you use this class in your data service:
public class DataService
{
public DataService()
{
MyMapper.Initialize();
}
public List<Dto.StudentDto> GetStudent(int id)
{
using (var context = new DbContext().GetContext())
{
var student = context.Students.FirstOrDefault(x => x.Id == id)
var returnDto = Mapper.Map<List<Dto.StudentDto>>(student);
return returnDto;
}
}
}
Dependending on how you actually host the DAL, you might be able to call the Initialize() method of your custom mapper class from the Main() method of an executable or from somewhere else than the constructor of your DataService class.
Use AutoMapper.Mapper.CreateMap on OnAppInitialize. You can do the implementation of course in an own static class for better style.
There is really no more magic in this - because you only have to register (CreateMap) the mappings one time.
initialize automatically when call to dataserive is made?
You can of course register it too in the constructor.
Here you can take a look at another sample - how to use register in one or two of many extended ways.
In the end AutoMapper should make your life easier and not harder. In my opinion the best way is to register everything at one point - when starting the application.
But you also can do it on demand like seperating each CreateMapin the constructor.
Both ways - just make sure you just call it once.

Mock AutoMapper Mapper.Map call using Moq

Whats the best way to setup a mock expection for the Map function in AutoMapper.
I extract the IMapper interface so I can setup expects for that interface. My mapper has dependencies, so I have to pass those in to the mapper.
What happens when I create 2 instances of my mapper class, with 2 different dependency implementations? I asume that both mappers will use the same dependency instance, since the AutoMapper map is static. Or AutoMapper might even throw an exception because I try to setup 2 different maps with the same objects.?
Whats the best way to solve this?
public interface IMapper {
TTarget Map<TSource, TTarget>(TSource source);
void ValidateMappingConfiguration();
}
public class MyMapper : IMapper {
private readonly IMyService service;
public MyMapper(IMyService service) {
this.service = service
Mapper.CreateMap<MyModelClass, MyDTO>()
.ForMember(d => d.RelatedData, o => o.MapFrom(s =>
service.getData(s.id).RelatedData))
}
public void ValidateMappingConfiguration() {
Mapper.AssertConfigurationIsValid();
}
public TTarget Map<TSource, TTarget>(TSource source) {
return Mapper.Map<TSource, TTarget>(source);
}
}
You don't need to mock AutoMapper, you can just inject the real one as explained here:
var myProfile = new MyProfile();
var configuration = new MapperConfiguration(cfg => cfg.AddProfile(myProfile));
IMapper mapper = new Mapper(configuration);
You can inject this mapper in your unit tests. The whole point of using tools like AutoMapper is for you not having to write a lot of mapping code. If you mock AutoMapper you'll end up having to do that.
Whats the best way to setup a mock expection for the Map function in AutoMapper[?]
Here's one way:
var mapperMock = new Mock<IMapper>();
mapperMock.Setup(m => m.Map<Foo, Bar>(It.IsAny<Foo>())).Returns(new Bar());
http://richarddingwall.name/2009/05/07/mocking-out-automapper-with-dependency-injection/
Points out another way of handling dependencies to AutoMapper, which basically will replace my attempt to extract my own IMapper interface. Instead I will attempt to use the existing IMappingEngine as dependency for my classes, to see if that works.
What you need to do is setup AutoMapper like this (StructureMap is IoC). Then you can make your services dependent on IMappingEngine. From there mocking it should be very easy.
public class AutoMapperConfigurationRegistry : Registry
{
public AutoMapperConfigurationRegistry()
{
ForRequestedType<Configuration>()
.CacheBy(InstanceScope.Singleton)
.TheDefault.Is.OfConcreteType<Configuration>()
.CtorDependency<IEnumerable<IObjectMapper>>().Is(expr => expr.ConstructedBy(MapperRegistry.AllMappers));
ForRequestedType<ITypeMapFactory>().TheDefaultIsConcreteType<TypeMapFactory>();
ForRequestedType<IConfigurationProvider>()
.TheDefault.Is.ConstructedBy(ctx => ctx.GetInstance<Configuration>());
ForRequestedType<IConfiguration>()
.TheDefault.Is.ConstructedBy(ctx => ctx.GetInstance<Configuration>());
}
}
The reason you have to invoke automapper config is because, the UNIT Test cases instance runs outside of main application start up files/configs. Hence the auto mapper configuration has to be called and setup before any unit tests start to run. Ideally you place it in TestInitialize methods.
Here is an example of how I provide an instance of AutoMapper as default Mock for IMapper, using AutoFixture and Moq:
Thanks Lucian Bargaoanu for this hint: Actually you can use cfg.AddMaps(params Assembly[]) and Automapper will search for profiles
Create an ICustomization
public class MapperCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register<IMapper>(() =>
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.AddMaps(
Assembly.Load("BookSharing.Application"),
Assembly.Load("BookSharing.Infrastructure"));
});
return new Mapper(configuration);
});
}
}
Register the customization
fixture.Customize(new MapperCustomization());
following #Dorin Baba answer, I created a useful generic class that can be used to inject any custom mapper in the unit tests
public class MapperCustomization<T> : ICustomization
where T : class
{
public void Customize(IFixture fixture)
{
fixture.Register<IMapper>(() =>
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddMaps(
typeof(T).Assembly);
});
return new Mapper(config);
});
}
}

Categories

Resources