I have an application that uses identity database to store users and customers.
Each customer has also a separate database with its data and its connection string is stored in Customer table in the identity database.
AspNetUsers has a field to tell which customer the user belongs to (also identity db).
I want to assign connection string to the user when he logs in and make it available in the application for the duration of the session.
I currently have customer model:
public partial class `Customer`
{
public int Id { get; set; }
public string Name { get; set; }
public int NoLicenses { get; set; }
public bool? Enabled { get; set; }
public string CustomerConnectionString { get; set; }
}
and user model:
public class ApplicationUser : IdentityUser
{
public string CustomerId { get; set; }
public bool? IsEnabled { get; set; }
// there ideally I'd have a connstring property
}
The models map db table fields.
I'm using .NET Core 1.1 and EF Core.
With the defalut ASP.NET Identity template , you can :
extend the ApplicationUser class in Models folder :
public class ApplicationUser : IdentityUser
{
public string CustomerId { get; set; }
public bool? IsEnabled { get; set; }
//add your custom claims
public string CustomerConnectionString { get; set; }
}
Add your custom model to ApplicationDbContext in Data folder :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
public DbSet<Customer> Customers { get; set; }
}
Sync your database : Add-Migration xxxx , then run the Update-Database command in Package Manager Console . Now you have the Customer table and have CustomerConnectionString column in AspNetUsers table.
Create you own implementation of IUserClaimsPrincipalFactory by inheriting the default one to generate a ClaimsPrincipal from your user :
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public AppClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager
, RoleManager<IdentityRole> roleManager
, IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{ }
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
if (!string.IsNullOrWhiteSpace(user.CustomerId))
{
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("customid", user.CustomerId)
});
}
if (!string.IsNullOrWhiteSpace(user.CustomerConnectionString))
{
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("CustomerConnectionString", user.CustomerConnectionString)
});
}
return principal;
}
}
Register the custom factory you just created in your application startup class, after adding Identity service:
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
Then you could access the claims like :
var connectString = User.Claims.FirstOrDefault(c => c.Type == "CustomerConnectionString").Value;
Modify the creating/editing user view/controller , add the customer dropdownlist on view , get the custom id in Register function in AccountController , query the connectingString of custom from db , and save in ApplicationUser object :
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
Related
I work with Asp.Net Core WebApi project.
Can I add my tables to IdentityDbContext, like this:
public class ApplicationDbContext : IdentityDbContext<User>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
public DbSet<ProgrammerRole> ProgrammerRoles { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<SubProject> SubProjects { get; set; }
public DbSet<Report> Reports { get; set; }
}
Or do I need to create a second DbContext. And if i create a second DbContext
how can I communicate wiht User in IdentityDbContext.
And my second question:
If i add data in IdentityDbContext, like above, How do I get the data from my tables in ApplicationDbContext?
Because i need to pass DbContextOptions object every time I create a new instance оf ApplicationDbContext. I do this in Startup.cs:
// ===== Add DbContext ========
var connectionString = Configuration.GetConnectionString("DbConnection");
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
I saw in older version of Asp.Net Core, that i can pass Connection String in IdentityDbContext constructor, but now only DbContextOptions.
And i can't do, for example this:
[AllowAnonymous]
[HttpGet]
public IEnumerable<Project> GetRoles()
{
using (var db = new ApplicationDbContext())
{
return db.Projects;
}
}
Can I add my tables to IdentityDbContext, like this:
Yes, it is how you create custom tables. You do not need to create another DbContext. E.g.
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Project> Projects { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Project>(entity =>
{
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
});
base.OnModelCreating(builder);
}
}
Note: you might need to run dotnet ef migrations add Initial and dotnet ef database update for database migration.
using (var db = new ApplicationDbContext()) {...}
You should not create or manage ApplicationDbContext inside controller. If you do so, they become tightly coupled, and you cannot implement unit tests.
Instead, you let dependency inversion (DI) container manage it for you. E.g.
public class UserController : Controller
{
private readonly ApplicationDbContext _context;
public UserController(ApplicationDbContext context)
{
_context = context;
}
[AllowAnonymous]
[HttpGet]
public IEnumerable<Project> GetRoles()
{
return _context.Projects;
}
}
I solve my problem, i just replaced code in my ApplicationDbContext, and get connection string from method:
public class ApplicationDbContext : IdentityDbContext<User>
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(GetConnectionString());
}
private static string GetConnectionString()
{
const string databaseName = "EmployeeReportsDb";
const string databasePass = "SuperPuper_Random_DB-key!";
return $"Server=localhost;" +
$"database={databaseName};" +
$"Trusted_Connection = True;" +
$"MultipleActiveResultSets = True;" +
$"pwd={databasePass};" +
$"pooling=true;";
}
public DbSet<ProgrammerRole> ProgrammerRoles { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<SubProject> SubProjects { get; set; }
public DbSet<Report> Reports { get; set; }
}
here is the resource: https://medium.com/#ozgurgul/asp-net-core-2-0-webapi-jwt-authentication-with-identity-mysql-3698eeba6ff8
Allright! So I extended my IdentityUser but it is not saving my Users anymore. The code samples can be viewed below. I am trying to seed my database with an admin user but it is not being stored. I have tried running a debugger on my seeds, but it doesn't trigger on anything. So I'm kinda lost. So, here is the code.
My extended User Class:
public class User : IdentityUser, IBaseEntity
{
public virtual ICollection<TimeLogEntry> TimeLogEntries { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsRemoved { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> 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;
}
}
Then ofcourse the Context that inherits from the IdentityDbContext:
public class Context : IdentityDbContext<User>, IContext
{
public Context()
: base("MyConnection", throwIfV1Schema: false)
{
Configuration.ProxyCreationEnabled = false;
}
// DBSETS
public static Context Create() => new Context();
// MODELBUILDER
}
and finaly my seed:
protected override void Seed(Context context)
{
// Launch debugger on seeds
//if (System.Diagnostics.Debugger.IsAttached == false)
// System.Diagnostics.Debugger.Launch();
SeedUsers(context);;
}
private void SeedUsers(Context context)
{
var manager = new UserManager<User>(new UserStore<User>(context));
var adminUser = new User
{
UserName = "Admin",
Email = "admin#test.com"
};
if (!manager.Users.Any())
{
manager.Create(adminUser, "Admin");
}
}
I hope someone can help me with this!
The Seed() that is inside your initializer will only run when your database is recreated - for instance when you are using DropCreateDatabaseIfModelChanges.
If you use migrations (MigrateDatabaseToLatestVersion initializer), there is another Seed() that runs every time you apply the migration using update-database.
http://blog.oneunicorn.com/2013/05/28/database-initializer-and-migrations-seed-methods/
Having some troubles with saving new Employee to database.
Project Model(useless fiels removed):
public class Project{
public virtual ICollection<EmployeeManager.Employee> Employees { get; set; }
}
EmployeeManager.Employee Model:
public class Employee{
public virtual ApplicationUser User { get; set; }
public virtual ProjectsManager.Project ProjectObj { get; set; }
}
Function:
public async Task AddNewEmployeeAsync(string projectId, string email, string fullname){
var projectInfo = await GetByIdAsync(projectId);
var userInfo = new ApplicationUser();
if (await _context.Users.AnyAsync(_ => _.Email == email)){
userInfo = await _context.Users.FirstAsync(_ => _.Email == email);
}
else{
....
userInfo = user;
}
projectInfo.Employees.Add(new EmployeeManager.Employee{
User = userInfo
});
_context.Entry(projectInfo).State = EntityState.Modified;
_context.SaveChanges();
}
And after all that, i have no changes in database.
Why?
If i use _context.Employees.Add() -> it creates new project entity in database.
For Entity Framework to create the correct relationships (one to many) without configuration (FluentApi) you need to make a couple of changes to Employee class
public class Employee
{
public virtual ApplicationUser User { get; set; }
public int ProjectId { get; set; } //foreign key maybe a string as defined in your Project class
public virtual ProjectsManager.Project Project { get; set; }
}
I'm looking at the interfaces on the new ASP.NET Identity classes and the database it creates using Entity Framework Code First. I'm using the Visual Studio 2013 RC.
At first glance the database schema looks reasonably normal:
But all the keys are NVARCHAR(128)
And for some crazy reason AspNetUserSecrets.Id is a PK that looks like it could point to more than one record in the AspNetUsers table. Does this mean multiple AspNetUsers will have to share the same password?
When I look at the Looking at the interfaces you're forced to implement, these are all strings...
public class User : IUser
{
public string Id { get; set; }
public string UserName { get; set; }
}
public class UserSecret : IUserSecret
{
public string UserName { get; set; }
public string Secret { get; set; }
}
public class UserRole : IUserRole
{
public string UserId { get; set; }
public string RoleId { get; set; }
}
public class UserClaim : IUserClaim
{
public string UserId { get; set; }
public string ClaimType { get; set; }
public string ClaimValue { get; set; }
}
public class UserManagement : IUserManagement
{
public string UserId { get; set; }
public bool DisableSignIn { get; set; }
public DateTime LastSignInTimeUtc { get; set; }
}
public class Tokens : IToken
{
public string Id { get; set; }
public string Value { get; set; }
public DateTime ValidUntilUtc { get; set; }
}
public class UserLogin : IUserLogin
{
public string UserId { get; set; }
public string LoginProvider { get; set; }
public string ProviderKey { get; set; }
}
public class Role : IRole
{
public string Id { get; set; }
public string Name { get; set; }
}
So I'm coming to terms with the fact that I may have to implement this using strings for PK and FK relationships.
But I'd really love to know WHY it's built like this...?
EDIT: Time has passed and there are now articles on how to extend the asp.net identity to use int (or guid) fields:
http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity
The intent was to allow both arbitrary id types (i.e. int, guid, string), but also avoid having serialization/casting issues for the id property.
So you can define your keys however you like and just implement the interface method
public class MyUser : IUser {
public int Id { get; set; }
string IUser.Id { get { return Id.ToString(); } }
}
Adding to what Hao said:
The Identity runtime prefers strings for the user ID because we don’t want to be in the business of figuring out proper serialization of the user IDs (we use strings for claims as well for the same reason), e.g. all (or most) of the Identity interfaces refer to user ID as a string.
People that customize the persistence layer, e.g. the entity types, can choose whatever type they want for keys, but then they own providing us with a string representation of the keys.
By default we use the string representation of GUIDs for each new user, but that is just because it provides a very easy way for us to automatically generate unique IDs.
With ASP.NET Core, you have a very simple way to specify the data type you want for Identity's models.
First step, override identity classes from < string> to < data type you want> :
public class ApplicationUser : IdentityUser<Guid>
{
}
public class ApplicationRole : IdentityRole<Guid>
{
}
Declare your database context, using your classes and the data type you want :
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
And in your startup class, declare the identity service using your models and declare the data type you want for the primary keys :
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext, Guid>()
.AddDefaultTokenProviders();
In ASP.NET identity tables, primary keys will still be in NVARCHAR but in your application it's will be the data type you want.
You can check this in a controller :
[HttpGet]
public async Task<IActionResult> Test()
{
ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
Guid userId = user.Id; // No cast from string, it's a Guid data type
throw new NotImplementedException();
}
I have a model named 'Account' with a foreign key 'UserId' which maps to the aspnet_users table.
The Account model also has as a navigation property 'Creator', the Creator model contains UserId, Username & Email.
What's the best way to map the values from aspnet_users to the Creator model?
I'd ideally like to be able to access this like: #Model.Creator.Username
I'm not looking to manipulate the membership system, I have a service that handles that. I'd just ideally like to map this object for read only purposes.
You can create a model which is not included in the context.
public class AccountContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Creator>(); //Ignore map
}
public void FillCreatorToAccount(Account account)
{
var creator = Database.SqlQuery<Creator>("select UserId,UserName,Email from aspnet_users where UserId=#p0", account.UserId).SingleOrDefault();
account.Creator = creator;
}
}
public class Account
{
public Guid UserId { get; set; }
public Creator Creator { get; set; }
}
public class Creator
{
public Guid UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}