I'm doing a hobby project for myself and later turn it into a portfolio work.
I'm working in Entityframework 6, with razor pages.
My class UserModel is a child of the IdentityUser class. (I presume you know what an IdentityUser, if not)
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-7.0
Inside the UserModel I also stores an Icollection of GroupClass.
My GroupClass has an ICollection of UserModels. Which creates a many to many relationship in EF6.
When i try to store the UserModel object in the groupclass, i get the error: Cannot implicitly convert type to AspNetCore.Identity.IdentityUser to System.collection.Generic.ICollection.
What i understand is, i somehow need to change the class groupClass. To be able to store an identity variable instead of a Icollection usermodel. `
What i don't understand is, why isn't UserModel already a part of IdentityUser, as it's a child and should have the same parameters as it's parent.
My question is, how do i store the UserModel inside my groupclass?
public class GroupClassModel
{
[Key]
public int GroupClassID { get; set; }
// info dump removed
public string userID { get; set; }
public ICollection<UserModel>? userModels { get; set; }
}
public class UserModel : IdentityUser
{
// info dump removed
public int? groupClassID { get; set; }
public ICollection<GroupClassModel>? GroupClass{ get; set; }
}
if (ModelState.IsValid)
{
//info removed
if (newGroup == null)
{
var NewGroup = new GroupClassModel()
{
userModels = await userManager.GetUserAsync(User)
};
Context.groupClass.Add(NewGroup );
Context.SaveChanges();
}
return RedirectToPage("Index");
}
return Page();
public class Context : IdentityDbContext
{
Context(DbContextOptions<Context> options) : base(options)
{
}
public DbSet<GroupClassModel> groupClassModels { get; set; }
public DbSet<UserModel> UserModels { get; set; }
public DbSet<MasterModel> MasterModels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
`
I tried changing the Icollection in both classes to Icollection. same problem. I tried changing Icollection To IdentityUser but i got tons of other errors. Now i'm 50/50 if latter is the correct way of doing this.
GroupClassModel.userModels is a collection of UserModels. This line of code is trying to set that property to a single instance of a UserModel.
// this generates an error
userModels = await userManager.GetUserAsync(User);
Instead you need to instantiate a new collection and add the current user's UserModel to that collection. (You're also going to need to cast the return value of userManager.GetUserAsync, which is an IdentityUser, to UserModel.)
userModels = new List<UserModel>();
// currentUser is an IdentityUser
var currentUser = await userManager.GetUserAsync(User);
// cast the IdentityUser to a UserModel
UserModel currentUserModel = (UserModel)currentUser;
userModels.Add(currentUserModel);
I am working on a website using ASP and entity framework and identity.
I am actually facing a problem where an entity framework doesn't add entries into the database tables even doe I am adding classes to my model.
The actual problem I am facing just hits a specific subclass of the identity model which I declared as the following :
public partial class ApplicationUser : IdentityUser
{
public virtual List<Friend> Friends { get; set; }
}
Where friends are declared as the following :
[Table("Friends")]
public class Friend
{
[Key, Column(Order = 1)]
public string AddingUser { get; set; }
[Key, Column(Order = 2)]
public string AddedUser { get; set; }
public string Pseudo { get; set; }
public DateTime FriendsSince { get; set; }
public bool IsInvitation { get; set; }
public bool IsAccepted { get; set; }
public bool IsBloqued { get; set; }
public int NumberOfUnreadenMessages { get; set; }
public virtual List<Message> FriendShipMessages { get; set; }
[ForeignKey("AddingUser")]
public virtual ApplicationUser AddingFriend { get; set; }
[ForeignKey("AddedUser")]
public virtual ApplicationUser AddedFriend { get; set; }
public Friend()
{
FriendShipMessages = new List<Message>();
}
public Friend(ApplicationUser AddingUser, ApplicationUser AddedUser)
{
AddedFriend = AddedUser;
AddingFriend = AddingUser;
this.AddingUser = AddingUser.Id;
IsInvitation = false;
IsAccepted = false;
IsBloqued = false;
FriendsSince = DateTime.Now;
NumberOfUnreadenMessages = 0;
FriendShipMessages = new List<Message>();
}
}
I did add to the EF modelbuilder this rule to map the application user to the friends table:
// Mapping Friends And Bloqueds
modelBuilder.Entity<ApplicationUser>()
.HasMany(c => c.Friends)
.WithRequired(c => c.AddingFriend)
.WillCascadeOnDelete(false);
In my controller part to add a user I am using the following code:
public string AddFriend(string AddedUserId)
{
var AddedUser = UserManager.FindById(AddedUserId);
var AddingUser = UserManager.FindById(User.Identity.GetUserId());
AddingUser.Friends.Add(new Friend(AddingUser, AddedUser) {
IsInvitation = true });
return "Friend was added successfully";
}
Where is the problem why is my controller not adding effectively the entry to the database knowing that during debugging sessions I do have the good entities going into the controller and it also effectively create the instance of the friend class correctly.
Thank you in advance for your response.
Changes to entity objects don't get persisted to a database until you actually tell your context to do it. My preferred method of this would be to use the context directly instead of the UserManager:
var addedUser = db.Users.Single(u => u.Id == addedUserId);
var addingUser = db.Users.Single(u => u.Id == User.Identity.GetUserId());
addingUser.Friends.Add(new Friend(addingUser, addedUser)
{
IsInvitation = true;
});
db.SaveChanges();
But this might work (I'm assuming UserManager calls db.SaveChanges internally) if you really want to use the UserManager:
var addedUser = UserManager.FindById(addedUserId);
var addingUser = UserManager.FindById(User.Identity.GetUserId());
addingUser.Friends.Add(new Friend(addingUser, addedUser)
{
IsInvitation = true;
});
UserManager.UpdateUser(addingUser);
Side note: I've also changed the casing of your parameters and variables to match with common convention. I highly recommend learning that way and sticking with it, particularly if you intend to work with other developers.
I'm trying to use the new ASP.NET Identity in my MVC5 application, specifically I'm trying to integrate ASP.NET Identity into an existing database. I've already read the questions/answers on SO pertaining to DB First and ASP.NET Identity, and having followed all the recommendations I still can't add roles to my database, although I have no problems adding users. Here's my code:
var context = new PayrollDBEntities();
var roleManager = new RoleManager<AspNetRole>(new RoleStore<AspNetRole>(context));
bool roleExists = roleManager.RoleExists(roleDto.Name);
if (roleExists){
return false;
}
var role = new AspNetRole(roleDto.Name){
Name = roleDto.Name,
};
IdentityResult result = roleManager.Create(role);//Getting exception here
At the last line of code I get an exception of type 'System.InvalidOperationException': The entity type IdentityRole is not part of the model for the current context.
Here is my context:
public partial class PayrollDBEntities : IdentityDbContext
{
public PayrollDBEntities()
: base("name=PayrollDBEntities")
{
}
public virtual DbSet<AspNetRole> AspNetRoles { get; set; }
public virtual DbSet<AspNetUserClaim> AspNetUserClaims { get; set; }
public virtual DbSet<AspNetUserLogin> AspNetUserLogins { get; set; }
public virtual DbSet<AspNetUser> AspNetUsers { get; set; }
......
}
My AspNetUser and AspNetRole classes derive from IdentityUser and IdentityRole respectively, but I'm still getting that exception. Here is my database diagram:
Any help would be greatly appreciated.
You have to specify during the creation of User Store that AspNetRole is used instead of IdentityRole. You can achieve this by using the UserStore class with 6 type parameters:
new UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(new PayrollDBEntities());
This indicates changes at User Manager creation as well. Here is a simplified example about the creation of needed instances:
public class AspNetUser : IdentityUser { /*customization*/ }
public class AspNetRole : IdentityRole { /*customization*/ }
public class PayrollDBEntities : IdentityDbContext //or : IdentityDbContext <AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
}
public class Factory
{
public IdentityDbContext DbContext
{
get
{
return new PayrollDBEntities();
}
}
public UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> UserStore
{
get
{
return new UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(DbContext);
}
}
public UserManager<AspNetUser, string> UserManager
{
get
{
return new UserManager<AspNetUser, string>(UserStore);
}
}
public RoleStore<AspNetRole> RoleStore
{
get
{
return new RoleStore<AspNetRole>(DbContext);
}
}
public RoleManager<AspNetRole> RoleManager
{
get
{
return new RoleManager<AspNetRole>(RoleStore);
}
}
}
After a few days of trying to get this to work in a clean manner, I've come to the conclusion that if you're using Database first and want to integrate ASP.NET Identity into your app, by far the easiest and cleanest solution is to create your own membership provider by overriding ASP.NET Identity. It's actually pretty easy, so far I've implemented UserStore and RoleStore to my liking. I've added columns/relations specific to my domain in my database, and whenever I create a user or a role, I take care of my database commits by adding the required relations. My UserStore implementation is quite similar to this. My RoleStore implementation is something like this:
public class ApplicationRoleStore : IRoleStore<ApplicationRoleDTO>
{
private PayrollDBEntities _context;
public ApplicationRoleStore() { }
public ApplicationRoleStore(PayrollDBEntities database)
{
_context = database;
}
public Task CreateAsync(ApplicationRoleDTO role)
{
if (role == null)
{
throw new ArgumentNullException("RoleIsRequired");
}
var roleEntity = ConvertApplicationRoleDTOToAspNetRole(role);
_context.AspNetRoles.Add(roleEntity);
return _context.SaveChangesAsync();
}
public Task DeleteAsync(ApplicationRoleDTO role)
{
var roleEntity = _context.AspNetRoles.FirstOrDefault(x => x.Id == role.Id);
if (roleEntity == null) throw new InvalidOperationException("No such role exists!");
_context.AspNetRoles.Remove(roleEntity);
return _context.SaveChangesAsync();
}
public Task<ApplicationRoleDTO> FindByIdAsync(string roleId)
{
var role = _context.AspNetRoles.FirstOrDefault(x => x.Id == roleId);
var result = role == null
? null
: ConvertAspNetRoleToApplicationRoleDTO(role);
return Task.FromResult(result);
}
public Task<ApplicationRoleDTO> FindByNameAsync(string roleName)
{
var role = _context.AspNetRoles.FirstOrDefault(x => x.Name == roleName);
var result = role == null
? null
: ConvertAspNetRoleToApplicationRoleDTO(role);
return Task.FromResult(result);
}
public Task UpdateAsync(ApplicationRoleDTO role)
{
return _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
private ApplicationRoleDTO ConvertAspNetRoleToApplicationRoleDTO(AspNetRole aspRole)
{
return new ApplicationRoleDTO{
Id = aspRole.Id,
EnterpriseId = aspRole.EnterpriseId,
Name = aspRole.Name
};
}
private AspNetRole ConvertApplicationRoleDTOToAspNetRole(ApplicationRoleDTO appRole)
{
return new AspNetRole{
Id = appRole.Id,
EnterpriseId = appRole.EnterpriseId,
Name = appRole.Name,
};
}
}
And my ApplicationRoleDTO:
public class ApplicationRoleDTO : IRole
{
public ApplicationRoleDTO()
{
Id = Guid.NewGuid().ToString();
}
public ApplicationRoleDTO(string roleName)
: this()
{
Name = roleName;
}
public string Id { get; set; }
public string Name { get; set; }
public Guid EnterpriseId { get; set; }
}
I also found these 2 articles pretty helpful:
Overview of Custom Storage Providers for ASP.NET Identity
Implementing a Custom MySQL ASP.NET Identity Storage Provider
I'll explain here with the code exampels :).
The trick is, they are already in the IdentityDbContext (AspNetRoles, AspNetUserClaims, AspNetUsers, ....)
In the IdentityModel you will see ApplicationUser is empty at the top. If you want to customize these users or roles, just add properties here and then update your database via the console
Example of my context
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public DbSet<Request> Requests { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<PriceType> PriceTypes { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Price> Prices { get; set; }
public DbSet<GuestbookPost> Posts { get; set; }
public DbSet<Count> Counts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceLine> InvoiceLines { get; set; }
...
}
So no application user is defined here, but I did add more properties to it, example:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string GroupName { get; set; }
public string Email { get; set; }
[StringLength(15)]
public string Phone { get; set; }
public string Remark { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidUntil { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
I know this is an old question, but just in case someone else is having a hard time adding roles/users when they modified asp identity to use numeric primary keys (int/long) instead of the default string for the Identity Roles, so if you have changed the IdentityUserRole in IdentityModels.cs to something like this:
public class Role : IdentityRole<long, UserRole>
{
public Role() { }
public Role(string name) { Name = name; }
}
You have to use the class Role instead of the default IdentityRole when constructing the RoleManager, so your code should be like this:
public static void RegisterUserRoles()
{
ApplicationDbContext context = new ApplicationDbContext();
var RoleManager = new RoleManager<Role, long>(new RoleStore(context));
if (!RoleManager.RoleExists("Administrador"))
{
var adminRole = new Role {
Name = "Administrador",
};
RoleManager.Create(adminRole);
}
}
So this should populate your database properly, I think all experienced ASP programmers already know this, but for others this could take some time to figure out.
I solved with a different way.
First I splited in two different Projects and Contexts.
My project that Handle the Identity has this context:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDisposable
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
This is my ApplicationUser:
public class ApplicationUser : IdentityUser
{
//Put here the extra properties that Identity does not handle
[Required]
[MaxLength(150)]
public string Nome { get; set; }
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;
}
}
And my ApplicationUserManager looks like this:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
//Setting validator to user name
UserValidator = new UserValidator<ApplicationUser>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
//Validation Logic and Password complexity
PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
//Lockout
UserLockoutEnabledByDefault = true;
DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
MaxFailedAccessAttemptsBeforeLockout = 5;
// Providers de Two Factor Autentication
RegisterTwoFactorProvider("Código via SMS", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Seu código de segurança é: {0}"
});
RegisterTwoFactorProvider("Código via E-mail", new EmailTokenProvider<ApplicationUser>
{
Subject = "Código de Segurança",
BodyFormat = "Seu código de segurança é: {0}"
});
//Email service
EmailService = new EmailService();
// Definindo a classe de serviço de SMS
SmsService = new SmsService();
var provider = new DpapiDataProtectionProvider("Braian");
var dataProtector = provider.Create("ASP.NET Identity");
UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtector);
}
}
I hope that this helps someone.
This solution was from this article:
Eduardo Pires - But it is in Portuguese
I fixed this issue by changing the web.config DefaultConnection connectionString property so it points to the new SQLServer database
This is my view model.
public class ProductViewModel
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
When form is posted from client the form is submitted to this Controller
public async Task<IHttpActionResult> AddProduct(ProductViewModel productViewModel)
{
await ServiceInstances.PostAsync("product/add", productViewModel);
return Ok();
}
Then this controller submit the form to the API controller
Which is on my separate Project.
[HttpPost]
[Route("add")]
public IHttpActionResult AddProduct(ProductViewModel model)
{
_productService.AddProduct(model.UserServiceDetails());
return Ok();
}
Extension UserServiceDetails Where i get the Login User Info
public static UserServiceDetailModel<T> UserServiceDetails<T>(this T model)
{
var serviceRequestModel = new ServiceRequestModel<T>()
{
Model = model,
LoginInfo = HttpContext.Current.User.Identity.GetUserLoginInfo();
};
}
AddProductService:
public void AddProduct(UserServiceDetailModel<ProductViewModel> serviceRequestModel)
{
var repo = _genericUnitOfWork.GetRepository<Product, Guid>();
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
mapped.Id = Guid.NewGuid();
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
repo.Add(mapped);
_genericUnitOfWork.SaveChanges();
}
Now my question is Is there any way to assign the value to this field CreatedDate and CreatedById before posting it to service?
Reduce these logic to mapper:
mapped.CreatedDate = GeneralService.CurrentDate();
mapped.CreatedById = serviceRequestModel.LoginInfo.UserId;
Or is there any way that those field gets mapped to Product when
var mapped = _mapper.Map<ProductViewModel, Product>(serviceRequestModel.Model);
Sometime i may have the List<T> on view-model and there i have to add this field using the loop.
So this same mapping may get repeated over and over on Add Method Or Update.
In some entity i have to assign the ModifiedDate and ModifiedById also.
My Mapper Configuration:
public class ProductMapper : Profile
{
public ProductMapper()
{
CreateMap<ProductViewModel, Product>();
}
}
I cannot add the Enitity as IAuditableEntity and Overrride in ApplicationDbContext because my DbContext is in separate Project and i donot have access to Identity there.
I'm currently writing a small test application to understand how IdentityUser works.
I've created a MyUser class that inherits from IdentityUser. The only additional property on my custom user class is a collection of my Book class.
I've created methods on the controller that successfully store new users to the database and associated Books. The problem is when I try to retrieve a user, the Books collection for that user is not populated - it's always null.
When I check the database I can see that a Book is stored in the database with an associated User ID however I can't seem to retrieve this collection.
Here is what I have so far:
Book.cs:
public class Book
{
public int Id { get; set; }
public string Isbn { get; set; }
public string Title { get; set; }
public string Author { get; set; }
}
MyUser.cs:
public class MyUser : IdentityUser
{
public IList<Book> Books { get; set; }
}
MyAppContext.cs:
public class MyAppContext : IdentityDbContext<MyUser>
{
public MyAppContext() : base("MyApp")
{
}
public DbSet<Book> Books { get; set; }
}
AuthRepository:
public class AuthRepository : IDisposable
{
private MyAppContext _ctx;
private UserManager<MyUser> _userManager;
public AuthRepository()
{
_ctx = new MyAppContext();
_userManager = new UserManager<MyUser>(new UserStore<MyUser>(_ctx));
}
public async Task<IdentityResult> RegisterUser(RegistrationModel userModel)
{
MyUser user = new MyUser();
user.UserName = userModel.UserName;
var result = await _userManager.CreateAsync(user, userModel.Password);
return result;
}
public async Task<IdentityResult> UpdateUser(MyUser userModel)
{
var result = await _userManager.UpdateAsync(userModel);
return result;
}
public async Task<MyUser> FindUser(string userName, string password)
{
var user = await _userManager.FindAsync(userName, password);
return user;
}
public async Task<MyUser> GetUser(string userName)
{
var user = await _userManager.FindByNameAsync(userName);
return user;
}
public void Dispose()
{
_ctx.Dispose();
_userManager.Dispose();
}
}
I figured maybe within the GetUser() method I could manually retrieve all books from the Book table with _ctx.Books.Where(b => b.MyUser_id == user.Id) however intellisense isn't even giving me the MyUser_Id property on the Books table.
I'm not really sure how to go about this. All I want to do is load all the associated books for a user automatically but I'm not sure how to do this. Any ideas?
Thanks
Your class for Book doesn't include user information for the foreign key reference. Try adding
[ForeignKey("UserId")]
public MyUser User { get; set; }
to the Book class definition.
When you get the users with the query
_ctx.Users.Include(u=> u.Books)
The books for each user should be included.