I am having a Entity and Dto and trying to custom map those classes to have EmpUrl=baseurl+EmpPath.I am planning to get baseurl from configuration file and mapping via DI but getting below error:
System.MissingMethodException: Cannot dynamically create an instance of type 'MappingProfile'. Reason: No parameterless constructor defined.
public MyMappingProfile(IOptions <ApplicationSettings> applicationsettings)
{
CreateMap<Emp, EmpDto>()
.ForMember(e => e.EmpUrl, e => e.MapFrom(src => applicationsettings.Value.BaseDomain + src.EmpPath));
}
The class to register dependency is below:
public class MyModule : Autofac.Module
{
private readonly IConfiguration Configuration;
public ApplicationModule(IConfiguration configuration)
{
Configuration = configuration;
}
protected override void Load(ContainerBuilder builder)
{
// Register Automapper profiles
var config = new MapperConfiguration(cfg => { cfg.AddMaps(typeof(MappingProfile).Assembly); });
config.AssertConfigurationIsValid();
builder.Register(c => config)
.AsSelf()
.SingleInstance();
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
var settings = new ApplicationSettings();
Configuration.Bind(settings);
builder.RegisterInstance(Options.Create(settings));
}
}
Startup.cs:
public void ConfigureContainer(ContainerBuilder container)
{
container.RegisterModule(new ApplicationModule(Configuration));
}
What am I doing wrong here?
Assuming that MyMappingProfile
inherits from the AutoMapper.Profile
class, there are a couple issues here:
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.cfg.AddMaps
method call uses .NET System.Activator.CreateInstance
directly to instantiate the mapping profile (eg 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.
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.
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.