AutoMapper - Cannot map between IdentityUser subclass and its correspondant DTO - asp.net

I'm working on a project with asp.net core and Identity,
I am trying to create a mapping configuration between IdentityUser subclasse and its correspondant DTO using Automapper
I have done similar configuration with other classes and it works fine, but with IdentityUser subclass it behaves differently :
Here is my IdentityUser subclasse :
public partial class Collaborateur : IdentityUser
{
public Collaborateur() : base()
{
this.Activites = new HashSet<ActiviteCollaborateur>();
this.ActeursAvantVente = new HashSet<ActeurAvv>();
}
public string Nom { get; set; }
public string Prenom { get; set; }
public string Telephone { get; set; }
public Nullable<long> Matricule { get; set; }
public string Structure { get; set; }
public string Login { get; set; }
public RoleEnum Role { get; set; }
public virtual ICollection<ActiviteCollaborateur> Activites { get; set; }
public virtual ICollection<ActeurAvv> ActeursAvantVente { get; set; }
public virtual Agence Agence { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime LastModified { get; set; }
}
Its corresponding DTO :
public class CollaborateurDTO : BaseDTO
{
public string Nom { get; set; }
public string Prenom { get; set; }
public string Telephone { get; set; }
public Nullable<long> Matricule { get; set; }
public string Structure { get; set; }
public string Login { get; set; }
public RoleEnum Role { get; set; }
}
CollaborateurProfile config class :
public class CollaborateurProfile : Profile
{
CollaborateurProfile()
{
CreateMap<Collaborateur, CollaborateurDTO>().ReverseMap();
CreateMap<Collaborateur, Collaborateur>()
.ForMember(x => x.Id, opt => opt.Ignore())
.ForMember(x => x.CreatedAt, opt => opt.Ignore())
.ForMember(x => x.LastModified, opts => opts.MapFrom(src => DateTime.UtcNow));
}
}
and Startup.cs :
services.AddAutoMapper();
it stops at this line with
MissingMethodException was unhandled by user code
An exception of type 'System.MissingMethodException' occurred in System.Private.CoreLib.ni.dll but was not handled in user code

By mistake i answered this question at the question linked in the comments (https://stackoverflow.com/a/46567611/7131186)
Here is my answer:
In my case (and it seems that this is your case too) it was a copy/paste problem. I somehow ended up with a PRIVATE constructor for my mapping profile:
using AutoMapper;
namespace Your.Namespace
{
public class MappingProfile : Profile
{
MappingProfile()
{
CreateMap<Animal, AnimalDto>();
}
}
}
(take note of the missing "public" in front of the ctor)
which compiled perfectly fine, but when AutoMapper tries to instantiate the profile it can't (of course!) find the constructor!

Related

.NET querying Aspnetusers instead of Custom AuthUser and Yet AspNetUser does not Exist

I write a custom IdentityUser which is AuthUser.
public class AuthUser : IdentityUser
{
public int StudentsId { get; set; }
public virtual Students StudentProfile { get; set; }
public int InstructorId { get; set; }
public virtual Instructor InstructorProfile { get; set; }
public bool IsStudent { get; set; }
public bool IsInstructor { get; set; }
}
The context is okay as you can see
public class LmsContext : IdentityDbContext<AuthUser> //DbContext
{
}
The startup.cs is all setup
services.AddDbContext<LmsContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("UCIPrimarySchool"))
);
services.AddDefaultIdentity<AuthUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<LmsContext>();
services.AddControllersWithViews();
services.AddRazorPages();
But when I try to login I get the following error.
An unhandled exception occurred while processing the request.
SqlException: Invalid object name 'AspNetUsers'.
Microsoft.Data.SqlClient.SqlCommand+<>c.<ExecuteDbDataReaderAsync>b__169_0(Task<SqlDataReader> result)
Why is is not querying the extended AuthUser but instead goes for the none existing table AspNetUsers?
First, you should clarify your relationship and then migrations and updated the database correctly.
Change your AuthUser like this:
public class AuthUser : IdentityUser
{
public virtual Students StudentProfile { get; set; }
public virtual Instructor InstructorProfile { get; set; }
public bool IsStudent { get; set; }
public bool IsInstructor { get; set; }
}
In your Context:
public DbSet<Students> Students { get; set; }
public DbSet<Instructor> Instructor { get; set; }
Migration and update:
After successfully updating the database, you need to change your View/Shared/_LoginPartial code:
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
to
#inject SignInManager<AuthUser> SignInManager
#inject UserManager<AuthUser> UserManager
Then
Select your LmsContext Add
Then start your app,and login.

Best way to create a map between two entities with a third one from another context

Hi I'd like to create a map between two entities (source: User, target: UserInfosDto) while one member of the target DTO (UserItemPreference) needs info from a third entity inside another context.
public class UserInfosDto
{
//public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public UserItemPreferencesDto UserItemPreferences { get; set; }
}
public class UserItemPreferencesDto
{
public bool SeeActuality { get; set; }
public bool IsInEditorMode { get; set; }
}
public class User
{
public string IdentityId { get; set; }
//...
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class UserIdentity
{
public string IdentityId { get; set; }
//...
public bool SeeActuality { get; set; }
public bool IsInEditorMode { get; set; }
}
User and UserIdentity come from different databases but have a common property IdentityId. I thought about using ITypeConverter in which I would inject the UserIdentity dbContext. Problem is that I can't find a way to use ITypeConverter on one member only.
Use an IValueResolver instead, which allows to resolve separate members instead of full types.
For your case above it will look like
public class UserItemPreferencesResolver
: IValueResolver<User, UserInfosDto, UserItemPreferencesDto>
{
private readonly UserEntityDbContext _dbContext;
public UserItemPreferencesResolver(UserEntityDbContext dbContext)
{
_dbContext = dbContext;
}
public UserItemPreferencesDto Resolve(
User source,
UserInfosDto destination,
UserItemPreferencesDto destinationMember,
ResolutionContext context
)
{
UserItemPreferencesDto preferences = /* resolve from _dbContext (and transform) */
return preferences;
}
}
Your create the mapping via
CreateMap<User, UserInfosDto>()
.ForMember(
dest => dest.UserItemPreferences,
opt => opt.MapFrom<UserItemPreferencesResolver>()
);

How to extend Application User to hold a collection of orders?

I'm trying to extend Application User (using Code-First) to hold a collection of orders, but I'm getting errors.
My Order class is
public class Order
{
public Order()
{
OrderDetails = new HashSet<OrderDetails>();
}
public int ID { get; set; }
public DateTime OrderDate { get; set; }
public string UserId { get; set; }
public bool IsDelivered { get; set; }
public bool IsReturned { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual ICollection<OrderDetails> OrderDetails { get; set; }
}
And I'm trying to extend Application user like this
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; }
public string Profession { get; set; }
public string TaxAuthority { get; set; }
public string TaxNumber { get; set; }
public string Address { get; set; }
public string PostalCode { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string MobilePhone { get; set; }
public bool NewsLetterSubscribe { get; set; } = false;
public DateTime TimeStamp { get; set; } = DateTime.Now;
public ICollection<Order> Orders { get; set; }
}
And I'm getting the following errors:
QCMS.Models.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.
QCMS.Models.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined. Define the key for this EntityType.
IdentityUserLogins: EntityType: EntitySet 'IdentityUserLogins' is based on type 'IdentityUserLogin' that has no keys defined.
IdentityUserRoles: EntityType: EntitySet 'IdentityUserRoles' is based on type 'IdentityUserRole' that has no keys defined.
Can you please help me to solve this problem?
UPDATE:
I'm using two db contexts. The one provided for Individual User Account (when the project is first created) and a second one named "qvModel" that is for all other database classes of my project.
public partial class qvModel : DbContext
{
public qvModel()
: base("name=qvModel")
{
}
//APPSETTINGS
public virtual DbSet<AdminLog> AdminLog { get; set; }
public virtual DbSet<WebLog> WebLog { get; set; }
//LANGUAGES
public virtual DbSet<Language> Languages { get; set; }
.
.
.
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<OrderDetails> OrderDetails { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Precision attribute for decimals
Precision.ConfigureModelBuilder(modelBuilder);
modelBuilder.Entity<Language>()
.HasMany(e => e.Brochures)
.WithRequired(e => e.Language)
.WillCascadeOnDelete(true);
.
.
.
modelBuilder.Entity<Order>()
.HasMany(c => c.OrderDetails)
.WithRequired(c => c.Order)
.WillCascadeOnDelete(true);
modelBuilder.Entity<ApplicationUser>()
.HasMany(c => c.Orders)
.WithRequired(c => c.User)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
I found a solution that is very simple.
The solution is to inherit from IdentityDbContext like this
public class qvModel : IdentityDbContext<ApplicationUser>
{
public qvModel()
: base("name=qvModel")
{
}
I was also missing the following line from OnModelCreating
base.OnModelCreating(modelBuilder);
After these changes, my migration is working and I stopped getting the errors I mentioned.

How to get dependent entities on ef core?

Here's my model schema.
This is the dependent entity
public class ArticleFee
{
public int ID { get; set; }
public string Description { get; set; }
public Type Type { get; set; }
public double? FixedFee { get; set; }
public int? RangeStart { get; set; }
public int? RangeEnd { get; set; }
public double? Percentage { get; set; }
[StringLengthAttribute(1, MinimumLength = 1)]
public string ArticleLetter { get; set; }
public Article Article { get; set; }
}
public class Article
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[KeyAttribute]
[StringLengthAttribute(1, MinimumLength = 1)]
public string Letter { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<ArticleFee> ArticleFees { get; set; }
}
Here's how I show data on my route but the ArticleFees just shows an empty array.
[HttpGetAttribute]
public IEnumerable<Article> Get()
{
return _context.Articles
.Include(a => a.ArticleFees)
.ToList();
}
Your model is good(*) and the Get() method too. Your issue is that an infinite loop is detected during the JSON serialization because Article points to ArticleFee and ArticleFee points to Article.
To solve your problem, you must configure the app in Startup.cs so that it "ignore" instead of "throw exception" when such a loop is detected. The solution in .NET Core from this SO answer:
services.AddMvc().AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}); ;
You will need to add using Newtonsoft.Json; to the file.
(*) Assuming that your Type entity is fine.

Using ComplexType with ToList causes InvalidOperationException

I have this model
namespace ProjectTimer.Models
{
public class TimerContext : DbContext
{
public TimerContext()
: base("DefaultConnection")
{
}
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectTimeSpan> TimeSpans { get; set; }
}
public class DomainBase
{
[Key]
public int Id { get; set; }
}
public class Project : DomainBase
{
public UserProfile User { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IList<ProjectTimeSpan> TimeSpans { get; set; }
}
[ComplexType]
public class ProjectTimeSpan
{
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public bool Active { get; set; }
}
}
When I try to use this action I get the exception The type 'ProjectTimer.Models.ProjectTimeSpan' has already been configured as an entity type. It cannot be reconfigured as a complex type.
public ActionResult Index()
{
using (var db = new TimerContext())
{
return View(db.Projects.ToList);
}
}
The view is using the model #model IList<ProjectTimer.Models.Project>
Can any one shine some light as to why this would be happening?
Your IList<ProjectTimeSpan> property is not supported by EF. A complex type must always be part of another entity type, you cannot use a complex type by itself. If you absolutely need to have ProjectTimeSpan as a complex type, you will need to create a dummy entity type that only contains a key and a ProjectTimeSpan, and change the type of Project.TimeSpans to a list of that new type.

Resources