I am fairly new to coding with asp.net so there might be an obvious answere to my question but I haven't found one yet.
So currently I am developing a site for project management and I want the users to get notified when an event happens, eg. they were added to a new project, a project has been updated etc.
For that I have expanded the IdentityUser Model with a new property List
public class CojectUser : IdentityUser
{
public List<Notification> Notifications { get; set; }
}
public class Notification
{
public int NotificationID { get; set; }
public string Message { get; set; }
public bool Seen { get; set; }
}
When an event happens I add them to the user's notification list and update the user via the userManager.
public class EventBroker<T> : IEventBroker<T>
{
private readonly UserManager<CojectUser> userManager;
public EventBroker(UserManager<CojectUser> userMgr, IUserValidator<CojectUser> userValid)
{
userManager = userMgr;
}
public async Task NotifyAsync(Message<T> message, List<UserRole> recipients)
{
foreach (var user in recipients)
{
var cojectUser = await userManager.FindByNameAsync(user.Name);
if (cojectUser != null)
{
if (cojectUser.Notifications == null)
{
cojectUser.Notifications = new List<Notification>();
}
cojectUser.Notifications.Add(new Notification
{
Message = message.Information,
Seen = false
});
IdentityResult result = await userManager.UpdateAsync(cojectUser);
if (!result.Succeeded)
{
throw new UserUpdateFailException();
}
}
}
}
}
}
I am able to save the custom data to the database, but I am unable to load it again from database.
When I want to display the user's notifications userManager retrieves an user object with null as notification list. Even though the data is stored in database.
public async Task<IActionResult> Index()
{
CojectUser user = await userManager.GetUserAsync(User);
if(user.Notifications == null)
{
user.Notifications = new List<Notification>();
}
return View(user);
}
Data in database:
Can anybody tell me what I am doing wrong?
UserManager don't eager load properties by default.
You should use DatabaseContext directly.
var user = _context.Users.Include(c => c.Notifications).Where(u => u.Id == user.Id).ToList();
I have an SQLite database. I work with it using EclipseLink and JPA. In addition I have an entity class User:
import com.vaadin.server.VaadinService;
import java.io.Serializable;
import javax.persistence.*;
#Entity
public class User implements Serializable {
#Id #GeneratedValue
long id; // Unique identifier of the user in the DB
String username; // Unique name used for login
String password; // Password used for login
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
In a registration form I create a user and then call an EntityManager to persist() the changes:
public int createUser(String username, String password, String password_confirmation) {
int regStatus = 0;
if(checkValidUsername(username)) {
if(checkValidPassword(password, password_confirmation)) {
EntityManager em = factory.createEntityManager();
try {
em.getTransaction().begin();
User user = new User(username, password);
em.persist(user);
em.getTransaction().commit();
}
catch(Exception e) {
System.out.println(e.getMessage());
regStatus = 3;
}
finally {
em.close();
}
}
else regStatus = 2; // Password mismatch
}
else regStatus = 1; // User with selected username already present in DB
return regStatus;
}
It works without any problems. I get each and every newly registered user in my USER table. However when I try to change the password it doesn't work. Here are the methods that are related to this procedure:
// Inside the controller for my settings view - here the user can change various things related to his/her profile
public void setCurrentUser() {
currentUser = UserController.findUserByName((String)VaadinSession.getCurrent().getAttribute("username")); // findUserByName() is a static method
}
// Inside the User controller I have multiple methods for common user-related queries; here I use the username that I have retrieved from the VaadinSession's attribute "username" to execute a query and get the User entity (I make sure that a user's name is unique so getting a single result here is not a problem)
public static User findUserByName(String username) {
if(username == null) return null;
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em = factory.createEntityManager();
User user = null;
try{
Query q = em.createQuery("SELECT u FROM User u WHERE u.username = :username");
q.setParameter("username", username);
user = (User)q.getSingleResult();
}
catch(Exception e){
System.err.println(e.getMessage());
}
finally {
em.close();
}
return user;
}
// Inside the controller fpr my settings view (where I change the password)
public int changePassword(String currentPassword, String newPassword, String newPasswordConfirmation) {
if(newPassword.isEmpty()) return 1;
if(!newPassword.equals(newPasswordConfirmation)) return 2; // Incorrect confirmation
if(!currentPassword.equals(currentUser.getPassword())) return 3; // Given current password doesn't match the one stored in the database for this user
int resStatus = 0;
EntityManager em = factory.createEntityManager();
try {
em.getTransaction().begin();
currentUser.setPassword(newPassword);
em.getTransaction().commit(); // NO ERRORS at all...
}
catch(Exception e) {
System.out.println(e.getMessage());
resStatus = 4; // Exception
}
finally {
em.close();
}
return resStatus;
}
I have also tried using EntityManager.find(...), which should return the same row from the USER table (and it does) but the result is again the same - transaction begins, finishes, entity manager closes but the table USER for the supposedly changed user is the same.
Any ideas? I have used the same routine in another project but for setting other things. The database there was PostreSQL and I haven't encountered such issues. Here with the SQLite database I get no errors but the commit fails somehow.
I just develop with hibernate etc. but it will be the same, because both implements JPA.
If you start a transaction JPA will remember all entities you load in this transaction and if you change something you will see the changes in db (after commit).
But in your transaction JPA don't recognize your entity and so the changes will not persists. Try to load the entity in the transaction. Like...
em.getTransaction().begin();
em.find(User.class, currentUser.getId()); //Reload the User from db, so it is attached to the session
currentUser.setPassword(newPassword);
em.getTransaction().commit();
More Information about the methods:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#refresh(java.lang.Object)
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#merge(T)
http://www.objectdb.com/java/jpa/persistence/managed
I am looking for a way to disable the user instead of deleting them from the system, this is to keep the data integrity of the related data. But seems ASPNET identity only offers Delete Acccount.
There is a new Lockout feature, but it seems to lockout can be controlled to disable user, but only lock the user out after certain number of incorrect password tries.
Any other options?
When you create a site with the Identity bits installed, your site will have a file called "IdentityModels.cs". In this file is a class called ApplicationUser which inherits from IdentityUser.
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://devblogs.microsoft.com/aspnet/customizing-profile-information-in-asp-net-identity-in-vs-2013-templates/ to learn more.
public class ApplicationUser : IdentityUser
There is a nice link in the comments there, for ease click here
This tutorial tells you exactly what you need to do to add custom properties for your user.
And actually, don't even bother looking at the tutorial.
add a property to the ApplicationUser class, eg:
public bool? IsEnabled { get; set; }
add a column with the same name on the AspNetUsers table in your DB.
boom, that's it!
Now in your AccountController, you have a Register action as follows:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, IsEnabled = true };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
I've added the IsEnabled = true on the creation of the ApplicationUser object. The value will now be persisted in your new column in the AspNetUsers table.
You would then need to deal with checking for this value as part of the sign in process, by overriding PasswordSignInAsync in ApplicationSignInManager.
I did it as follows:
public override Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool rememberMe, bool shouldLockout)
{
var user = UserManager.FindByEmailAsync(userName).Result;
if ((user.IsEnabled.HasValue && !user.IsEnabled.Value) || !user.IsEnabled.HasValue)
{
return Task.FromResult<SignInStatus>(SignInStatus.LockedOut);
}
return base.PasswordSignInAsync(userName, password, rememberMe, shouldLockout);
}
Your mileage may vary, and you may not want to return that SignInStatus, but you get the idea.
The default LockoutEnabled property for a User is not the property indicating if a user is currently being locked out or not. It's a property indicating if the user should be subject to lockout or not once the AccessFailedCount reaches the MaxFailedAccessAttemptsBeforeLockout value. Even if the user is locked out, its only a temporary measure to bar the user for the duration of LockedoutEnddateUtc property. So, to permanently disable or suspend a user account, you might want to introduce your own flag property.
You don't need to create a custom property. The trick is to set the
LockoutEnabled property on the Identity user AND set the LockoutoutEndDateUtc to a future date from your code to lockout a user. Then, calling the UserManager.IsLockedOutAsync(user.Id) will return false.
Both the LockoutEnabled and LockoutoutEndDateUtc must meet the criteria of true and future date to lockout a user. If, for example, the LockoutoutEndDateUtc value is 2014-01-01 00:00:00.000 and LockoutEnabled is true, calling theUserManager.IsLockedOutAsync(user.Id) will still return true. I can see why Microsoft designed it this way so you can set a time span on how long a user is locked out.
However, I would argue that it should be if LockoutEnabled is true then user should be locked out if LockoutoutEndDateUtc is NULL OR a future date. That way you don't have to worry in your code about setting two properties (LockoutoutEndDateUtc is NULL by default). You could just set LockoutEnabled to true and if LockoutoutEndDateUtc is NULL the user is locked out indefinitely.
You would need to introduce your own flag into a custom IdentityUser-derived class and implement/enforce your own logic about enable/disable and preventing the user from logging in if disabled.
This all I did actually:
var lockoutEndDate = new DateTime(2999,01,01);
UserManager.SetLockoutEnabled(userId,true);
UserManager.SetLockoutEndDate(userId, lockoutEndDate);
Which is basically to enable lock out (if you don't do this by default already, and then set the Lockout End Date to some distant value.
Ozz is correct, however it may be adviseable to look at the base class and see if you can find a method that is checked for all signin angles - I think it might be CanSignIn?
Now that MS is open source you can see their implementation:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/src/Identity/SignInManager.cs
(Url has changed to:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Core/src/SignInManager.cs)
public class CustomSignInManager : SignInManager<ApplicationUser>
{
public CustomSignInManager(UserManager<ApplicationUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<ApplicationUser>> logger,
IAuthenticationSchemeProvider schemes) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
public override async Task<bool> CanSignInAsync(ApplicationUser user)
{
if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user)))
{
Logger.LogWarning(0, "User {userId} cannot sign in without a confirmed email.", await UserManager.GetUserIdAsync(user));
return false;
}
if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user)))
{
Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user));
return false;
}
if (UserManager.FindByIdAsync(user.Id).Result.IsEnabled == false)
{
Logger.LogWarning(1, "User {userId} cannot sign because it's currently disabled", await UserManager.GetUserIdAsync(user));
return false;
}
return true;
}
}
Also consider overriding PreSignInCheck, which also calls CanSignIn:
protected virtual async Task<SignInResult> PreSignInCheck(TUser user)
{
if (!await CanSignInAsync(user))
{
return SignInResult.NotAllowed;
}
if (await IsLockedOut(user))
{
return await LockedOut(user);
}
return null;
}
You can use these classes... A clean implemantation of ASP.NET Identity...
It's my own code. int is here for primary key if you want different type for primary key you can change it.
IdentityConfig.cs
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<ApplicationContext>()));
manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.UserLockoutEnabledByDefault = false;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
base(userManager, authenticationManager) { }
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, int> store)
: base(store)
{
}
}
public class ApplicationRoleStore : RoleStore<ApplicationRole, int, ApplicationUserRole>
{
public ApplicationRoleStore(ApplicationContext db)
: base(db)
{
}
}
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, int,
ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
public ApplicationUserStore(ApplicationContext db)
: base(db)
{
}
}
IdentityModel.cs
public class ApplicationUser : IdentityUser<int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
//your property
//flag for users state (active, deactive or enabled, disabled)
//set it false to disable users
public bool IsActive { get; set; }
public ApplicationUser()
{
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
public class ApplicationUserRole : IdentityUserRole<int>
{
}
public class ApplicationLogin : IdentityUserLogin<int>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationClaim : IdentityUserClaim<int>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationRole : IdentityRole<int, ApplicationUserRole>
{
public ApplicationRole()
{
}
}
public class ApplicationContext : IdentityDbContext<ApplicationUser, ApplicationRole, int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
//web config connectionStringName DefaultConnection change it if required
public ApplicationContext()
: base("DefaultConnection")
{
Database.SetInitializer<ApplicationContext>(new CreateDatabaseIfNotExists<ApplicationContext>());
}
public static ApplicationContext Create()
{
return new ApplicationContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
}
I upvoted Watson, as there is another public method in SignInManager that accepts TUser user instead of string userName. The accepted answer only suggests overriding the method with the username signature. Both should really be overridden, otherwise there is a means of signing in a disabled user. Here are the two methods in the base implementation:
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
public virtual async Task<SignInResult> PasswordSignInAsync(User user, string password, bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
Overriding CanSignIn seems like a better solution to me, as it gets called by PreSignInCheck, which is called in CheckPasswordSignInAsync. From what I can tell from the source, overriding CanSignIn should cover all scenarios. Here is a simple implementation that could be used:
public override async Task<bool> CanSignInAsync(User user)
{
var canSignIn = user.IsEnabled;
if (canSignIn) {
canSignIn = await base.CanSignInAsync(user);
}
return canSignIn;
}
In asp.net Core Identity v3, a new way of preventing a user from signing in has been added. Previously you could require that an account has a confirmed email address or phone number, now you can specify .RequireConfirmedAccount. The default implementation of the IUserConfirmation<> service will behave the same as requiring a confirmed email address, provide your own service to define what confirmation means.
public class User : IdentityUser<string>{
public bool IsEnabled { get; set; }
}
public class UserConfirmation : IUserConfirmation<User>
{
public Task<bool> IsConfirmedAsync(UserManager<User> manager, User user) =>
Task.FromResult(user.IsEnabled);
}
services.AddScoped<IUserConfirmation<User>, UserConfirmation>();
services.AddIdentity<User, IdentityRole>(options => {
options.SignIn.RequireConfirmedAccount = true;
} );
You need to implement your own UserStore to remove the identity.
Also this might help you.
I've looked through the current literature but I'm struggling to workout exactly how to make the new IdentityStore system work with your own database.
My database's User table is called tblMember an example class below.
public partial class tblMember
{
public int id { get; set; }
public string membership_id { get; set; }
public string password { get; set; }
....other fields
}
currently users login with the membership_id which is unique and then I use the id throughout the system which is the primary key. I cannot use a username scenario for login as its not unique enough on this system.
With the examples I've seen it looks like the system is designed to me quite malleable, but i cannot currently workout how to get the local login to use my tblmember table to authenticate using membership_id and then I will have access the that users tblMember record from any of the controllers via the User property.
http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx
Assuming you are using EF, you should be able to do something like this:
public partial class tblMember : IUserSecret
{
public int id { get; set; }
public string membership_id { get; set; }
public string password { get; set; }
....other fields
/// <summary>
/// Username
/// </summary>
string UserName { get { return membership_id; set { membership_id = value; }
/// <summary>
/// Opaque string to validate the user, i.e. password
/// </summary>
string Secret { get { return password; } set { password = value; } }
}
Basically the local password store is called the IUserSecretStore in the new system. You should be able to plug in your entity type into the AccountController constructor like so assuming you implemented everything correctly:
public AccountController()
{
var db = new IdentityDbContext<User, UserClaim, tblMember, UserLogin, Role, UserRole>();
StoreManager = new IdentityStoreManager(new IdentityStoreContext(db));
}
Note the User property will contain the user's claims, and the NameIdentifier claim will map to the IUser.Id property in the Identity system. That is not directly tied to the IUserSecret which is just a username/secret store. The system models a local password as a local login with providerKey = username, and loginProvider = "Local"
Edit: Adding an example of a Custom User as well
public class CustomUser : User {
public string CustomProperty { get; set; }
}
public class CustomUserContext : IdentityStoreContext {
public CustomUserContext(DbContext db) : base(db) {
Users = new UserStore<CustomUser>(db);
}
}
[TestMethod]
public async Task IdentityStoreManagerWithCustomUserTest() {
var db = new IdentityDbContext<CustomUser, UserClaim, UserSecret, UserLogin, Role, UserRole>();
var manager = new IdentityStoreManager(new CustomUserContext(db));
var user = new CustomUser() { UserName = "Custom", CustomProperty = "Foo" };
string pwd = "password";
UnitTestHelper.IsSuccess(await manager.CreateLocalUserAsync(user, pwd));
Assert.IsTrue(await manager.ValidateLocalLoginAsync(user.UserName, pwd));
CustomUser fetch = await manager.Context.Users.FindAsync(user.Id) as CustomUser;
Assert.IsNotNull(fetch);
Assert.AreEqual("Custom", fetch.UserName);
Assert.AreEqual("Foo", fetch.CustomProperty);
}
EDIT #2: There's also a bug in the implementation of IdentityAuthenticationmanager.GetUserClaims that is casting to User instead of IUser, so custom users that are not extending from User will not work.
Here's the code that you can use to override:
internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
internal const string DefaultIdentityProviderClaimValue = "ASP.NET Identity";
/// <summary>
/// Return the claims for a user, which will contain the UserIdClaimType, UserNameClaimType, a claim representing each Role
/// and any claims specified in the UserClaims
/// </summary>
public override async Task<IList<Claim>> GetUserIdentityClaims(string userId, IEnumerable<Claim> claims) {
List<Claim> newClaims = new List<Claim>();
User user = await StoreManager.Context.Users.Find(userId) as IUser;
if (user != null) {
bool foundIdentityProviderClaim = false;
if (claims != null) {
// Strip out any existing name/nameid claims that may have already been set by external identities
foreach (var c in claims) {
if (!foundIdentityProviderClaim && c.Type == IdentityProviderClaimType) {
foundIdentityProviderClaim = true;
}
if (c.Type != ClaimTypes.Name &&
c.Type != ClaimTypes.NameIdentifier) {
newClaims.Add(c);
}
}
}
newClaims.Add(new Claim(UserIdClaimType, userId, ClaimValueTypes.String, ClaimsIssuer));
newClaims.Add(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String, ClaimsIssuer));
if (!foundIdentityProviderClaim) {
newClaims.Add(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String, ClaimsIssuer));
}
var roles = await StoreManager.Context.Roles.GetRolesForUser(userId);
foreach (string role in roles) {
newClaims.Add(new Claim(RoleClaimType, role, ClaimValueTypes.String, ClaimsIssuer));
}
IEnumerable<IUserClaim> userClaims = await StoreManager.Context.UserClaims.GetUserClaims(userId);
foreach (IUserClaim uc in userClaims) {
newClaims.Add(new Claim(uc.ClaimType, uc.ClaimValue, ClaimValueTypes.String, ClaimsIssuer));
}
}
return newClaims;
}
I am trying to teach my self MVC3 and EF4 using code first and the DbContext generator, so forgive me if this is a silly question.
Basically I have a user class, and an email class; this is because i want each user to be able to have multiple email addresses. the classes are set up like this:
public class User
{
[Key]
public int Id { get; set; }
public string User_Name { get; set; }
public string Password { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public virtual ICollection<Email> Emails { get; set; }
}
public class Email
{
[Key]
public int Id { get; set; }
public string Address { get; set; }
public User User { get; set; }
}
I am happily manipulating the user class using the CRUD methods build by MVC3 and inserting users programmatically to "seed" the db with test data, the latter i am doing like so by overriding the Seed method in the DropCreateDatabaseAlways class like so:
public class dbInitializer : DropCreateDatabaseAlways<UserContext>
{
protected override void Seed(UserContext context)
{
var Users = new List<User>
{
new User { User_Name = "uname",
Password = "pword",
First_Name = "fname",
Last_Name = "sname",
}
};
Users.ForEach(u => context.Users.Add(u));
}
}
Now i would also like to add so email addresses, and because of the way i set up my classes code first obviously realises that each user can have multiple email addresses and each email addresses can belong only to one user because when creating a new user or email object the intellisense (VS10) presents me with Emails and User properties that are not actually part of either class.
My question is this: How do i add an email address to a user as its created, and how do i add an email address to a user that has been created previously?
Add emails to user as it is created:
protected override void Seed(UserContext context)
{
var Users = new List<User>
{
new User { User_Name = "uname",
Password = "pword",
First_Name = "fname",
Last_Name = "sname",
Emails = new[]
{
new Email { Address = "email1#domain.tld" },
new Email { Address = "email2#domain.tld" },
}
}
};
Users.ForEach(u => context.Users.Add(u));
}
To add an email to a previously created user, you first need a reference to the user:
var user = Users.First();
user.Emails.Add(new Email { Address = "email3#domain.tld" });
Reply to comments:
new[] is shorthand for new Email[] (new Email array).
Technically Eranga's answer is a little more flexible. In mine, since arrays are fixed length, you can't add an Email to the Emails collection after it has been initialized as an array. You would need to either use List<Email> as in Eranga's answer, or convert to a list like so before invoking .Add():
user.Emails.ToList().Add(new Email { Address = "email3#domain.tld" });
I prefer arrays when possible because they are easier to type, less verbose in the code, and I generally add everything in the object initializer, so I don't need to add it again later.
That said, after you save the User and get it back out of the db, your Emails collection property will not be a fixed-length array, and can be added to without having to invoke .ToList().
Adding Email to a new user
protected override void Seed(UserContext context)
{
var Users = new List<User>
{
new User { User_Name = "uname",
Password = "pword",
First_Name = "fname",
Last_Name = "sname",
Emails = new List<Email> { new Email { Addess = "foo#bar.baz" } }
}
};
Users.ForEach(u => context.Users.Add(u));
}
Similarly for existing user.
user.Emails.Add(new Email { Addess = "foo#bar.baz" });
user.Emails.Add will issue a database request to load the Email collection (ie. Lazy Loading). Alternate way to do this is
var email = new Email { Addess = "foo#bar.baz", User = user };
context.Emails.Add(email);