I'm trying to convert an object's member from a short? to a bool using AutoMapper CreateMap.. using a resolver interface.
I created an IMemberValueResolver interface, class to the interface with a resolve function, and then tried using CreateMap with opt.MapFrom and opt.ResolveUsing - nothing works..
Interface and class compiles without error but only the CreateMap is in error.
Tried following documentation:
http://docs.automapper.org/en/stable/Custom-value-resolvers.html
http://docs.automapper.org/en/stable/5.0-Upgrade-Guide.html
Looked for 2 days now and tried EVERYTHING I could find, nothing works.. :(
This is what I have now..
Interface (IMemberValueResolver.cs):
public interface IMemberValueResolver<in TSource, in TDestination, TSourceMember, TMember>
{
TMember Resolve(TSource source, TDestination destination, TSourceMember sourceMember, TMember destinationMember, ResolutionContext context);
}
Mapper configuration (AutoMapperConfig.cs):
public class IsBootstrapResolver : IMemberValueResolver<PageTemplateDataContract, PageTemplateViewModel, short?, bool>
{
public bool Resolve(PageTemplateDataContract source, PageTemplateViewModel destination, short? sourceMember, bool destMember, ResolutionContext context)
{
return sourceMember == 1 ? true : false;
}
}
//NONE OF THESE WORK!!!!!
public class DataContractToViewModelProfile : Profile
{
public DataContractToViewModelProfile()
{
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing<IsBootstrapResolver>().FromMember(x => x.IsBootstrapEnable));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing(IsBootstrapResolver));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.MapFrom<IsBootstrapResolver, short?>(!?!?!?!));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.MapFrom(new IsBootstrapResolver()));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing<IsBootstrapResolver>().FromMember(x => x.IsBootstrapEnable));
CreateMap<PageTemplateDataContract, PageTemplateViewModel>().ForMember(dest => dest.IsBootstrapEnable, opt => opt.ResolveUsing<IsBootstrapResolver, bool>(src => src.IsBootstrapEnable));
}
}
I'm expecting the object mapped with a short? gets converted to a bool using a custom resolver.
Thank you!
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 have the following setup and its working fine
Startup.cs
public static IServiceCollection AddServices(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
}
MappingProfile class
public class MyDtoMappingProfile: Profile
{
public MyDtoMappingProfile()
{
CreateMap<MyDtoMappingSource<MyContentType>, MyDestinationObject>()
.ForMember(d => d.BatchFeedDate, opt => opt.MapFrom(s => DateTime.UtcNow));
}
}
But I would like to do the following by inject the following service in the mapping profile class
Startup.cs
public static IServiceCollection AddServices(this IServiceCollection serviceCollection, IConfiguration configuration)
{
serviceCollection.AddAutoMapper(builder => {
builder.AddProfile(new MyDtoMappingProfile(serviceCollection.BuildServiceProvider().GetService<ILookupCodeMapper>()));
});
}
MappingProfile
public class MyDtoMappingProfile: Profile
{
private readonly ILookupCodeMapper lookupCodeMapper;
public MyDtoMappingProfile(ILookupCodeMapper lookupCodeMapper)
{
this.lookupCodeMapper = lookupCodeMapper;
CreateMap<MyDtoMappingSource<MyContentType>, MyDestinationObject>()
.ForMember(d => d.BatchFeedDate, opt => opt.MapFrom(s => DateTime.UtcNow))
.ForMember(d => d.Strand, opt => opt.MapFrom(s => lookupCodeMapper.Code(s.StringValue)));
}
}
But when i do the above im getting this error,
Missing type map configuration or unsupported mapping.
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'm working on .NetCore MVC project where I've had to reverse engineer an existing database and am having a real nightmare getting ASPIdentity to play nicely.
I've had to manually add my Identity DbSets in the Context class that was created but that did not include the Identity tables as I had hoped would happen. I've managed to create Migrations for the ASPIdentity properties that I've wanted (Roles, Claims) and tested the Registration / Login / Manage aspect of the project. I get the error below when I click on the "Two-Factor Authentication" tab in the Manage area:
ArgumentException: Entity type 'IdentityUserToken<string>' is defined with a single key property, but 3 values were passed to the 'DbSet.Find' method.
Microsoft.EntityFrameworkCore.Internal.EntityFinder<TEntity>.FindTracked(object[] keyValues, out IReadOnlyList<IProperty> keyProperties)
Stack Query Cookies Headers
ArgumentException: Entity type 'IdentityUserToken<string>' is defined with a single key property, but 3 values were passed to the 'DbSet.Find' method.
Microsoft.EntityFrameworkCore.Internal.EntityFinder<TEntity>.FindTracked(object[] keyValues, out IReadOnlyList<IProperty> keyProperties)
Microsoft.EntityFrameworkCore.Internal.EntityFinder<TEntity>.FindAsync(object[] keyValues, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.FindAsync(object[] keyValues, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>.FindTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.UserStoreBase<TUser, TKey, TUserClaim, TUserLogin, TUserToken>.GetTokenAsync(TUser user, string loginProvider, string name, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.UI.Pages.Account.Manage.Internal.TwoFactorAuthenticationModel<TUser>.OnGetAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory+GenericTaskHandlerMethod.Convert<T>(object taskAsObject)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory+GenericTaskHandlerMethod.Execute(object receiver, object[] arguments)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeHandlerMethodAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeNextPageFilterAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Services code in Startup.cs
services.AddDefaultIdentity<User>()
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<MyContext>();
My onModelBuilding code (ASPIdentity specific code):
public partial class MyContext : DbContext
{
public MyContext()
{
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public virtual DbSet<User> User { get; set; }
public virtual DbSet<IdentityUserClaim<string>> IdentityUserClaim { get; set; }
public virtual DbSet<IdentityUserToken<string>> IdentityUserToken { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdentityUserClaim<string>>().HasKey(p => new { p.Id });
modelBuilder.Entity<IdentityUserToken<string>>().HasKey(p => new { p.UserId });
}
}
I can see that I need to include more keys but cannot find any information on what the relationships are. I created a blank .NetCore MVC app with ASPIdentity and used the same ASPIdentity configuration in startup.cs and I'm none-the-wiser, what are the keys that I should be using or how should I build the DbSet?
This is an old question, but in case someone else needs this.
I don't think your issue is with the db schema. Something in the click event from your MVC app is ultimately using dbset.Find incorrectly.In your case;
var tokenEntity = MyContext.UserTokens.Find(user.Id)
Instead you could just use Linq;
var tokenEntity = MyContext.UserTokens.FirstOrDefault(ut => ut.UserId == user.Id);
Just also add;
using System.Linq
This post helped immensely with understanding the relationships between the different Identity properties: https://learn.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db
I was able to overcome the issues explained above using the relational information and templates provided in the Microsoft article.
DbContext OnModelCreating method:
modelBuilder.Entity<ApplicationUser>(b =>
{
// Each User can have many UserClaims
b.HasMany(e => e.Claims)
.WithOne(e => e.User)
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
b.HasMany(e => e.Logins)
.WithOne(e => e.User)
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
b.HasMany(e => e.Tokens)
.WithOne(e => e.User)
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
modelBuilder.Entity<ApplicationRole>(b =>
{
// Each Role can have many entries in the UserRole join table
b.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
// Each Role can have many associated RoleClaims
b.HasMany(e => e.RoleClaims)
.WithOne(e => e.Role)
.HasForeignKey(rc => rc.RoleId)
.IsRequired();
});
modelBuilder.Entity<ApplicationUserLogin>(b =>
{
b.HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId });
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity<ApplicationUserRole>(b =>
{
b.HasKey(r => new { r.UserId, r.RoleId });
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity<ApplicationUserToken>(b =>
{
b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });
b.ToTable("AspNetUserTokens");
});
(I'd created my own ApplicationUser class with custom properties)
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));
}
}