Extending EF Core Identity UserStore with integer user ID - asp.net-core

Using Blazor server, dotnet 5 and Entity Framework Core, I have successfully customised Identity using integer IDs
e.g
public class ApplicationUser : IdentityUser<int>
{
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
}
I now want to extend UserStore for custom login to store password history to prevent duplication. The documented way of doing this seems to be:
public class ApplicationUserStore : UserStore<ApplicationUser>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
public override async Task<IdentityResult> CreateAsync(ApplicationUser appUser)
{
await base.CreateAsync(appUser);
await AddToUsedPasswordAsync(appUser, appUser.PasswordHash);
}
public Task AddToUsedPasswordAsync(ApplicationUser appuser, string userpassword)
{
appuser.UserUsedPassword.Add(new UsedPassword() { UserID = appuser.Id, HashPassword = userpassword });
return UpdateAsync(appuser);
}
}
This works for the default GUID Id but when using an integer it throws the error:
The type ApplicationUser cannot be used as type parameter 'TUser' in the generic type or method 'UserStore'. There is no implicit reference conversion from ApplicationUser' to 'Microsoft.AspNetCore.Identity.IdentityUser'.
What is the correct way to do this?
EDIT:
Per #Yinqiu, modified for custom ApplicationRole to
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
Builds successfully and creates users but gives run time error when trying to access ApplicationUserManager.IsInRoleAsync:
{"Cannot create a DbSet for 'IdentityUserRole' because this type is not included in the model for the context."}
However I have a custom User Role:
public class ApplicationUserRole : IdentityUserRole<int>
{
public ApplicationUserRole() : base() { }
public bool IsSystemEssential { get; set; }
}
With a definition in ApplicationDbContext :
public DbSet ApplicationUserRoles { get; set; }

SOLVED (provisionally)
After looking at the overloaded inheritance options you have to extend as follows:
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, int, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationUserToken, ApplicationRoleClaim>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
}
This seems to work. Still need to check all role and claim updates.

You can change your ApplicationUserStore as following.
public class ApplicationUserStore : UserStore<ApplicationUser,IdentityRole<int>, ApplicationDbContext,int>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null) : base(context, describer)
{
}
//...
}

Related

AutoMapperMappingException: Missing type map configuration or unsupported mapping IN .NET 6

I have this entity:
public class Genres
{
public int Id { get; set; }
[Required(ErrorMessage ="the field {0} is required")]
[StringLength(50)]
[FirstLetterUpperCase]
public string Name { get; set; }
}
And this DTO or model:
public class GenresDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
I have initiated my mapper like this:
public class AutoMapperClass : Profile
{
public AutoMapperClass()
{
generateMapper();
}
private void generateMapper()
{
CreateMap<GenresDTO, Genres>().ReverseMap();
CreateMap<GenresCreationDTO, Genres>();
}
}
I have also written this part of code in my program.cs :
builder.Services.AddAutoMapper(typeof(IStartup));
I am using .NET 6 and Visual Studio, and when I run my project, I get an error that is mentioned in the title and its related to this section :
public async Task<ActionResult<List<GenresDTO>>> Get()
{
var genres = await dbContext.Genres.ToListAsync();
return mapper.Map<List<GenresDTO>>(genres);
}
which is in my Controller file, and I initiated the mapper like this :
private readonly ILogger<GenresController> ilogger;
private readonly ApplicationDBContext dbContext;
private readonly IMapper mapper;
public GenresController(ILogger<GenresController> ilogger,
ApplicationDBContext dbContext , IMapper mapper)
{
this.ilogger = ilogger;
this.dbContext = dbContext;
this.mapper = mapper;
}
Should be probably typeof(Program) in registration (assuming that you are using .Net6 where we have only Program.cs)
builder.Services.AddAutoMapper(typeof(Program))
If you have multiple projects in solution,then value used there should be a file in the assembly in which the mapping configuration resides.

How can i use UserManager with custom user?

I have created custom user class which inherits IdentityUser<int>.
[Table("Users", Schema = "UserData")]
public class User : IdentityUser<int>
{
/// <summary>
/// Property for sake of creating One-to-One relationship UserDetails -> User
/// </summary>
[Required]
public UserDetails UserDetails { get; set; }
}
public class BlogDbContext : IdentityDbContext<User, IdentityRole<int>, int>
{
public BlogDbContext(DbContextOptions<BlogDbContext> options)
: base(options) {}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
//UserDataConfig
builder.ApplyConfiguration(new UserConfiguration());
builder.ApplyConfiguration(new UserDetailsConfiguration());
//UserData
builder.Entity<Location>()
.HasOne<UserDetails>(s => s.UserDetails)
.WithOne(g => g.Location)
.HasForeignKey<UserDetails>(ad => ad.LocationId);
builder.Entity<User>()
.HasOne<UserDetails>(s => s.UserDetails)
.WithOne(g => g.User)
.HasForeignKey<UserDetails>(ad => ad.UserId);
base.OnModelCreating(builder);
}
//UserData
public DbSet<User> Users { get; set; }
public DbSet<UserDetails> UserDetails { get; set; }
}
}
Now I am trying to create following field
private UserManager<User, int> _userManager;
And my error:
The type 'ApplicationCore.DataModel.UserData.User' cannot be used as
type parameter 'TUser' in the generic type or method
'UserManager<TUser, TKey>
How can I fix it? Thanks for your attention.
Replace User to IdentityUser and Role to IdentityRole and working fine. like
public class DataContext : IdentityDbContext<IdentityUser,IdentityRole, string, IdentityUserClaim<string>, IdentityUserRole<string>,
IdentityUserLogin<string>, IdentityRoleClaim<string>,IdentityUserToken<string>>
Or use:-
public class ApplicationUser : IdentityUser<int>
{
}
public class ApplicationRole : IdentityRole<int>
{
}
public class BlogDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
}
It will resolve your issue.

.NET Core 3 dependency injection in custom validation [duplicate]

This question already has answers here:
Inject Dependencies into Validation Attribute using ASP.NET Core's WebAPI
(4 answers)
Closed 1 year ago.
I want to make a custom validation attribute in .NET Core called CheckIfEmailExists. I want to make sure the user is not in my database already. So this is my create user view model:
public class CreateUserViewModel
{
public readonly UserManager userManager;
public CreateUserViewModel()
{
}
public ExtendedProfile ExtendedProfile { get; set; }
public User User { get; set; }
public int SchemeId { get; set; }
public SelectList Schemes { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
[CheckIfEmailExists()]
[Display(Name = "Email Address")]
public string Email { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "Confirm Email Address")]
public string ConfirmEmail { get; set; }
}
Here is my custom validation:
public class CheckIfEmailExists : ValidationAttribute
{
private readonly UserManager _userManager;
public CheckIfEmailExists(UserManager userManager)
{
var _userManager = userManager;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var user = (User)validationContext.ObjectInstance;
var result = _userManager.FindByEmailAsync(user.Email).Result;
//do something
return ValidationResult.Success;
}
}
I get an error when I add my custom validation on my email property, the error is that I must pass in the usermanager object to the custom class constructor.
Why doesn't my app just inject the object itself?
Is there a way I can create a user manager object in my custom class without coupling the classes?
Should I only access my database in my controller?
The comment above from Nikki9696 helped me and now I know how to get my user manager and dbcontext in my custom validation attribute.
public class IsEmailInDatabase : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var context = (DatabaseContext)validationContext.GetService(typeof(DatabaseContext));
if (value == null)
{
return null;
}
else
{
var results = context.Users.AnyAsync(x => x.Email == value.ToString());
if (results.Result)
{
return new ValidationResult("This email address already exists in our database");
}
return null;
}
}
}

In EF Code First, How to have ApplicationUser UserId as foreign key in a custom entity?

My Custom Entity:
public class Order
{
[Key,Column(Order=0)]
public int OrderId { get; set; }
//Other properties
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser User { get; set; }
}
ApplicationUser Class:
public class ApplicationUser : IdentityUser
{
//one to many relation
public virtual List<Sandwich.Order>Order { get; set; }
public async Task<ClaimsIdentity>
GenerateUserIdentityAsync(UserManager<ApplicationUser> manager){..}
}
I have two DbContexts (One default of AppUser and One I created):
public class ADbContext : DbContext
{
public ADbContext() : base("DefaultConnection")
{
}
public DbSet<Toppings> ToppingsDbset { get; set;}
//I had to comment the line below to in order to work with ToppingDBset but then I can't work with OrderDBSet
//public DbSet<Order> OrderDbSet { get; set; }
}
//Default AppDbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
When I run it just working with ApplicationDbContext it works fine and creates following table with all the relationships.
enter image description here
My problem: is when I try to work with ADbContext with un-commented public DbSet<Order> OrderDbSet { get; set; }
{"One or more validation errors were detected during model generation:\r\n\r\n.IdentityUserLogin: : EntityType 'IdentityUserLogin' has no key defined. Define the key for this EntityType.\r\n.IdentityUserRole: : EntityType 'IdentityUserRole' has no key defined
Solutions I tried:
//Adding following method on ADbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
I am trying to have all tables created in one Database. I am using EF6.
try this in OnModelCreating(DbModelBuilder modelBuilder):
modelBuilder.Entity()
.HasMany(c => c.Order)
.WithOne(e => e.ApplicationUser);
refrence
Solved :
In DbContext file, I added OrderDbSet within ApplicationDbContext class
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Order> OrderDbset { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
}

exception:"type was not mapped" in entityframework codefirst with layers

i'm trying to apply LAYERS Concept on demo project developed using mvc and entity framework both
Data Annotations : for validations in Data Access Layer and
Fluent API : for mapping and tables relations
Problem : DbContext didn't Create DB and there is a Runtime Exception :
The type 'Domain.DataLayer.Member' was not mapped. Check that the type has not been explicitly excluded by using the Ignore method or NotMappedAttribute data annotation. Verify that the type was defined as a class, is not primitive, nested or generic, and does not inherit from EntityObject.
Code : my solutions consists of :
1- class library (Domain.Classes project): where i wrote all of my classes
public class Member
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
}
2- DAL (Domain.DataLayer project): also another class library and i referenced domain.classes
namespace Domain.DataLayer.Repositories
{
[MetadataType(typeof(MemberMetadata))]
public partial class Member : Classes.Member , IValidatableObject
{
public Member()
{
Tasks = new HashSet<Task>();
History = new HashSet<Commint>();
}
public string ConfirmPassword { get; set; }
public HashSet<Task> Tasks { get; set; }
public HashSet<Commint> History { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
if (!string.Equals(Password,ConfirmPassword))
{
result.Add(new ValidationResult("mismatch pwsd", new[] {"ConfirmPassword" }));
}
return result;
}
}
}
and i used repository pattern :
public class MemberRepository : IRepository<Member>
{
public Task<IQueryable<Member>> GetAllEntities()
{
return Task<IQueryable<Member>>.Factory.StartNew(() => new Context().Members.AsQueryable());
}
}
3-BLL : for sake of simplicity : there is no Business Logic Layer
4- PL (Domain.Application MVC Project) : Member Controller :
public async Task<ActionResult> Index()
{
var members = await _repository.GetAllEntities();
return View(members);
}
Note : i depended on DbContext to create DB with name like : Domain.DataLayer.Context but it didn't craete DB so i created the DB and passed the connectionString through Context constructor like this :
namespace Domain.DataLayer
{
public class Context : DbContext
{
public Context(): base("InterviewDemo") // i tried also base("name=InterviewDemo")
{
}
public DbSet<Member> Members { get; set; }
public DbSet<Task> Tasks { get; set; }
public DbSet<Commint> TaskHistory { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MemberConfig());
modelBuilder.Configurations.Add(new TaskConfig());
modelBuilder.Configurations.Add(new CommintConfig());
base.OnModelCreating(modelBuilder);
}
}
}