I have a .NET6 project with minimal APIs. This is the code
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ClientContext>(opt =>
opt.UseInMemoryDatabase("Clients"));
builder.Services
.AddTransient<IClientRepository,
ClientRepository>();
builder.Services
.AddAutoMapper(Assembly.GetEntryAssembly());
var app = builder.Build();
// Get the Automapper, we can share this too
var mapper = app.Services.GetService<IMapper>();
if (mapper == null)
{
throw new InvalidOperationException(
"Mapper not found");
}
app.MapPost("/clients",
async (ClientModel model,
IClientRepository repo) =>
{
try
{
var newClient = mapper.Map<Client>(model);
repo.Add(newClient);
if (await repo.SaveAll())
{
return Results.Created(
$"/clients/{newClient.Id}",
mapper.Map<ClientModel>(newClient));
}
}
catch (Exception ex)
{
logger.LogError(
"Failed while creating client: {ex}",
ex);
}
return Results.BadRequest(
"Failed to create client");
});
This code is working. I have a simple Profile for AutoMapper
public class ClientMappingProfile : Profile
{
public ClientMappingProfile()
{
CreateMap<Client, ClientModel>()
.ForMember(c => c.Address1, o => o.MapFrom(m => m.Address.Address1))
.ForMember(c => c.Address2, o => o.MapFrom(m => m.Address.Address2))
.ForMember(c => c.Address3, o => o.MapFrom(m => m.Address.Address3))
.ForMember(c => c.CityTown, o => o.MapFrom(m => m.Address.CityTown))
.ForMember(c => c.PostalCode, o => o.MapFrom(m => m.Address.PostalCode))
.ForMember(c => c.Country, o => o.MapFrom(m => m.Address.Country))
.ReverseMap();
}
}
I wrote a NUnit test and a xUnit test. In both cases, when I call the API I receive the error
Program: Error: Failed while creating client: AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
ClientModel -> Client
MinimalApis.Models.ClientModel -> MinimalApis.Data.Entities.Client
at lambda_method92(Closure , Object , Client , ResolutionContext )
How can I use the Profile in the main project? The full source code is on GitHub.
When you run your project normally, Assembly.GetEntryAssembly() will resolve to your project's assembly (the one that contains the Profile classes). When you launch your project via a unit test project, the entry point is actually that unit test.
That means that this code isn't actually finding the profiles because they're not in that assembly:
builder.Services
.AddAutoMapper(Assembly.GetEntryAssembly());
Normally what I do in this situation is to use typeof(someAssemblyInMyProject).Assembly. In this example I use Program but any class should work so long as its in the same project as the Profile classes:
builder.Services
.AddAutoMapper(typeof(Program).Assembly);
Now, no matter what the entry assembly is, you'll still find the right list of profiles.
You can instantiate AutoMapper with a specific profile class using the code below:
var configuration = new MapperConfiguration(cfg => cfg.AddProfile(ClientMappingProfile));
var mapper = new Mapper(configuration);
To do this with dependency injection:
services.AddAutoMapper(config =>
{
config.AddProfile(ClientMappingProfile);
});
Related
Is it possible using Maptser to configure the Map configuration by condition?
puplic static GetCustomConfig(condition)
{
var config = new TypeAdapterConfig();
config.NewConfig<BookEntity, BookModel>()
.Map(dest => dest.Name, src => src.Title)
.Map(dest => dest.Address, src => $"{src.Street} {src.House}")
// other Map
if (condition)
{
// add several Map to the config
}
return config;
}
It is planned that these named configurations will be contained in some static class, from where they will be returned differently depending on the conditions.
For some types, I have a Mapster configured via DI, but I needed custom configurations for some cases.
public static class MapsterConfig
{
public static void MapsterSetup()
{
TypeAdapterConfig<Type1, Type2>
.NewConfig()
.Map(dest => dest.Name, src => src.Title);
}
}
I get an error which says I didn't register AutoMapper, but I did, and such configuration as listed below was used in another projects successfully:
System.InvalidOperationException: 'Unable to resolve service for type 'AutoMapper.Configuration.IConfiguration' while attempting to activate 'PromoAction.Services.Identity.API.Controllers.AccountController'
Please help me to figure out how to make it work in asp net core 5.
AutoMapperConfiguration.cs
public class AutoMapperConfiguration
{
public MapperConfiguration Configure() => new(cfg =>
{
cfg.CreateMap<User, ClientDTO>();
cfg.CreateMap<UserForRegistrationDto, User>()
.ForMember(u => u.UserName, opt => opt.MapFrom(x => x.Email))
.ForMember(u => u.FirstName, opt => opt.MapFrom(x => x.Name))
.ForMember(u => u.LastName, opt => opt.MapFrom(x => x.Surname));
});
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var config = new AutoMapperConfiguration().Configure().CreateMapper();
services.AddSingleton(sp => config);
}
AccountController.cs
public AccountController(IMapper mapper)
{
this._mapper = mapper;
}
Recomended way of registering Autommaper is described in their docs: https://docs.automapper.org/en/stable/Dependency-injection.html#asp-net-core
Create mapping profile and register it using
services.AddAutoMapper(profileAssembly1, profileAssembly2 /*, ...*/);
In your case looks like you registered mapper instance, your example says that you injected mapper instance but exception says that you want to resolve IConfiguration. Check your code, if you don't try to inject IConfiguration (which is not registered).
The problem is that you are not injecting automapper in a good way.
follow the following steps:
Install the nuget package AutoMapper.Extensions.Microsoft.DependencyInjection
Create a new automapper profile inherited from Profile class ( remember to add using AutoMapper), example:
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<User, ClientDTO>();
CreateMap<UserForRegistrationDto, User>()
.ForMember(u => u.UserName, opt => opt.MapFrom(x => x.Email))
.ForMember(u => u.FirstName, opt => opt.MapFrom(x => x.Name))
.ForMember(u => u.LastName, opt => opt.MapFrom(x => x.Surname));
}
}
In your startup class in the method configure services use AddAutoMapper and provide the typeof your startup
services.AddAutoMapper(typeof(Startup));
After that you can inject it normally
public AccountController(IMapper mapper)
{
this._mapper = mapper;
}
I am using Automapper 6.2.2 and I'm trying to set it up in a Web App. I am trying to use the static Automapper.Initialize() method placed directly in my Global.asax file.
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<LoadArea, LoadAreaWithoutPlannedSlotDto>();
cfg.CreateMap<LoadArea, LoadAreaDto>();
cfg.CreateMap<LoadAreaForCreationDto, LoadArea>().Ignore(d => d.Slots);
cfg.CreateMap<LoadArea, LoadAreaForUpdateDto>();
cfg.CreateMap<LoadAreaForUpdateDto, LoadArea>().ForMember(m => m.Code, i => i.UseDestinationValue());
cfg.CreateMap<PlannedSlot, PlannedSlotDto>();
cfg.CreateMap<PlannedSlotForCreationDto, PlannedSlot>().Ignore(d => d.Area);
cfg.CreateMap<PlannedSlotForUpdateDto, PlannedSlot>();
cfg.CreateMap<UserToReturnDto, User>();
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>();
});
AreaRegistration.RegisterAllAreas();
UnityConfig.RegisterComponents();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
The strange issue is that while this code runs at startup, the mappings are created but none of them are actually configured.
So if I try to Ignore a property in the Mapper.Initialize(...) method, it doesn't work and I get an error when the unmapped property is run into when mapping occurs.
I tried using:
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>().ForMember(d => d.LoadArea, opt => opt.Ignore());
Also tried:
cfg.CreateMap<LoadAreaSlotDetailForReturnDto, LoadAreaSlotDetail>(MemberList.None);
And a few other combinations, including an extension method that would ignore all unmapped members:
public static IMappingExpression<TSource, TDestination> Ignore<TSource, TDestination>(this IMappingExpression<TSource, TDestination> map,
Expression<Func<TDestination, object>> selector)
{
map.ForMember(selector, config => config.Ignore());
return map;
}
But what does work is if I try to Ignore the property Inline in my controller as follows:
[HttpPost]
[Route("{loadAreaId}/details")]
public IHttpActionResult AddLoadAreaSlotDetails([FromUri] string loadAreaId, [FromBody] LoadAreaSlotDetailForAddDto loadAreaSlotDetails)
{
var loadAreaSlotDetailEntity = Mapper.Map<LoadAreaSlotDetailForAddDto, LoadAreaSlotDetail>(loadAreaSlotDetails, opt => opt.ConfigureMap().ForMember(d => d.LoadArea, o => o.Ignore()));
_repo.AddLoadAreaSlotDetail(loadAreaSlotDetailEntity);
return Ok();
}
This proves to me that the Ignore works but at the same time I assume that I'm Initializing and configuring my mappings wrongly but I don't know why because many other examples are Initializing in the same way using the static API. I'm doing the same in a .NET Core project (in the ConfigureServices method) and mappings work, it also ignores unmapped properties by default.
Why does this happen ?
Have you tried using AutoMapper Profiles?
AutoMapper Configuration
I was then able to configure this in the Startup.cs of my WebApi application. I was using SimpleInjector as my Container:
var profiles =
Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => typeof(Profile).IsAssignableFrom(t))
.ToList();
Mapper.Initialize(
mp =>
{
var mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfiles(profiles));
var mapper = mapperConfiguration.CreateMapper();
container.Register(() => mapper, Lifestyle.Scoped);
});
You then need to define one or more profiles depending on how you want to split out your auto mapper config.
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<UserDetails, UserTransferObject>();
CreateMap<UserAndAccountDetails, UserAndAccountTransferObject>();
CreateMap<User, UserAndAccountTransferObject>()
.ForMember(
dest => dest.DifferentPropertyName,
orig => orig.MapFrom(src => src.OriginalPropertyName));
}
}
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]
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.
Try
builder.RegisterType<ReportPreferenceReportUserIdsResolver>().AsSelf();
I have tried various permutations of this but my current configuration (as it relates to AutoMapper) is like this:
builder.RegisterAssemblyTypes().AssignableTo(typeof(Profile)).As<Profile>();
builder.Register(c => new MapperConfiguration(cfg =>
{
foreach (var profile in c.Resolve<IEnumerable<Profile>>())
{
cfg.AddProfile(profile);
}
})).AsSelf().SingleInstance();
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>().As<IMappingEngine>();
I have a constructor using IMapper mapper, however I continue to get the YSOD:
None of the constructors found with'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'
on type '' can be invoked with the available services and parameters:
Cannot resolve parameter 'AutoMapper.IMapper mapper' of constructor
'Void .ctor(...,...,..., AutoMapper.IMapper)'.
This class works perfectly without the automapper reference so I'm certain that the trouble lies with my automapper configuration.
I'm not sure what I'm missing here as I'm very new to both AutoFac and AutoMapper.
Edit:
I've also tried:
builder.Register(c => new MapperConfiguration(cfg =>
{
cfg.CreateMap<IdentityUser, AspNetUser>().ReverseMap();
})).AsSelf().SingleInstance();
builder.Register(ctx => ctx.Resolve<MapperConfiguration>().CreateMapper()).As<IMapper>();
//I've tried both of these lines separately, neither work
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve)).As<IMapper>().InstancePerLifetimeScope();
I've also tried manually adding the profiles per the suggestion in the comments
As I mentioned in a comment, your AutoFac code appears to be correct (except for the assembly scanning portion).
I created the following test app, and it does in fact run without any exceptions and puts a 3 into the Output window (as intended):
using System.Diagnostics;
using Autofac;
using AutoMapper;
namespace Sandbox
{
public partial class App
{
public App()
{
var builder = new ContainerBuilder();
builder.Register(
c => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TestProfile());
}))
.AsSelf()
.SingleInstance();
builder.Register(
c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>()
.As<IMappingEngine>();
builder.RegisterType<Test>().AsSelf();
var container = builder.Build();
container.Resolve<Test>();
}
}
public class TestProfile : Profile
{
protected override void Configure()
{
CreateMap<Source, Destination>();
}
}
public class Test
{
public Test(IMapper mapper)
{
var source = new Source { Id = 3 };
var destination = mapper.Map<Destination>(source);
Debug.Print(destination.Id.ToString());
}
}
public class Source
{
public int Id { get; set; }
}
public class Destination
{
public int Id { get; set; }
}
}
I would suggest creating a new branch of your app in version control and stripping things out until it works.
This is worked for me...
builder = new ContainerBuilder();
builder.Register(
c => new MapperConfiguration(cfg =>
{
cfg.AddProfile(new TestProfile());
}))
.AsSelf()
.SingleInstance();
builder.Register(
c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.InstancePerLifetimeScope();
builder.RegisterType<MappingEngine>()
.As<IMappingEngine>();
builder.RegisterType<Test>().AsSelf();
var container = builder.Build();
container.Resolve<Test>();