Automapper throws System.ArgumentException Method T GetValue[T]() is a generic method definition (Parameter 'method') - automapper

I am working on a for me rather large blazor web app and use Automapper to simplify mapping. After upgrade to VS 2019 and update to newest nuget packages I get this error:
avsweb.ApplicationTests.Mappings.MappingTests.ShouldHaveValidConfiguration
Source: MappingTests.cs line 69
Duration: 1 ms
Message:
System.ArgumentException : Method T GetValue[T]() is a generic method definition (Parameter 'method')
Stack Trace:
Expression.ValidateMethodInfo(MethodInfo method, String paramName)
Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
Expression.Call(Expression instance, MethodInfo method)
<>c.<MemberAccesses>b__3_0(Expression inner, MemberInfo getter)
Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
ExpressionFactory.MemberAccesses(IEnumerable`1 members, Expression obj)
TypeMapPlanBuilder.Chain(IMemberMap memberMap, Type destinationType)
TypeMapPlanBuilder.BuildValueResolverFunc(IMemberMap memberMap, Expression destValueExpr, Expression defaultValue)
TypeMapPlanBuilder.CreatePropertyMapFunc(IMemberMap memberMap, Expression destination, MemberInfo destinationMember)
TypeMapPlanBuilder.CreateAssignmentFunc(Expression destinationFunc)
TypeMapPlanBuilder.CreateMapperLambda(HashSet`1 typeMapsPath)
TypeMap.CreateMapperLambda(IConfigurationProvider configurationProvider, HashSet`1 typeMapsPath)
TypeMap.Seal(IConfigurationProvider configurationProvider)
MapperConfiguration.Seal()
MapperConfiguration.ctor(MapperConfigurationExpression configurationExpression)
<>c.<AddAutoMapperClasses>b__12_2(IServiceProvider sp)
CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
ServiceProviderEngineScope.GetService(Type serviceType)
ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
<>c.<AddAutoMapperClasses>b__12_3(IServiceProvider sp)
CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
ServiceProviderEngine.GetService(Type serviceType)
ServiceProvider.GetService(Type serviceType)
ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
MappingTests.ctor(ITestOutputHelper _outputHelper) line 65
Automapper is registerd using the ServiceCollection Extention. All ValuResolvers are registered through custom service extension.
I have at the moment no glue where to look at. Does anyone hav a hint?

For anyone coming across this post later and "removing the generic method" is not an answer: AutoMapper has a feature to map Pre/PostFixes.
By default it knows the prefix "Get" and tries to map the source method GetValue[T]()to the destination property Value. However, it doesn't know how to map generic methods by default, thus the mapping fails.
To fix this, there are two options:
Rename the method to not include the "Get"
As stated in the documentation below, clear the prefixes using: cfg.ClearPrefixes()
This behaviour is defined briefly here:
https://docs.automapper.org/en/stable/Configuration.html#recognizing-pre-postfixes

OK, found the reasen for problem. To hande key/value pairs I added following class to project:
public class KeyValuePair
{
public string Key { get; set; }
public string Value { get; set; }
public T GetValue<T>()
{
var value = (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(Value);
return value;
}
public void SetValue<T>(T value)
{
Value = value.ToString();
}
}
The method GetValue should provide a way to get the string value converted to object of type T. Through some automation process a mapping profile hase been defined for this class. This finally leads to the Argument exception either when validating mapping or when instantiating automapper (IMapper). I don't know why this error exactly happens, but mapping is for this class not useful anyway. so removing it from profile solved the case.
The hard part of it was to find the failing class out of 50+ classes / dto's. Luckily I create the Automapper profile dynamically during startup from methods in the class definitions. So it was possible to implement a testmethod with xUnit and Memberdata creating a mappingprofile for each single class and verify each single configuration. This did show the failing mappings.
Here a snippet how the map is created:
public partial class SettingDto : IMapperBase<Setting>
{
public void MappingBase(Profile profile)
{
// Generated Mapping
profile.CreateMap<Setting, SettingDto>()
.IncludeAllDerived()
.ForMember(m => m.Id, s => s.MapFrom(id => id.SettingId))
;
}
}
Class Setting derived from KeyValuePair class shown above.
Here a snippet for testing the single map:
[Theory]
[InlineData(typeof(ReychSettingDto),"MappingBase")]
public void SingleClassProfileMapperTest(Type type, string method)
{
// Arrange
logger.Information("Processing {#Name}", type.Name);
var profile = new MappingProfile(type, method) as Profile;
var config = new MapperConfiguration(cfg => { cfg.AddProfile(profile); });
// Act
//Action act = () => config.AssertConfigurationIsValid();
//act.Should().Throw<AutoMapperConfigurationException>();
try
{
config.AssertConfigurationIsValid();
}
catch (AutoMapperConfigurationException aex)
{
logger.Error(aex, "Config");
}
// Assert
}
public static IEnumerable<object[]> GetProfiles(Type mapType, string method, int skip, int count)
{
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "avsweb.Application");
var allObjects = assembly.GetExportedTypes().Where(t => t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == mapType))
.Select(o => new object[] { o, method })
.ToList();
if (count < 0)
{
return allObjects;
}
return allObjects.Skip(skip).Take(count);
}
}
public partial class MappingProfile : Profile
{
public MappingProfile(Type type, string method)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod(method);
methodInfo?.Invoke(instance, new object[] { this });
}
}
Hopefuly it helps somebody

Related

Mockito Matching parameterized method with multiple interface

Hi I've created a nice interface for my service which accepts object implementing 2 interfaces, but now I have difficulties to create a matcher for this interface.
Anyone has any idea how to create a matcher for the following?
<T extends HasDocumentTags & HasResources> ResponseEntity<Void> setDocumentMetadata(T t);
Just an any() wouldn't help here, as the method is overloaded already twice
ResponseEntity<Void> setDocumentMetadata(List<Document> attachments);
ResponseEntity<Void> setDocumentMetadata(ApproveDocumentsCommand<?> command);
Now I'm trying to mock the service and defining an answer
when(service.setDocumentMetadata( ??? ).thenReturn(anAnswer);
I just can't figure out the right matcher for any(), eq() or whatever will be working.
Or am I trying thing which aren't possible (in java8)?
Can you help me?
use any for type Document and ApproveDocumentsCommand:
when(service.setDocumentMetadata(any(Document.class)).thenReturn(anAnswer1);
when(service.setDocumentMetadata(any(ApproveDocumentsCommand.class)).thenReturn(anAnswer2);
I this any(Document.class) and any(ApproveDocumentsCommand.class) should be enoght.
also you can use ArgumentMatcher , if you need check type for arguments with some extra checks:
when(service.setDocumentMetadata(argThat(new MyCommonMatcher<>())))
.thenReturn(responseEntityOk);
when(service.setDocumentMetadata(argThat(new MyDocumentMatcher<>())))
.thenReturn(responseEntityOk);
when(service.setDocumentMetadata(argThat(new MyApproveDocumentsCommandMatcher<>())))
.thenReturn(responseEntityOk);
class MyCommonMatcher<T extends HasDocumentTags & HasResources>
extends ArgumentMatcher<T>{
#Override
public boolean matches(Object argument) {
return (argument instanceof HasResources) && (argument instanceof HasDocumentTags);
}
}
class MyDocumentMatcher<T extends HasDocumentTags & HasResources>
extends ArgumentMatcher<T>{
#Override
public boolean matches(Object argument) {
return argument instanceof Document;
}
}
class MyApproveDocumentsCommandMatcher<T extends HasDocumentTags & HasResources>
extends ArgumentMatcher<T>{
#Override
public boolean matches(Object argument) {
return argument instanceof ApproveDocumentsCommand;
}
}

Forgetting to map classes with AutoMapper

The application I'm working on has several places where we use AutoMapper to map entities.
The problem is if I had a model entity from one side to the other of the project, many times I forget to add the mapping for the new entity (I just need a copy paste from other entities), ending up that the solution compiles and I get no exception.
It just launches the application without full functionality and no debugging messages, which makes difficult to figure out what I've missed.
Is there any way to force the compiler at compile time to give me an error in case I forget to do a mapping?
AFAIK, there isn't a possibility to force compile-time checking for Automapper.
Nevertheless, there is a possibility to verify the correctness of your mappings:
After you've defined all your mappings, call the AssertConfigurationIsValid method which will throws an AutoMapperConfigurationException exception if the defined mappings are broken.
You can make this a part of your unit or integration test suite.
I had the same problem and decided to solve it by wrapping up AutoMapper. For each source-destination map I provide a method that I create after I've added it to my AutoMapper profile.
This may take away some of the ease of implementing AutoMapper but I find the compile time checking worth it.
public class MyType {
public string SomeProperty { get;set; }
}
public class MyOtherType {
public string SomeProperty { get;set; }
}
public class MyAlternateType {
public string AlternateProperty {get;set;}
}
public class AutoMapperProfile : Profile {
public AutoMapperProfile() {
CreateMap<MyType, MyOtherType>();
CreateMap<MyAlternateType, MyOtherType>()
.ForMember(ot => ot.SomeProperty, options => options.MapFrom(at => at.AlternateProperty));
}
}
public interface IMyMappingProvider {
// Uncomment below for Queryable Extensions
//IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand);
//IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand);
/*
* Add your mapping declarations below
*/
MyOtherType MapToMyOtherType(MyType source);
MyOtherType MapToMyOtherType(MyAlternateType source);
}
public class MyMappingProvider : IMyMappingProvider {
private IMapper Mapper { get; set; }
public MyMappingProvider(IMapper mapper) {
Mapper = mapper;
}
/* Uncomment this for Queryable Extensions
public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, params Expression<Func<TDestination, object>>[] membersToExpand) {
return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(null, membersToExpand);
}
public IQueryable<TDestination> ProjectTo<TSource, TDestination>(IQueryable<TSource> source, IDictionary<string, object> parameters, params string[] membersToExpand) {
return new ProjectionExpression(source, Mapper.ConfigurationProvider.ExpressionBuilder).To<TDestination>(parameters, membersToExpand);
}
*/
/*
* Implement your mapping methods below
*/
public MyOtherType MapToMyOtherType(MyType source) {
return Mapper.Map<MyType, MyOtherType>(source);
}
public MyOtherType MapToMyOtherType(MyAlternateType source) {
return Mapper.Map<MyAlternateType, MyOtherType>(source);
}
}
If you are using the AutoMapper's Queryable extensions you can add the following class and uncomment the Queryable Extensions code above.
public static class QueryableExtensions {
/*
* Implement your extension methods below
*/
public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
{
return mapper.ProjectTo<MyType, MyOtherType>(source, membersToExpand);
}
public static IQueryable<MyOtherType> ProjectToMyOtherType(this IQueryable<MyAlternateType> source, IMyMappingProvider mapper, params Expression<Func<MyOtherType, object>>[] membersToExpand)
{
return mapper.ProjectTo<MyAlternateType, MyOtherType>(source, membersToExpand);
}
}
Tested with AutoMapper 6.1.1 using LinqPad:
var autoMapperConfig = new MapperConfiguration(cfg => { cfg.AddProfile(new AutoMapperProfile()); });
IMyMappingProvider mapper = new MyMappingProvider(autoMapperConfig.CreateMapper());
var myTypes = new List<MyType>()
{
new MyType() {SomeProperty = "Test1"},
new MyType() {SomeProperty = "Test2"},
new MyType() {SomeProperty = "Test3"}
};
myTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();
var myAlternateTypes = new List<MyAlternateType>()
{
new MyAlternateType() {AlternateProperty = "AlternateTest1"},
new MyAlternateType() {AlternateProperty = "AlternateTest2"},
new MyAlternateType() {AlternateProperty = "AlternateTest3"}
};
myAlternateTypes.AsQueryable().ProjectToMyOtherType(mapper).Dump();
mapper.MapToMyOtherType(myTypes[0]).Dump();
As #serge.karalenka said, don't forget to still test your mapping configuration by calling AssertConfigurationIsValid().

Inject parameter into automapper custom ValueResolver using Ninject

I'm using automapper library to convert my Model into my ViewModel. For each Model, I create profile which inside i add my maps using CreateMap.
I want to use custom ValueResolver in which it will get the logged user ID from IContext, so i need to pass implementation of IContext using Ninject.
Inside my profile class:
Mapper.CreateMap<ViewModel, BusinessModel>()
.ForMember(dest => dest.ManagerId, opt => opt.ResolveUsing<GetManagerResolver>());
Then my GetManagerResolver:
public class GetManagerResolver : ValueResolver<BusinessModel, int>
{
private IContext context;
public GetManagerResolver(IContext context)
{
this.context = context;
}
protected override int GetManagerResolver(BusinessModel source)
{
return context.UserId;
}
}
But i get this exception message {"Type needs to have a constructor with 0 args or only optional args\r\nParameter name: type"}.
Any Ideas on how make automapper use ninject for object creation?
UPDATE
My code to add automapper configuration:
public static class AutoMapperWebConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new Profile1());
cfg.AddProfile(new Profile2());
// now i want to add this line, but how to get access to kernel in static class?
// cfg.ConstructServicesUsing(t => Kernel.Get(t));
});
}
}
You can use the ConstructedBy function to configure how Automapper should create your GetManagerResolver after calling ResolveUsing:
Mapper.CreateMap<ViewModel, BusinessModel>()
.ForMember(dest => dest.ManagerId,
opt => opt.ResolveUsing<GetManagerResolver>()
.ConstructedBy(() => kernel.Get<GetManagerResolver>());
Or you can globally sepecify your Ninject kernel to be used by Automapper when resolving any type with Mapper.Configuration.ConstructServicesUsing method:
Mapper.Configuration.ConstructServicesUsing((type) => kernel.Get(type));
What I ended up doing was to create NinjectModule for Automapper where I put all my automapper configuration and tell automapper to use Ninject Kernel to construct objects. Here is my code:
public class AutoMapperModule : NinjectModule
{
public override void Load()
{
Mapper.Initialize(cfg =>
{
cfg.ConstructServicesUsing(t => Kernel.Get(t));
cfg.AddProfile(new Profile1());
cfg.AddProfile(new Profile2());
});
}
}

AutoMapper ConstructServicesUsing is ignored

I have a Person and a PersonViewModel. I created a map from Person => PersonViewModel. The problem is that PersonViewModel's only constructor needs an argument (it has a dependency that I want to be injected) and AutoMapper is complaining because it says it needs a parameterless constructor.
To fix it, I used the ConstructServicesUsing method, but I haven't been successful with it :(
To illustrate the case, I created a test for you to see what I'm doing. It's pretty simple:
[TestMethod]
public void TestConstructServicesUsing()
{
Mapper.Initialize(configuration =>
{
configuration.ConstructServicesUsing(FactoryMethod);
configuration.CreateMap<Person, PersonViewModel>();
});
Mapper.AssertConfigurationIsValid();
var person = new Person();
var personViewModel = Mapper.Map<Person, PersonViewModel>(person);
}
private object FactoryMethod(Type type)
{
throw new NotImplementedException();
}
}
The rest of the code is the classes and interface definitions. They are almost empty.
public class SomeyDependency : ISomeDependency
{
}
public class PersonViewModel
{
private readonly ISomeDependency service;
public PersonViewModel(ISomeDependency service)
{
this.service = service;
}
public string Name { get; set; }
}
public class Person
{
public string Name { get; set; }
}
public interface ISomeDependency
{
}
As you see, I provide AutoMapper with a FactoryMethod, but it never get called.
When it reaches the last line of the test (Mapper.Map<...>()) it throws an excepton saying:
AutoMapper.AutoMapperMappingException:
Mapping types:
Person -> PersonViewModel
MappingWithContainerTests.Person -> MappingWithContainerTests.PersonViewModel
Destination path:
PersonViewModel
Source value:
MappingWithContainerTests.Person ---> System.ArgumentException: Type needs to have a constructor with 0 args or only optional args
Parameter name: type
What's the problem?
Why isn't the FactoryMethod being called?
As #khorvat mention where is missing .ConstructUsingServiceLocator(), for concrete mapping.
Also you can set constructor directly by
.ConstructUsing(source => Method(source.anySourceOptions))
Or as exception said:
PersonViewModel, must have a constructor with 0 args or only optional
args. You have only one constructor with 1 not optional argument
you may create one more constructor without args:
public PersonViewModel()
{
this.service = new SomeDependency();
}
I'm using .NET Core 3.1 and Automapper.Extensions.Microsoft.DependencyInjection.
This does not work for me (Same error as yours):
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Context, MainViewModel>()
.ReverseMap()
.ConstructUsingServiceLocator();
}
}
But this does work:
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Context, MainViewModel>()
.ConstructUsingServiceLocator()
.ReverseMap();
}
}
I still do not fully understand the cause.

AutoMapper cannot convert enum to nullable int?

I got AutoMapperMappingException exception
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown. ---> System.InvalidCastException: Invalid cast from 'DummyTypes' to 'System.Nullable`1[[System.Int32, ...
when
public enum DummyTypes : int
{
Foo = 1,
Bar = 2
}
public class DummySource
{
public DummyTypes Dummy { get; set; }
}
public class DummyDestination
{
public int? Dummy { get; set; }
}
[TestMethod]
public void MapDummy()
{
Mapper.CreateMap<DummySource, DummyDestination>();
Mapper.AssertConfigurationIsValid();
DummySource src = new DummySource()
{
Dummy = DummyTypes.Bar
};
Mapper.Map<DummySource, DummyDestination>(src);
}
Should not AutoMapper map this implicitly without any extra explicit rule?
P.S. I cannot change the definition of DummyDestination.Dummy to enum. I have to deal with such interfaces.
It looks like no, it won't take care of this automatically for you. Interestingly, it will map an enum to a regular int.
Looking at AutoMapper's source, I think the problematic line is:
Convert.ChangeType(context.SourceValue, context.DestinationType, null);
Assuming context.SourceValue = DummyTypes.Foo and context.DestinationType is int?, you would end up with:
Convert.ChangeType(DummyTypes.Foo, typeof(int?), null)
which throws a similar exception:
Invalid cast from 'UserQuery+DummyTypes' to
'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0
So I think really the question is why can't we cast a variable of type enum to int? That question has already been asked here.
This seems like a bug in AutoMapper. Anyway the workaround is to map the property manually:
Mapper.CreateMap<DummySource, DummyDestination>()
.ForMember(dest => dest.Dummy, opt => opt.MapFrom(src => (int?)src.Dummy));
Just in case if anyone want to try using a type converter
Mapper.CreateMap<int?, DummyTypes.Foo?>().ConvertUsing(new FooTypeConverter());
public class FooTypeConverter: TypeConverter<int?, DummyTypes.Foo?>
{
protected override DummyTypes.Foo? ConvertCore(int? source)
{
return source.HasValue ? (DummyTypes.Foo?)source.Value : null;
}
}
Cheers

Resources