Add Properties to Asp.Net 2.0 Identity Database first - c#

I have a DataBase First Website in mvc.
My Problem is that i want to add some Points to the AspNetUser and be able to show/modify them. For example when register.
So i added for example age to the AspNetUser updated the Database. Then i went to the edmx DataBase and updated my Model so far so good. Everything is working till this point.
Now i went to the IdentityModel and added the age.
public class ApplicationUser : IdentityUser
{
public int Age { 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;
}
}
Then i went to the RegisterViewModel
public class RegisterViewModel
{
.....
public int Age { get; set; }
}
Now when i try to start the Website and to Login, as soon as i press Login i get the Folowing error.
The model backing the 'ApplicationDbContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).
Zeile 77: // This doesn't count login failures towards account lockout
Zeile 78: // To enable password failures to trigger account lockout, change to shouldLockout: true
Zeile 79: var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
Zeile 80: switch (result)
Zeile 81: {
As soon as i comment out the last two changes everything works again.I´m wondering what i`ve done wrong.

In order for your project to work with the additional properties you need to add a "Migration" in order that Entity Framework Code First can update your database automatically-
Open the NuGet package manager console from the "Tools" menu in Visual Studio.
Type "add-migration AddedUserProperties"
Type "update-database"
See Code First Migrations on MSDN for more information.
Depending on your project configuration you may need to type "enable-migrations" which will set up code first migrations for you before this can work (if it is set up already you should have a "Migrations" directory in your project).
It is also possible to set your code first context to drop and recreate the database if the model has changed, or simply every time you run your code.
Obviously dropping the database is going to delete any users already registered, so this is probably best done in the early prototyping stage and with "DropCreateDatabaseWhenModelChanges" initialization rather than "DropCreateDatabaseAlways", or by seeding your test users so that these are automatically recreated with the database.
To do this -
Database.SetInitializer(new DropCreateDatabaseWhenModelChanges<ApplicationDbContext>());
See Understanding Database Initializers in Entity Framework Code First for more.

Related

Entityframework identity register from api with custom identityUser

I need to create an ApplicationUser (a derived class from IdentityUser) with my Asp.Net API, I need to create it in the same database that my Asp.net MVC uses. My request works since it gives me 200 response but my user is not in the AspNetUser table autogenerated by EntityFramework.
[HttpPost]
public async Task<ActionResult> Register(RegisterDTO register)
{
ApplicationUser user = new ApplicationUser();
if (register.Password == register.PasswordConfirm)
{
user.UserName = register.Email;
user.Email = register.Email;
}
else
{
return StatusCode(StatusCodes.Status500InternalServerError, new { Message = "Le mot de passe et sa confirmation ne sont pas identiques." });
}
var identityResult = await userManager.CreateAsync(user, register.Password);
if (!identityResult.Succeeded)
{
return StatusCode(StatusCodes.Status500InternalServerError, new { Error = identityResult.Errors });
}
return Ok();
}
this is my register from my api
services.AddDbContext<CegesDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<ICegesServices, CegesServices>();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders().AddDefaultUI()
.AddEntityFrameworkStores<CegesDbContext>();
this is the startup from my api
public class ApplicationUser : IdentityUser
{
public List<Entreprise> Entreprises { get; set; }
}
this is my ApplicationUser class in the Core project
I'm not sure what to do here. Do I need to create my on Create method for my ApplicationUser or am I missing something?
Check that the DefaultConnection points to the correct database in appsettings.json AND appsettings.Developer.json if that file exists.
I'm also assuming userManager.CreateAsync works correctly and is just part of AspNetCore.Identity.
My next advice would be to not split your models / data access layer across two projects like this. Instead create a Shared Project and reference your other projects from that.
In Visual Studio, this can be done by right clicking the project, selecting add, choose Project Reference and check the projects to reference.
In here you can manage your entire data access layer (DAL), and reference it in any other projects without need to worry about maintaining it in two locations that might be conflicting.
You can't update the database with your users migration if the database migration records do not match up. You'll need to add the users from the existing location, assuming you're using code first due to the AspNetUser table.
TL;DR
Add your data access layer (models, dbContext, migrations etc.) to a C# Shared Project and reference this in your MVC and Web API projects to keep consistency and reduce future work on maintenance.

`[Authorize (Roles="Role")]` not working, User.IsInRole("Role") always false

I feel like I'm missing something. Authorize by itself works, but using roles does not. The cshtml-Code contains razor stuff, which I'm not sure I'm allowed to use in combination with roles based authorization. Furthermore, IsInRole always returns false.
In my database in the table AspNetUserRoles, the role instance is there with the correct RoleId and UserId. I added the user to the role in the Seed method of the database.
if (!userManager.IsInRole(adminUser.Id, "SystemAdministrator"))
userManager.AddToRole(adminUser.Id, "SystemAdministrator");
Do I need to add them somewhere else, like in some kind of role manager?
Here's what I think is the relevant part of the Configuration in Startup:
app.CreatePerOwinContext<RoleManager<AppRole>>((options, context) =>
new RoleManager<AppRole>(
new RoleStore<AppRole>(context.Get<MyANTon.DataContext.AntContext>())));
Maybe, after this, I have to add the user to the role?
Edit: I found a possible error source for this. My db context does not seem to include the identity tables like AspNetUserRoles or even AspNetUsers. I migrated from Forms-Authentication to Identities last week, this is probably the problem now. Do I have to change the context accordingly? It inherits from IdentityDbContext<AppUser>, which is why I can't just add the AspUserstuff (since it's already there), but when I look at the context at runtime, it's not there...
Next edit: I was missing the web.config for my role manager. After adding it, it seems like my data context idea actually feels to be true. Now, the error thrown is: 'The entity type AppUser is not part of the model for the current context.'. My context inherits from IdentityDbContext<AppUser>, why doesn't it contain AppUser then?
Context class:
public class AntContext : IdentityDbContext<AppUser>
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<AntContext>(null);
modelBuilder.Entity<AppUser>().ToTable("AppUsers");
base.OnModelCreating(modelBuilder);
}
UserManager constructor call in controller:
private UserManager<AppUser> userManager = new UserManager<AppUser>(new UserStore<AppUser>());
When you are creating your roles ... make sure there are no extra spaces before or after the Role Name when you save them into the DB. I wasted 3/4 of a day trying to chase down why this was happening.
Its looks like you have "SystemAdministrator" role and may be many others.
You should use [Authorize(Roles = "RoleName")]
ex:- [Authorize(Roles = "SystemAdministrator")]
Cross verify in your database if you have the corresponding roles.

Add relationships to the ApplicationUser class in ASP.NET Identity (Database First)

I'm using ASP.NET Identity (Database First) in my ASP.NET MVC application. I followed the instructions here, to set up the ASP.NET Identity with database first approach.
My AspNetUsers table has a relationship with the Employee table (The Employee table has a UserId foreign key, and the AspNetUsers entity has an ICollection<Employee> property).
I would like to add the ICollection<Employee> property to the ApplicationUser, like below:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public ICollection<Employee> Employees { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> 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;
}
}
But, when I do that, I get the following error message:
EntityType 'AspNetUserLogins' has no key defined. Define the key for
this EntityType. AspNetUserLogins: EntityType: EntitySet
'AspNetUserLogins' is based on type 'AspNetUserLogins' that has no
keys defined.
Why am I getting this error message? How can I fix that?
I cannot reproduce the issue, even when I create the tables in another database without keys and relations. So I'm sure that there is a problem with your model. Unfortunately you didn't add code which I can compare, so I can't tell what is different and answer the question directly. The only thing I can do is to show what works for me. However, first I have some remarks.
I think you shouldn't follow the article. As there is no reason to add the context to an existing database.
Like Ivan Stoev mentioned you are not supposed to mix contexts. The Identity context is meant to authenticate the user. It stores the credentials, the roles of the user and claims. Where claims are meant to add identity information about the user.
In fact, the default Hometown field of the ApplicationUser template can be removed, as it is an identity claim which should be stored in the AspNetUserClaims table. Not something you need to extend the ApplicationUser for. Actually I can't think of any reason to extend the ApplicationUser.
About the roles, these are not really claims, as they tell nothing about the identity but rather are used for authorization. That's why it's fine that they are stored in the AspNetUserRoles table. Unfortunately roles are added to the identity as role claims, which makes things confusing.
Please note that the Identity information is present in the claims. This means that the application doesn't have to call the Identity context. E.g. User.IsInRole checks the role claims of the current identity, not the roles stored in the table.
About the different contexts, the other context (which I usually call the business model) has nothing in common with the Identity context. Email and other fields are not part, nor have meaning to the business model. You may think that those fields are redundant, but in fact they are not. I could login using a google account, but for the business use my work email address.
There are several reasons to keep the context seperated.
Seperation of concerns. Suppose you want to exchange the authentication framework in the future with another one. Like implement IdentityServer in case you want to support single sign-on (SSO).
You can't move the users table to another database if another application needs the same logins. So you'll end up adding other contexts as well to the database.
Trouble with migrations. If you mix the contexts then migrations will fail.
It'll makes things far more easier. This is the first problem you've encountered, not the last.
As also mentioned in the article:
At this point if you need to add any relationships (E.g. foreign keys)
from your own tables to these tables you are welcome to do so but do
not modify any of the Entity Framework 2.0 tables directly or later on
any of their POCO classes. Doing so will result in errors based upon
feedback I’ve received.
So how to manage the information if you shouldn't access the identity context from your application?
For the current user you don't need to access the users table. All the information is present in the identity claims. The only reason to access the identity context is to allow a user to login. Besides user management.
You can suffice by adding a reference to the user (the userid). If you need to show information of other users (like name) in a report, then create a user table in your business context to store the information. You can add relations to this table, as it is part of the same context.
Please let me know if you have questions about this approach.
Now the code that works for me. Like others have mentioned, it is not likely that adding the line:
public ICollection<Employee> Employees { get; set; }
is the cause. Without the virtual keyword I think it is even ignored (remains null).
When I follow the steps of the article then I end up with the following model:
public class ApplicationUser : IdentityUser
{
public string Hometown { get; set; }
//public virtual ICollection<Employee> Employees { 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;
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
// Disable migrations
//Database.SetInitializer<ApplicationDbContext>(null);
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
And then I add the Employee class and uncomment the line in the ApplicationUser class above:
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
//public virtual ApplicationUser ApplicationUser { get; set; }
public string ApplicationUserId { get; set; }
}
In the database I added the table:
CREATE TABLE [dbo].[Employees](
[Id] [int] NOT NULL,
[Name] [varchar](50) NOT NULL,
[ApplicationUserId] [nvarchar](128) NOT NULL,
PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
You can use the [ForeignKey] attribute to use a different field name.
You can try this or choose to keep both contexts seperated instead.
Concern: I know exactly what your burden is here. Yes, Microsoft, an esoteric cult, did very poor job in providing the information to this matter that is creating a relationship with the Identity (Entity Framework).
Contribution: Ruard van Elburg post on Aug 24 at 16:31 gives good insight on this matter; however, there was one key component that I noticed was missing in his code which was DbSet that needed to be placed in the DBContext of IdentityModels.
Tech Stack:
I provide my tech stack so that if this does not work with older versions of software, you will know what I used to get this issue resolved.
Visual Studio 2017 MVC 5. FYI, MVC 5 is built in into most recent VS.
SQL Server 17
MS SQL Management Studio 17
Solution:
Disclaimer!!! I understand that the concern is for database first; however, this solution is only for code first approach. But hey, it works!
Here I provide a walk through on how to do this. Please make sure you have all the dependencies in top margin of your code.
Step 1: Add
public virtual DbSet<ModelNameOfInterest> ModelNameOfInterest { get; set; } to public class ApplicationDbContext : IdentityDbContext<ApplicationUser>{} as seen in code below.
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.ComponentModel.DataAnnotations.Schema;
namespace AwesomeCode.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
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 class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
//A virtul DbSet in order to interact with the autogenerated code the identity framewrok produces.
public virtual DbSet<ModelNameOfInterest> ModelNameOfInterest { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
}
Step 2: Add public virtual ApplicationUser ApplicationUser { get; set; } to your model that you want to create a relationship with as seen code below.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;
namespace AwesomeCode.Models
{
public class WorkExp
{
[Key]
public int Id { get; set; }
public string JobTitle { get; set; }
//Create foreign key with reference to ApplicationUser_Id that was auto-generated by entity framework.
public virtual ApplicationUser ApplicationUser { get; set; }
}
}
Step 3: Given that you set up your connection string for your database, you need to produce a migration. Path to Package Manager Console: Tools->NuGet Packer Manager->Package Manager Console
Enable a migration if migration folder is absent in root: After PM>, type Enable-Migrations you should see a migration folder with two files.
Once migration is enabled: After PM>, type Update-Database You should see tables in your database now.
To add another migration: After PM>, type Add-Migration After Name:, type InitialCreate or Your model of interest You should see tables in your database now. You should see tables in your database now.
Step 4: Double check that the model of interest's foreign key is properly referenced to the AspNetUser table. In MS Management Studio, you can create a relational diagram to show the references. You can find how to do that on google.
Step 5: As always stay cool, calm, and collected.

Move IdentityModels to DataAccessLayer

I'm trying to create my first 3 layers application with C# and MVC 5. I use Identity 2 for my authentication system and I installed it with this command :
Install-Package Microsoft.Aspnet.Identity.samples _pre
I found an article and question like that I need in website. I did step by step exact like this article. Here is Article
I added a class library to my project and move my models in this class library but I got 3 errors on IdentityConfig.cs Startup.Auth.cs and Manage controller about
user.GenerateUserIdentityAsync(UserManager));
Error detail is here:
CS7036 There is no argument given that corresponds to the required formal parameter 'authenticationType' of 'ApplicationUser.GenerateUserIdentityAsync(UserManager, string)' PresentationLayer D:\Projects\Divar\PresentationLayer\App_Start\IdentityConfig.cs 148 Active
When I ctrl+click on GenerateUserIdentityAsync I move to DataAccessLayer and this method.
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
In addition I changed my IdentityModels.cs name space to DataAccessLayer As well but I don't know why I have error Again,
Here Is my error image
enter image description here
You need to provide the authenticationType. There is no overload of user.GenerateUserIdentityAsync with 1 argument.
Should be more like
string a;
user.GenerateUserIdentityAsync(UserManager, a);
Just a small tip as well... I've done this same task before and I got alot of value from looking at different ORMs integrated wit Identity. Maybe you won't but i think this is the one that originally helped me...
https://github.com/JudahGabriel/RavenDB.AspNet.Identity

Get users last accessed time

I am using IdentityServer 4 (with sql storage) for my asp.net core API. I would like to add a "Last Accessed" field somewhere in the database that can be used to order one of my user types in a user search endpoint. Ideally this would show the last time they performed an action in the system.
If I do this in the login method it won't be accurate as it will only update when a token is initially generated. I could add a method to every endpoint that the user accessed but this doesn't feel like the right solution as it would be repeating myself.
What is the correct place and method for maintaining a database field that records the last time a user accessed the system?
For recording the user active time, you could try Middleware which will be called for every request.
Here are steps.
1.Add field to ApplicationUser which is store the last accessed time
public class ApplicationUser : IdentityUser
{
public DateTime LastAccessed { get; set; }
}
2.run command add-migration LastAccessed and update-database
3.Add middleware to update the last accessed time based on the user name
app.UseAuthentication();
app.Use(async (context, next) => {
await next.Invoke();
//handle response
//you may also need to check the request path to check whether it requests image
if (context.User.Identity.IsAuthenticated)
{
var userName = context.User.Identity.Name;
//retrieve uer by userName
using (var dbContext = context.RequestServices.GetRequiredService<ApplicationDbContext>())
{
var user = dbContext.ApplicationUser.Where(u => u.UserName == userName).FirstOrDefault();
user.LastAccessed = DateTime.Now;
dbContext.Update(user);
dbContext.SaveChanges();
}
}
});
I added an answer for a similar question which I did with a centralized solution in the identity server project (using IEventSink). We have 30+ apis and I found it better to do the solution in only one place instead of doing it in each and every api. So i added this answer here just in case anybody came across and have the same requirements as mine. This is my answer

Categories

Resources