MVC 5 IdentityDbContext and DbContext transactions - c#

I work on a MVC 5 project with EF6.1. As a beginner, I try to keep the code generated by the MVC 5 template in order to use the default authentification system with AccountController.
However, I had to add some classes, like "User.cs" as below, so I'm using Entity Framework Data Model.
public partial class User
{
public User()
{
this.Logs= new HashSet<Log>();
}
public int IdUser { get; set; }
public string AspNetUsersId { get; set; } //References to AspNetUsers.Id
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job{ get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
So, I have two contexts :
public partial class MyEFDataModelContainer : DbContext
{
public MyEFDataModelContainer ()
: base("name=MyEFDataModelContainer")
{
}
public DbSet<User> Users { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
So, my scenario is that I need to create an AspNetUsers, then add a AspNetRoles to this AspNetUsers and then create a User. We suppose the AspNetUsers creation and AspNetRoles works, but if the User creation fails, how can I rollback the AspNetUsers and AspNetRoles?
Can I create a transaction with these two contexts?
Unfortunately, I also need to call WCF webservices to execute this task? What can I do?
Best regards,
Updated
My merged contexts:
public partial class User: IdentityUser { }
public partial class MyEFDataModelContainer : IdentityDbContext<User>
{
public MyEFDataModelContainer() : base("name=MyEFDataModelContainer")
{ }
}
My AccountController.cs:
public class AccountController : Controller
{
public AccountController()
{
UserManager =new UserManager<User>(new UserStore<User>(new MyEFDataModelContainer()))
}
public UserManager<User> UserManager {get;set;}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new USER() { UserName = model.UserName, Email = model.Email, PhoneNumber = model.Phone };
var result = await UserManager.CreateAsync(user, model.Password);
var role = await UserManager.AddToRoleAsync(user.Id, "Membre");
if (result.Succeeded)
{
using (UserBusiness business = new UserBusiness())
{
var userResult = business.AddUser(model.FirstName, model.LastName, user.Id);
}
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
return View(model);
}
}
My Web.config :
<connectionStrings>
<add name="MyEFDataModelContainer" connectionString="metadata=res://*/MyEFDataModel.csdl|res://*/MyEFDataModel.ssdl|res://*/MyEFDataModel.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=DB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
But now, I have the error The specified type member ' UserName ' is not supported in LINQ to Entities . Only initializers , entity members , and entity navigation properties are supported . at the following line:
var result = await UserManager.CreateAsync(user, model.Password);

So, my first question would be, why do you need two contexts? Is there a reason that the aspnet tables have to be separate from the business tables?
Could you do something like:
public partial class MyEFDataModelContainer : IdentityDbContext<User>
{
public MyEFDataModelContainer ()
: base("name=MyEFDataModelContainer")
{
}
public DbSet<User> Users { get; set; }
}
Make sure to have your custom User class inherit from IdentityUser.
public partial class User : IdentityUser
{
public User()
{
this.Logs= new HashSet<Log>();
}
public int IdUser { get; set; }
public string AspNetUsersId { get; set; } //References to AspNetUsers.Id
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Job{ get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
Then, change your AccountController to this connection string. This way everything is in the same database.
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<User>(
new UserStore<User>(new MyEFDataModelContainer())))
{
}
}
Although, to answer your question, yes you can wrap access to two contexts inside a transaction. You will have to deal with MSDTC, however, which, in my experience, can sometimes be frustrating.

Related

Changes to ICollection (many to many relationship) are not saved using ASP.NET Core and Entity Framework Core

I feel like I'm completely desperate because I don't know how to fix this. Please help me if you can.
I am trying to build many-to-many relationship between my two models, "project" and "user". As in the typical examples, this relationship is implemented using ICollection.
My models - User:
namespace MLpolygon.Models
{
public class user : IdentityUser
{
public string FName { get; set; }
public string LName { get; set; }
public ICollection<project> Projects { get; set; }
public user()
{
Projects = new List<project>();
}
}
}
I use AspNetCore.Identity to work with user, but it seem not to be important, the changes are not saved in other model as well.
Project:
namespace MLpolygon.Models
{
public class project
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<user> Users { get; set; }
public ICollection<calculator> Calculators { get; set; }
public project()
{
Users = new List<user>();
Calculators = new List<calculator>();
}
}
}
Controller: I am trying to create a relationship when adding a new project. I pass a new project object and the name of the user who created it to the function. All user names are unique.
Changes to the ICollection persist until you exit this function. But after this function exits, the changes disappear.
public class projectController : Controller
{
private readonly MathLibContext _context;
private readonly UserManager<user> _userManager;
public projectController(MathLibContext context, UserManager<user> userManager)
{
_context = context;
_userManager = userManager;
}
...
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name")] project Project, string UserName)
{
if (ModelState.IsValid)
{
var result = _userManager.FindByNameAsync(UserName).Result;
Project.Users.Add(result);
_context.Add(Project);
await _context.SaveChangesAsync();
result.Projects.Add(Project);
await _userManager.UpdateAsync(result);
return RedirectToAction("Index", "Home");
}
return RedirectToAction(nameof(Add));
}
}
My DbContext file:
public class MathLibContext : IdentityDbContext
{
public MathLibContext(DbContextOptions<MathLibContext> options)
: base(options)
{
Database.EnsureCreated();
}
public DbSet<calculator> Calculators { get; set; }
public DbSet<project> Projects { get; set; }
}
Changes to the ICollection persist until you exit this function. But after this function exits, the changes disappear. means the middle table(projectuser) did not add data successfully?
From your code, I find that your error may be in MathLibContext, Beacuse you custom identity, So you can't implement IdentityDbContext directly, You need wirte like this:
public class MathLibContext : IdentityDbContext<user>{xxxx}
Then in your Startup.cs, You may need to change too:
services.AddIdentity<user, IdentityRole>(xxxx)xxx;

Why does my IdentityUser ICollection property not load on ASP.NET Core 5.0?

I have a many-to-many relationship between AppUser : IdentityUser, and TaxAccount, which is an abstract class that separates into Household and Business. I want to query how many TaxAccounts a User has access to on load.
Here's the AppUser class.
public class AppUser : IdentityUser
{
public string FullName { get; set; }
public virtual List<TaxAccount> Accounts { get; set; }
}
Here's the TaxAccount classes.
public abstract class TaxAccount
{
[Key]
public int ID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public virtual List<AppUser> Users { get; set; }
}
public class Household : TaxAccount
{
[MaxLength(1000)]
public string HomeAddress { get; set; }
}
public class Business : TaxAccount
{
[EmailAddress, MaxLength(500)]
public string Email { get; set; }
}
However, when I attempt to query the AppUser object in my Razor page, AppUser.Accounts is null! As a result, I always return to the "NoAccount" page.
public async Task<IActionResult> OnGetAsync()
{
AppUser = await _manager.GetUserAsync(User);
// Checks how many TaxAccounts the user has.
if (AppUser.Accounts == null)
{
return RedirectToPage("NoAccount");
}
else if (AppUser.Accounts.Count == 1)
{
return RedirectToPage("Account", new { accountID = AppUser.Accounts[0].ID });
}
else
{
return Page();
}
}
I've found that if I use a .Include() statement to manually connect TaxAccounts to AppUsers, the connection sticks! I just have to insert a .ToList() call that goes nowhere. My question is: why do I need this statement?
AppUser = await _manager.GetUserAsync(User);
_context.Users.Include(X => X.Accounts).ToList();
// Now this works!
if (AppUser.Accounts == null)
EDIT: I've tested removing virtual to see if it was a lazy loading thing, there's no difference.

ASP.NET Identity my schema

I already have DB and need to add ASP.NET Identity. My AspNetUser class:
public partial class AspNetUser
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public AspNetUser()
{
AspNetUserClaims = new HashSet<AspNetUserClaim>();
AspNetUserLogins = new HashSet<AspNetUserLogin>();
AspNetRoles = new HashSet<AspNetRole>();
}
public string Id { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
public int? CompanyId { get; set; }
public string FullName { get; set; }
[Required]
[StringLength(128)]
public string Discriminator { get; set; }
public string SMSnumber { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AspNetUserClaim> AspNetUserClaims { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AspNetUserLogin> AspNetUserLogins { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AspNetRole> AspNetRoles { get; set; }
}
then my ApplicationDbContext context:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MainContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
connection string:
<add name="MainContext" connectionString="data source=server;initial catalog=3md_maindb_remote;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
ApplicationUser class:
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 int CompanyId { get; set; }
public string SMSnumber { get; set; }
public string FullName { get; set; }
}
ApplicationUserManager:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = false
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
when I try to login, I get an error:
Exception Details: System.Data.SqlClient.SqlException: Invalid column
name 'Email'. Invalid column name 'EmailConfirmed'. Invalid column
name 'PhoneNumber'. Invalid column name 'PhoneNumberConfirmed'.
Invalid column name 'TwoFactorEnabled'. Invalid column name
'LockoutEndDateUtc'. Invalid column name 'LockoutEnabled'. Invalid
column name 'AccessFailedCount'.
Also, I have MainContext:
public partial class MainContext : DbContext
{
public MainContext()
: base("name=MainContext")
{
}
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; }
as I understand, I need to use my MainContext instead of ApplicationDbContext (with my schema) , but don't understand how...
You can use multiple contexts on the same database. Out of the box the Identity Context is set to go. If you don't need to change it then don't. Use the Identity tables for Identity only.
You don't need to define the tables in the Identity Context. Just add the ApplicationDbContext. You can use the same connectionstring as for your MainContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("name=MainContext")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
You really shouldn't add all your tables to the IdentityContext as that is not the place where they should be. Though the tables may be in the same database, the two contexts have nothing to do with eachother.
Keep you own tables in MainContext and do not relate beyond contexts, other than a column where you can reference (value only, not a database reference) the IdentityUser to your MainContext.User. Remove the AspNet... tables from MainContext.
You cannot use the class AspNetUser. So you may as well remove this. Use the ApplicationUser class instead. To overcome the problem of missing fields use this:
public class ApplicationUser : IdentityUser
{
[NotMapped]
public override bool EmailConfirmed { get => base.EmailConfirmed; set => base.EmailConfirmed = value; }
// etc.
}
This will ignore the missing columns. Keep in mind that the properties exist in the code and will have default values. Though I do not think your code is actually going to depend on these properties.
In case the MainContext gives you trouble, you can create your own context using the same entity objects. You can create multiple contexts on the same database, including used tables only, even if you use a seperate assembly.
Since ApplicationDbContext is a DbContext out of the box. You don't need any other DbContext in your project. So simply remove MainContext Class completely in your project and put your own entities in ApplicationDbContext class.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MainContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
// put your extra entities here like this.
public IDbSet<MyEntity> MyEntites { get; set;}
// don't put Identity related entities like AspNetRoles. Since already added
}

Combining AppDbContext And IdentityDbContext Issue

I am Using EF 6.0 and want to combine AppDbContext & IdentityDbContext into a single context which is AppDbContext.
This is a requirement as i have other tables which has relations with the AspNetUsers table created by EF.
The problem is EF is creating two tables for Users such as AspNetUsers and IdentityUsers.
Also If i use DbSet<ApplicationUser> in DbContext instead of DbSet<IdentityUsers> , Then add-migration throws up error.
My AppDbContext is
public class AppDbContext : IdentityDbContext<ApplicationUser>,IDisposable
{
public AppDbContext()
: base("MvcArchBSEFDB", throwIfV1Schema: false)
{
}
public static AppDbContext Create()
{
return new AppDbContext();
}
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
//public DbSet<ApplicationUser> AppUsers { get; set; } // breaks migration
//Multiple object sets per type are not supported. The object sets 'AppUsers ' and 'Users' can both contain instances of type 'MvcArchBS.DAL.Setp.ApplicationUser'.
public DbSet<IdentityUser> AppUsers { get; set; } // Creates Two Tables AspNetUsers & IdentityUser
public DbSet<IdentityUserRole> UserRoles { get; set; }
public DbSet<IdentityUserClaim> Claims { get; set; }
public DbSet<IdentityUserLogin> Logins { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<SubModule> SubModules { get; set; }
public DbSet<PageMst> Pages { get; set; }
public void Dispose()
{
base.Dispose();
}
}
And My ApplicationUser Class is
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
status = "A";
reptngusrid = "admin";
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int usrid { get; set; }
public string usrdescr { get; set; }
public int empid { get; set; }
public int usrgrpid { get; set; }
[StringLength(1)]
public string status { get; set; }
public string reptngusrid { get; set; }
public int defmodid { get; set; }
[StringLength(20)]
public string cltur { get; set; }
[ForeignKey("defmodid")]
public virtual Module Module { 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;
}
}
How do i get this to work ? I want to use something like context.AppUsers in my queries which i am unable to get.
It turns out my Service Layer Project also needs a reference to Microsoft.AspNet.Identity.EntityFramework.dll.
Once i added that all identity entity sets are available now with my context.
Also as tmg & Brendan had pointed out the IdentityDbContext members DbSets are not required in the AppDbContext.
Hope this helps someone.

Creating inheritance users from base asp.net identity user

I have problem in which i would like to create N, two in the example, user objects (e.g. Customer & Supplier) which all inherent from the asp.net IdentityUser object. These object have very different additional data besides the the data from the IdentityUser. I would like to use the IdentityUser user as this gives me a flexible way of taking care of authentication and authorization.
This example has been very stripped down but should supply sufficient information concerning the not being able to create a concrete user (e.g. Customer of Supplier). It seems i need to use the UserManager object as this also takes care of creating for example the password hash and additional security information.
I get presented the following error:
{"Attaching an entity of type 'Supplier' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate."}
Classes which inherent from IdentityUser
public class Customer : IdentityUser
{
public string CustomerProperty { get; set; }
}
public class Supplier : IdentityUser
{
public string SupplierProperty { get; set; }
}
Database context class
public class ApplicationDbContext : IdentityDbContext {
public ApplicationDbContext() : base("ApplicationDbContext")
{
Database.SetInitializer(new ApplicationDbInitializer());
}
public DbSet<Customer> CustomerCollection { get; set; }
public DbSet<Supplier> SupplierCollection { get; set; }
}
Seeding class which throws the exception
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var userStore = new UserStore(context);
var userManager = new UserManager(userStore);
// Seed customer user which inherents from asp.net IdentityUser
var user = userManager.FindByEmail("customer#customer.com");
if (user == null)
{
user = new User()
{
UserName = "customer#customer.com",
Email = "customer#customer.com"
};
userManager.Create(user, userPassword);
var customerUser = new Customer()
{
Id = user.Id,
CustomerProperty = "Additional Info"
};
context.Entry(customerUser).State = EntityState.Modified;
context.SaveChanges();
}
// Seed supplier user which inherents from asp.net IdentityUser
var user = userManager.FindByEmail("supplier#supplier.com");
if (user == null)
{
user = new User()
{
UserName = "supplier#supplier.com",
Email = "supplier#supplier.com"
};
userManager.Create(user, userPassword);
var supplierUser = new Supplier()
{
Id = user.Id,
IBAN = "212323424342234",
Relationship = "OK"
};
context.Entry(supplierUser).State = EntityState.Modified;
context.SaveChanges();
}
}
}
**** UPDATE ****
The solution below works but i am still struggling with two issues:
I would always like to have one user type (e.g. Customer of Supplier) associated with the IdentityUser. I though about using an interface but this doesn't work.
If i also add the virtual reference towards the IdentityUser on the user types i get an 'Unable to determine the principal end of an association between the types 'ApplicaitonUser' and 'Supplier'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.' exception.
Classes
public class Customer
{
[Key]
public int CustomerId { get;set; }
public string CustomerProperty { get; set; }
*public virtual User User { get; set; }*
}
public class Supplier
{
[Key]
public int SupplierId { get;set; }
public string SupplierProperty { get; set; }
*public virtual User User { get; set; }*
}
**Class IdentityUser (which works) **
public class User : IdentityUser
{
public virtual Supplier Supplier { get; set; }
public virtual Customer Customer { get; set; }
}
**Class IdentityUser (what i would like) **
public class User : IdentityUser
{
public virtual IConcreteUser ConcreteUser{ get; set; }
}
Database context class
public class ApplicationDbContext : IdentityDbContext {
public ApplicationDbContext() : base("ApplicationDbContext")
{
Database.SetInitializer(new ApplicationDbInitializer());
}
public DbSet<Customer> CustomerCollection { get; set; }
public DbSet<Supplier> SupplierCollection { get; set; }
}
**Seeding class **
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var userStore = new UserStore(context);
var userManager = new UserManager(userStore);
var roleManager = new RoleManager(roleStore);
var user = userManager.FindByEmail("customer#customer.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "customer#customer.com",
Email = "customer#customer.com"
Customer = new Customer()
{
CustomerProperty = "Additional Info"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Customer");
}
user = userManager.FindByEmail("supplier#supplier.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "supplier#supplier.com",
Email = "supplier#supplier.com",
Supplier = new Supplier()
{
IBAN = "212323424342234",
Relationship = "OK"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Supplier");
}
}
}
As others do too I think this is a design problem. There are some alternative approaches like:
use roles to define the "user-type" (a user can be supplier AND customer)
make the Supplier and Customer entities a relation not extension of the user
e.g.:
public class ApplicationUser : IdentityUser
{
public virtual Customer Customer { get; set; }
public virtual Supplier Supplier { get; set; }
}
public class Customer
{
[Key]
public int Id { get; set; }
public virtual ApplicationUser User { get; set; }
public string CustomerProperty { get; set; }
}
public class Supplier
{
[Key]
public int Id { get; set; }
public virtual ApplicationUser User { get; set; }
public string SupplierProperty { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
}
public class ApplicationDbInitializer
: DropCreateDatabaseAlways<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
var userStore = new UserStore(context);
var userManager = new UserManager(userStore);
var roleManager = new RoleManager(roleStore);
var user = userManager.FindByEmail("customer#customer.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "customer#customer.com",
Email = "customer#customer.com"
Customer = new Customer()
{
CustomerProperty = "Additional Info"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Customer");
}
user = userManager.FindByEmail("supplier#supplier.com");
if (user == null)
{
user = new ApplicationUser()
{
UserName = "supplier#supplier.com",
Email = "supplier#supplier.com",
Supplier = new Supplier()
{
IBAN = "212323424342234",
Relationship = "OK"
}
};
userManager.Create(user, userPassword);
roleManager.AddUserToRole("Supplier");
}
}
}
and in your logic you can do something like:
if (User.IsInRole("Customer"))
{
// do something
}
DISCLAIMER: This is not a "copy&paste" example and should just give you an idea of a different approach.
I just resolved a similar problem. I created a navigation property of abstract type DomainUser in my AppUser (that inherits from Identity User)
public class AppUser : IdentityUser
{
public DomainUser DomainUser { get; set; }
}
DomainUser looks like this:
public abstract class DomainUser : IAggregateRoot
{
public Guid Id { get; set; }
public AppUser IdentityUser { get; set; }
}
I inherit from DomainUser in all concrete domain user types:
public class AdministrationUser : DomainUser
{
public string SomeAdministrationProperty { get; set; }
}
public class SupplierUser : DomainUser
{
public string SomeSupplierProperty { get; set; }
}
public class Customer : DomainUser
{
public string SomeCustomerProperty { get; set; }
}
And in DbContext in OnModelCreating method I configured Entity Framework to store all entities inherited from DomainUser in separate tables (it's called Table per Concrete Type). And configured one to one relationship between IdentityUser and DomainUser:
modelBuilder.Entity<DomainUser>()
.Map<AdministrationUser>(m =>
{
m.MapInheritedProperties();
m.ToTable("AdministrationUsers");
})
.Map<SupplierUser>(m =>
{
m.MapInheritedProperties();
m.ToTable("SupplierUsers");
})
.Map<Customer>(m =>
{
m.MapInheritedProperties();
m.ToTable("Customers");
});
modelBuilder.Entity<DomainUser>()
.HasRequired(domainUser => domainUser.IdentityUser)
.WithRequiredPrincipal(groomUser => groomUser.DomainUser);
This code added column "DomainUser_Id" to table AspNetUsers and now I'm able to access IdentityUser navigation property in each domain user and DomainUser navigation property in AppUser.

Categories