简体   繁体   中英

No parameterless constructor defined for this object in automapper

Question

I have the following value resolver:

public class StudentResolver : IValueResolver<Lesson, LessonResponse, UserResponse>
{
    private readonly ApplicationDbContext _dbContext;

    public StudentResolver(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    
    public UserResponse Resolve(
        Lesson lesson, 
        LessonResponse response, 
        UserResponse member, 
        ResolutionContext context)
    {
        var user = _dbContext.Users
            .Where(x => x.Id == lesson.StudentId)
            .FirstOrDefault();
        if (user == null)
            return null;
        var result = Mapper.Map<UserResponse>(user); // this line triggers a "no parameterless constructor", why?
        return result;
    }
}

This resolver tries to fetch the Student attribute for this model:

public class LessonResponse
{
    public string Id { get; set; }

    public UserResponse Student { get; set; }
}

But on the line:

var result = Mapper.Map<UserResponse>(user);

I get:

{System.MissingMethodException: No parameterless constructor defined for this object. at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor) at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) at System.Activator.CreateInstance(Type type, Boolean nonPublic) at System.Activator.CreateInstance(Type type) at AutoMapper.MappingOperationOptions`2.CreateInstanceT}

The only type in that expression that should be created is UserResponse which is defined as follows:

public class UserResponse
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }
    public string Picture { get; set; }
    public string X { get; set; }

    public bool IsAdmin { get; set; }
    public bool IsClient { get; set; }
    public bool IsInstructor { get; set; }
}

But it is constructible, so what am I doing wrong?


Extra

The mapping profile is the following:

public class MappingProfile : Profile 
{
    public MappingProfile() {
        CreateMap<Lesson, LessonResponse>()                
            .ForMember(
                dest => dest.Student,
                opts => opts.ResolveUsing<StudentResolver>());

    }
}

And the mapping for the user is:

public class MappingProfile : Profile 
{
    public MappingProfile() {
        CreateMap<ApplicationUser, UserResponse>()
            .ForMember(
                dest => dest.FullName, 
                opts => opts.MapFrom(
                    origin => origin.FirstName + " " + origin.LastName))
            .ForMember(
                dest => dest.IsAdmin,
                opts => opts.ResolveUsing<IsAdminResolver>())
            .ForMember(
                dest => dest.IsInstructor,
                opts => opts.ResolveUsing<IsInstructorResolver>())
            .ForMember(
                dest => dest.IsClient,
                opts => opts.ResolveUsing<IsClientResolver>());
    }
}

The boolean resolvers are all similar to:

public class IsAdminResolver : IValueResolver<ApplicationUser, UserResponse, bool>
{
    private readonly UserManager<ApplicationUser> _userManager;

    public IsAdminResolver(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
    public bool Resolve(ApplicationUser user, UserResponse response, bool member, ResolutionContext context)
    {
        return _userManager.IsInRoleAsync(user, "Admin").Result;
    }
}

I had the same problem because of my inattention... I generate by Visual Studio quick tip default constructor for class inherited from Profile which was protected instead of public . Probably that will help somebody because I spent a few hours to figure out that

sorry for long response time since yesterday was exhausted after work.

I was able to replicate your issue. You have 2 options:

1st Option Resolve your StudentResolver with your DbContext :

CreateMap<Lesson, LessonResponse>()
            .ForMember(
                dest => dest.Student,
                opts => opts.ResolveUsing(new StudentResolver(ApplicationDbContext.Create())));

2nd Option, configure your mapper configuration ConstructServicesUsing . In my case I was using Simple Injector to test things out.

Mapper.Initialize(cfg =>
        {
            cfg.ConstructServicesUsing(type => container.GetInstance(typeof(StudentResolver)));
            cfg.AddProfile<MappingProfile>();
        });

For my testing, I was getting an error thrown var lessonResponse = Mapper.Map<LessonResponse>(lesson);

So if your is thrown for Mapper.Map<UserResponse>(user); you may have to ConstructService for IsAdminResolver , IsInstructorResolver and IsClientResolver

In case anyone else does the same thing I did, here's what my problem was:

public sealed class MyProfile : Profile
{
    MyProfile()
    {
        // Configuration
    }
}

As you can see, I forgot to mark the constructor as public . Once I marked it public everything worked fine.

We had the same problem and it turned out that this line of code should have been the last line of code in our startup

services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

Furthermore, when loading autofac modules, we moved our local registrations to the last lines so they overwrite the ones comming from nuget packages.

Pleased note that removing a constructor is not the solution so try to fix your dependency injection issues as mentioned above.

In my case I had the following mapper:

public class Mapper : Profile
{
   public Mapper(IConfiguration configuration)
   {

   }
}

The fix was removing configuration parameter from the constructor (and thus making it parameterless constructor).

Startup - ConfigureServices

services.AddAutoMapper(x=> x.AddProfile(new
MappingProfile(dependencyAlredyDefinedBefore) ));

MappingProfile class

DependencyClass readonly dependency;

//Constructor as usual
public MappingProfile(DependencyClass dependency)
{
    this.dependency = dependency;
    //Configure maps using dependency...
}

I ran into this as well in AutoMapper 11.0.1 and AutoMapper.Extensions.Microsoft.DependencyInjection 11.0.0.

It seems that if assembly scanning is enabled for the assembly that contains the Profile derived class that does not have a No-Args constructor, that AutoMapper tries to instantiate it even with an explicit registration. What worked for me is to disable the type scanning for that repository by providing an empty array to match the correct overload. Either an empty array of Type or Assembly will work.

Assuming your dependency ApplicationDbContext is available via .NET core dependency injection then you can use the overload on the IServiceCollection extension methods in AutoMapper.Extensions.Microsoft.DependencyInjection that accepts an Action<IServiceProvider,AutoMapper.IMapperConfigurationExpression> and params Type[] or params Assembly[] arguments to instantiate your profile class when the DI container is built.

The downside of this is because scanning needs to be disabled, all profile classes in that assembly need to now be explicitly registered, including no-arg ones. This seems like a bug to me as I would expect the explicit instantiation to be favored for a type over the generic scanning, but there might be a timing component to this in the implementation that would make that difficult to achieve.

        return services.AddAutoMapper((serviceProvider, mapperConfiguration) =>
            {
                mapperConfiguration.AddProfile(new MappingProfile(serviceProvider.GetRequiredService<ApplicationDbContext>()));
            },
            Array.Empty<Type>()
        );

In my project I have multiple class libraries and I had AutoMapperProfile file in one of them. I moved it to my startup project and it worked. another solution is to enable automapper in startup like this:

            builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

then your profile can be anywhere inside your project.

My suggestion is to use Mapster instead AutoMapper, Because of Mapster many benefits:)

But the answer to your question:

add a constructor with out parameter to class witch inherit from profile if you want to have constructor with parameter, no problem, but earlier, add constructor with out parameter in class

like this:

public class MappingProfile : Profile
{
    public MappingProfile()
    {

    }
    public MappingProfile(AnyParameter)
    {
        //do config with input parameter
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM