Registering AutoMapper ValueResolvers?

1.5k Views Asked by At

I'm on AutoMapper 4.2 and I cant figure out why I'm getting this error

Autofac.Core.Registration.ComponentNotRegisteredException The requested service 'Navigator.ItemManagement.Data.MappingProfiles.ReportPreferenceReportUserIdsResolver' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

I'm getting this error for my one of my value resolvers

public class ReportPreferenceProfile : Profile
    {
        protected override void Configure()
        {
            CreateMap<ReportPreference, ReportPreferenceSummaryDto>()
                .ForMember(d => d.Id, o => o.MapFrom(s => s.Id))
                .ForMember(d => d.Name, o => o.MapFrom(s => s.Name))
                .ForMember(d => d.ReportUserIds, o => o.ResolveUsing<ReportPreferenceReportUserIdsResolver>());
        }
    }

public class ReportPreferenceReportUserIdsResolver : ValueResolver<IList<ReportUser>, IList<Guid>>
    {
        protected override IList<Guid> ResolveCore(IList<ReportUser> source)
        {
            return source.Select(r => r.UserId).ToList();
        }
    }

I've registered this in my Autofac module

protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<ReportPreferenceReportUserIdsResolver>().As<IValueResolver>();

            //register all profile classes in the calling assembly
            var profiles =
                from t in typeof(Navigator.ItemManagement.Data.MappingProfiles.PlaceMapperProfile).Assembly.GetTypes()
                where typeof(Profile).IsAssignableFrom(t)
                select (Profile)Activator.CreateInstance(t);

            builder.Register(context => new MapperConfiguration(cfg =>
            {
                foreach (var profile in profiles)
                {
                    cfg.AddProfile(profile);
                }


            })).AsSelf().SingleInstance();

            builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
                .As<IMapper>()
                .SingleInstance();
        }

UPDATE 1

I tried the suggestion from Lucian Bargaoanu and replaced

builder.RegisterType<ReportPreferenceReportUserIdsResolver>().As<IValueResolver>();

with

builder.RegisterType<ReportPreferenceReportUserIdsResolver>().AsSelf();

Now the error I get is

System.ObjectDisposedException

This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored. Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from.

Mapping types: ReportPreference -> IList1 Navigator.ItemManagement.Core.ItemAggregate.ReportPreference -> System.Collections.Generic.IList1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

Destination path: ReportJobSummaryDto.Reports.Reports.Reports0[0].ReportUserIds0[0]

Source value: Navigator.ItemManagement.Core.ItemAggregate.ReportPreference ---> AutoMapper.AutoMapperMappingException:

Mapping types: ReportPreference -> IList1 Navigator.ItemManagement.Core.ItemAggregate.ReportPreference -> System.Collections.Generic.IList1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

Destination path: ReportJobSummaryDto.Reports.Reports.Reports0[0].ReportUserIds0[0]

2

There are 2 best solutions below

1
On

I arrived at this Q&A after encountering the same problem.

As @PeterRiesz mentioned in comments, since your IValueResolver does not require any dependencies, the simplest solution here would be to change the form in which you wire up the value resolver to just take a manual new instance:

o.ResolveUsing(new ReportPreferenceReportUserIdsResolver())

This wouldn't require any registration with the Autofac container.

However, you may wish to inject services in to it, or just want to register it with Autofac for other reasons and maintainability.

First, ensure that you've registered your IValueResolver type with Autofac as @LucianBargaoanu answered:

builder.RegisterType<ReportPreferenceReportUserIdsResolver>().AsSelf();

I was defining my AutoMapper registrations the same way as you, and as a result was also getting the same error you show above.

After much research and trial and error to resolve the error myself, I found this StackOverflow Q&A which led me in the right direction.

You are already setting up the service function for AutoMapper to use to resolve dependencies here:

builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
    .As<IMapper>()
    .SingleInstance();

However, as the error states, the context c has been disposed by the time this actually gets executed. The way to fix this is to rewrite the lambda registration as follows:

builder.Register(c =>
{
    //This resolves a new context that can be used later.
    var context = c.Resolve<IComponentContext>();
    var config = context.Resolve<MapperConfiguration>();
    return config.CreateMapper(context.Resolve);
})
.As<IMapper>()
.SingleInstance();

An additional means of registering the service resolver exists as well. You can do it in the MapperConfiguration registration instead as follows:

builder.Register(c =>
{
    //Again, we must store a new instance of a component context for later use.
    var context = c.Resolve<IComponentContext>();
    var profiles = c.Resolve<IEnumerable<Profile>>();

    return new MapperConfiguration(x =>
    {
        foreach (var profile in profiles)
        {
            x.AddProfile(profile);
        }

        //Registering the service resolver method here.
        x.ConstructServicesUsing(context.Resolve);
    });
})
.SingleInstance()
.AsSelf();

Either of these appears to be equivalent. I think the latter is cleaner personally.

The take away here is that a new context needs to be resolved in the top-level lambda since the instance being passed in will be disposed by the time the lambda is actually executed.

9
On

Try

builder.RegisterType<ReportPreferenceReportUserIdsResolver>().AsSelf();