Assuming that MyMappingProfile inherits from the AutoMapper.Profile class, there are a couple issues here:
- The
cfg.AddMaps method is called immediately during construction of the MapperConfiguration instance (per AutoMapper v11.0.1 source). This is during application startup loading of the ApplicationModule (not during lazy resolution of dependencies from the container at runtime). The AddMaps call, which creates a new instance of MyMappingProfile, is executed even before the ApplicationSettings are bound from configuration and added to the DI container.
- Likely related to #1, the
cfg.AddMaps method call uses .NET System.Activator.CreateInstance directly to instantiate the mapping profile (e.g. MyMappingProfile) and does not pass any arguments, which is why you're getting the "No parameterless constructor defined" error. This flow does not use the serviceCtor argument passed to the MapperConfiguration.CreateMapper method. (AutoMapper MapperConfigurationExpression.AddMaps calls AddMapsCore, which calls AddProfile(Type) per v11.0.1 source)
One approach to resolve IOptions<ApplicationSettings> at runtime from the DI container using AutoMapper is by using the service factory method (serviceCtor) passed to MapperConfiguration.CreateMapper.
The mapping profile could be something like:
public class MyMappingProfile : Profile
{
public MyMappingProfile()
{
CreateMap<Emp, EmpDto>()
.ForMember(dest => dest.EmpUrl, e => e.MapFrom((src, dest, destMember, ctx) =>
{
if (string.IsNullOrWhiteSpace(src.EmpPath))
{
return null;
}
var applicationSettings = (IOptions<ApplicationSettings>)ctx.Options.ServiceCtor(typeof(IOptions<ApplicationSettings>));
return applicationSettings.Value.BaseDomain + src.EmpPath;
}));
}
}
To keep the mapping clean or reuse the logic, you could extract the inline MapFrom logic into a local method in the mapping profile class, a separate custom Value Resolver (or Member Value Resolver) used with opt.MapFrom, or a separate custom Value Converter used with opt.ConvertUsing. A custom converter or resolver can use dependencies injected via the constructor rather than using the ResolutionContext explicitly.
If you try only the mapping profile changes, then Autofac will throw an ObjectDisposedException with a message like 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. from this line:
var applicationSettings = (IOptions<ApplicationSettings>)ctx.Options.ServiceCtor(typeof(IOptions<ApplicationSettings>));
The solution is to change your ApplicationModule.Load from
builder.Register(c => c.Resolve<MapperConfiguration>()
.CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
to
builder.Register(c =>
{
var ctx = c.Resolve<IComponentContext>();
var mapperConfig = c.Resolve<MapperConfiguration>();
return mapperConfig.CreateMapper(ctx.Resolve);
})
.As<IMapper>()
.InstancePerLifetimeScope();
This is based on personal experience and is also covered by other SO questions such as This resolve operation has already ended. Autofac, Automapper & IMemberValueResolver. Dennis Doomen gives a good explanation of this Autofac design nuance in The curious case of a deadlock in Autofac.
So to summarize, use the temporary container for resolving dependencies needed at construction time, but use the global container to resolve any run-time dependencies.
Another software design alternative is to not resolve the ApplicationSettings from the DI container inside the AutoMapper profile at all. Instead, the class that calls IMapper.Map could inject the IOptions<ApplicationSettings> via constructor injection and then either transform the EmpDto.EmpUrl in place or add a separate property like EmpDto.EmpFullUrl that adds the base domain prefix to an EmpDto.EmpPath property mapped verbatim from the Emp.EmpPath via AutoMapper.