Updating user role using asp.net identity - asp.net

I have the following problem. While using the following code below to change the user's current role i am getting an exception with the message like below:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(User user, string role)
{
if (ModelState.IsValid)
{
var oldUser = DB.Users.SingleOrDefault(u => u.Id == user.Id);
var oldRoleId = oldUser.Roles.SingleOrDefault().RoleId;
var oldRoleName = DB.Roles.SingleOrDefault(r => r.Id == oldRoleId).Name;
if (oldRoleName != role)
{
Manager.RemoveFromRole(user.Id, oldRoleName);
Manager.AddToRole(user.Id, role);
}
DB.Entry(user).State = EntityState.Modified;
return RedirectToAction(MVC.User.Index());
}
return View(user);
}
Attaching an entity of type 'Models.Entities.User' 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.
Does anybody know a good solution to this problem ?

The problem is that your Manager and DB doesn't use the same DbContext. So when you send an user from the context of your DB to the Manager it will handle it as a "new" one - and then you cant remove it from the role. You have two ways to go here. The easiest is to get the User from your Manager.
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(User user, string role)
{
if (ModelState.IsValid)
{
// THIS LINE IS IMPORTANT
var oldUser = Manager.FindById(user.Id);
var oldRoleId = oldUser.Roles.SingleOrDefault().RoleId;
var oldRoleName = DB.Roles.SingleOrDefault(r => r.Id == oldRoleId).Name;
if (oldRoleName != role)
{
Manager.RemoveFromRole(user.Id, oldRoleName);
Manager.AddToRole(user.Id, role);
}
DB.Entry(user).State = EntityState.Modified;
return RedirectToAction(MVC.User.Index());
}
return View(user);
}
The more elegant way is to start using an DI-framework like AutoFac (https://code.google.com/p/autofac/wiki/MvcIntegration) and set your DbContext as InstancePerApiRequest.
builder.RegisterType<YourDbContext>().As<DbContext>().InstancePerApiRequest();

My roles are managed in the seed method of my DbMigrationsConfiguration class and I renamed it like this:
if (context.Roles.Any(r => r.Name == "User"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = manager.Roles.First(r => r.Name == "User");
role.Name = "NewNameForUser";
manager.Update(role);
}

In Identity Version 6.0.8
It's work for me.
if (role != currentrole)
{
_context.UserRoles.Remove(userIdData);
_context.SaveChanges();
userIdData.RoleId = aspnetroleId.Single();
userIdData.UserId = userId;
_context.UserRoles.Add(userIdData);
_context.SaveChanges();
}

Related

How to update the extended UserRole of Identity User Role in .NET Core 2

We have an extended of Identity User (ApplicationUser), Role (ApplicationRole) and UserRole (ApplicationUserRole).
We managed to save changes for ApplicationUser BUT NOT be able to save changes on ApplicationUserRole using the following code:
public async Task<IActionResult> OnPostAsync(Guid id)
{
if (!ModelState.IsValid)
{
return Page();
}
var userToUpdate = await _context.Users
.FirstOrDefaultAsync(m => m.Id == id.ToString());
var userRoleToUpdate = await _context.UserRoles
.FirstOrDefaultAsync(m => m.UserId == id.ToString());
// null means User was deleted by another user.
if (userToUpdate == null || userRoleToUpdate == null)
{
return await HandleDeletedUser();
}
// Update the RowVersion to the value when this entity was
// fetched. If the entity has been updated after it was
// fetched, RowVersion won't match the DB RowVersion and
// a DbUpdateConcurrencyException is thrown.
// A second postback will make them match, unless a new
// concurrency issue happens.
_context.Entry(userToUpdate)
.Property("RowVersion").OriginalValue = User.RowVersion;
if (await TryUpdateModelAsync<ApplicationUser>(
userToUpdate,
"User",
s => s.FirstName, s => s.LastName, s => s.Email))
{
try
{
await _context.SaveChangesAsync();
if (await TryUpdateModelAsync<ApplicationUserRole>(
userRoleToUpdate,
"UserRole",
s => s.RoleId.ToString()))
{
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (ApplicationUser)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The user was deleted by another user.");
return Page();
}
var dbValues = (ApplicationUser)databaseEntry.ToObject();
await setDbErrorMessage(dbValues, clientValues, _context);
// Save the current RowVersion so next postback
// matches unless an new concurrency issue happens.
User.RowVersion = (byte[])dbValues.RowVersion;
// Must clear the model error for the next postback.
ModelState.Remove("User.RowVersion");
}
}
return Page();
}
Any pointer for this? I assumed that we need to break up the save changes. Do the User first and then UserRole, doesn't it?
Also, we didn't implement the RowVersion for ApplicationUserRole which I believe is too overkill.
Environment:
.Net Core 2.1
UPDATE - 1:
Exception -
InvalidOperationException: The passed expression of expression node type 'Call' is invalid. Only simple member access expressions for model properties are supported.
UPDATE- 2:
I've changed to this and no error BUT no changes on Roles:
if (await TryUpdateModelAsync<ApplicationUserRole>(
userRoleToUpdate,
"UserRole",
ur => ur.UserId, ur => ur.RoleId))
{
await _context.SaveChangesAsync();
}
How do we map work in relation to updating this RoleId from the dropdown list as on the edit form (Edit.html) ?!?!
<div class="form-group">
Role:
<select asp-for="User.UserRoles.ElementAt(0).RoleId" class="form-control"
asp-items="#Model.RoleNameSL">
<option value="">-- Select Role --</option>
</select>
<span asp-validation-for="User.UserRoles.ElementAt(0).Role" class="text-danger"></span>
</div>
UPDATE - 3:
This is quite interesting. I changed the text from "UserRole" to "":
if (await TryUpdateModelAsync<ApplicationUserRole>(
userRoleToUpdate,
"",
ur => ur.RoleId))
{
await _context.SaveChangesAsync();
}
and now we have an error saying which I think this is getting close:
InvalidOperationException: The property 'RoleId' on entity type
'ApplicationUserRole' is part of a key and so cannot be modified or
marked as modified. To change the principal of an existing entity with
an identifying foreign key first delete the dependent and invoke
'SaveChanges' then associate the dependent with the new principal.
Is it possible because we have this in our ApplicationDBContext ? In that case, what is the best way to update this then?
protected override void OnModelCreating(ModelBuilder builder)
{
....
// Many to Many relationship - ApplicationUserRole
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
If you are using AspNetIdentity, which it looks like you are, you should be able to update the role using UserManager. UserManager is a part of Identity and is intended for managing the Identity of a user. As long as your roles are AspNetRoles then injecting UserManager will let you update any user to any predefined role.
Adding
private readonly UserManager<ApplicationUser> _userManager;
public Controller(UserManager<ApplicationUser> userManger, ApplicationUser applicationUser)
{
_userManager = userManger;
}
public async Task UpDateRole(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.AddToRoleAsync(userId, "RoleName");
}
should do what you want, you might also want to remove that user from the role they were previously in.

User.Identity.Name is always null when using AspNetIdentity with IdentityServer3

I've been trying to setup a new IdentityServer3 with AspNetIdentity for a few days now. I'm able to login using my existing Identity DB and that's all good but I can never get the User.Identity.Name to contain data.
I've tried multiple attempts at adding custom claims & scopes and adding scopes to clients.
Finally, I loaded up the IdentityServer3 Sample repository and tested it out with the webforms client project since it already used the User.Identity.Name in it's About page.
Using WebForms sample client + AspNetIdentity sample server = User.Identity.Name is always null
Using WebForms sample client + SelfHost with Seq sample server = User.Identity.Name with data
I've tried other sample host projects that all populate the User.Identity.Name value just fine.
Now, on the client side I've written a workaround to pull the 'preferred_username' claim value and set the 'name' claim with it.
var id = new claimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
//set the User.Identity.Name value
var name = id.Claims.Where(x => x.Type == "name").Select(x => x.Value).FirstOrDefault() ??
id.Claims.Where(x => x.Type == "preferred_username").Select(x => x.Value).FirstOrDefault();
id.AddClaim(new Claim("name", name));
My questions are:
Why doesn't the AspNetIdentity package fill this by default?
And what do I need to change on the server side so that I don't need to change the client?
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("MyApi", "My Admin API")
{
UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email }
}
};
}
In Identityserver4 you can add the UserClaims to your resource. Fixed it for me.
On IdentityServer4 you can implement IProfileService on server and add the Claim in GetProfileDataAsync
public class AspNetIdentityProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
};
context.IssuedClaims.AddRange(claims);
//Return
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
context.IsActive = (user != null) && ((!user.LockoutEnd.HasValue) || (user.LockoutEnd.Value <= DateTime.Now));
//Return
return Task.FromResult(0);
}
}
Then add "AddProfileService()" to your ConfigureServices method.
services.AddIdentityServer(...)
...
.AddProfileService<AspNetIdentityProfileService>();

Insert Data Into another table during Registration in ASP.NET MVC5

I understand that ASP.NET MVC creates a default table "AspNetUser" for storing users information during registration. How do I get Some of those data such as "Username" and store it in a seperate table during the registration process.
Am trying to get the "username" supplied by the user which is by default stored in the "AspNetUser Table" and store it in the "Student Table" also during the registration process
Register action
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
StudentEntities db = new StudentEntities();
var student = new Student
{
Name = model.Username
};
db.Student.Add(Student);
db.SaveChanges();
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Welcome", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This doesn't seem to work
This doesn't seem to work
StudentEntities db = new StudentEntities();
var student = new Student
{
Name = model.Username
};
db.Student.Add(Student);
db.SaveChanges();
If you are using MVC5, it means you are using identity2. So It is better to use that to get user details. The best approach is doing this:
Add the following using statement.
using Microsoft.AspNet.Identity;
You can now get more methods from the HttpContext.User.Identity.
var UserName = User.Identity.GetUserName();
var UserId = User.Identity.GetUserId();
Be noted that identity usually uses email as username. But you can change it anyway.

How do I access Microsoft.Owin.Security.xyz OnAuthenticated context AddClaims values?

I'm trying to retrieve user properties that are returned as the OnAuthenticated context and added as a claims following this example: How to access Facebook private information by using ASP.NET Identity (OWIN)?
I can see that data I am expecting is being returned at login and is being added as a Claim within Starup.Auth.cs. But, when I am within the Account Controller, the only claims that appears within the UserManager or UserStore is issued by LOCAL AUTHORITY. No claims can be found for Facebook (or other external providers). Where do the claims added to context end up? (I'm using VS2013 RTM.)
Full source and live site on Azure linked here: https://github.com/johndpalm/IdentityUserPropertiesSample/tree/VS2013rtm
Here is what I have in Startup.Auth.cs:
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
foreach (var x in context.User)
{
var claimType = string.Format("urn:facebook:{0}", x.Key);
string claimValue = x.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));
}
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
return Task.FromResult(0);
}
}
};
facebookOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookOptions);
An alternative way to capture the external login properties would be to add a single claim for the access token and populate it with properties:
const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
var claim = new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook");
foreach (var x in context.User)
{
string key = string.Format("urn:facebook:{0}", x.Key);
string value = x.Value.ToString();
claim.Properties.Add(key, value);
}
context.Identity.AddClaim(claim);
return Task.FromResult(0);
}
}
};
NOTE - This sample does not work: Though it would be nice to pass a single claim with properties. The external cookie seems to note honor the claims properties. The properties are empty when retrieving them later from the identity.
I was able to create a working example, using MVC 5 RTM templates, OWIN, and ASP.NET Identity bits. You can find the complete source and a link to a live working example here: https://github.com/johndpalm/IdentityUserPropertiesSample
Here's what worked for me:
Create a new (insert provider name here) AuthenticationOptions object in Startup.ConfigureAuth (StartupAuth.cs), passing it the client id, client secret, and a new AuthenticationProvider. You will use a lambda expression to pass the OnAuthenticated method some code to add Claims to the identity which contain the values you extract from context.Identity.
StartUp.Auth.cs
// Facebook : Create New App
// https://dev.twitter.com/apps
if (ConfigurationManager.AppSettings.Get("FacebookAppId").Length > 0)
{
var facebookOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions()
{
AppId = ConfigurationManager.AppSettings.Get("FacebookAppId"),
AppSecret = ConfigurationManager.AppSettings.Get("FacebookAppSecret"),
Provider = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, XmlSchemaString, "Facebook"));
foreach (var x in context.User)
{
var claimType = string.Format("urn:facebook:{0}", x.Key);
string claimValue = x.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));
}
return Task.FromResult(0);
}
}
};
app.UseFacebookAuthentication(facebookOptions);
}
NOTE: The Facebook auth provider works with the code used here. If you use this same code with the Microsoft Account provider (or Foursquare provider I created using the MS account code as a model), it fails to login. If you select just the access_token parameter, it works fine. Seems like some parameters break the login process. (An issue has been opened on katanaproject.codeplex.com if progress on this is of interest to you.) I'll update if I find the cause. I didn't do much with Twitter or Google beyond verifying that I could get the access_token.
var msaccountOptions = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions()
{
ClientId = ConfigurationManager.AppSettings.Get("MicrosoftClientId"),
ClientSecret = ConfigurationManager.AppSettings.Get("MicrosoftClientSecret"),
Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:microsoftaccount:access_token", context.AccessToken, XmlSchemaString, "Microsoft"));
return Task.FromResult(0);
}
}
};
app.UseMicrosoftAccountAuthentication(msaccountOptions);
In AccountController, I extract the ClaimsIdentity from the AuthenticationManager using the external cookie. I then add it to the identity created using the application cookie. I ignored any claims that starts with "...schemas.xmlsoap.org/ws/2005/05/identity/claims" since it seemed to break the login.
AccountController.cs
private async Task SignInAsync(CustomUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Extracted the part that has been changed in SignInAsync for clarity.
await SetExternalProperties(identity);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
private async Task SetExternalProperties(ClaimsIdentity identity)
{
// get external claims captured in Startup.ConfigureAuth
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (ext != null)
{
var ignoreClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims";
// add external claims to identity
foreach (var c in ext.Claims)
{
if (!c.Type.StartsWith(ignoreClaim))
if (!identity.HasClaim(c.Type, c.Value))
identity.AddClaim(c);
}
}
}
And finally, I want to display whatever values are not from the LOCAL AUTHORITY. I created a partial view _ExternalUserPropertiesListPartial that appears on the /Account/Manage page. I get the claims I previously stored from AuthenticationManager.User.Claims and then pass it to the view.
AccountController.cs
[ChildActionOnly]
public ActionResult ExternalUserPropertiesList()
{
var extList = GetExternalProperties();
return (ActionResult)PartialView("_ExternalUserPropertiesListPartial", extList);
}
private List<ExtPropertyViewModel> GetExternalProperties()
{
var claimlist = from claims in AuthenticationManager.User.Claims
where claims.Issuer != "LOCAL AUTHORITY"
select new ExtPropertyViewModel
{
Issuer = claims.Issuer,
Type = claims.Type,
Value = claims.Value
};
return claimlist.ToList<ExtPropertyViewModel>();
}
And just to be thorough, the view:
_ExternalUserPropertiesListPartial.cshtml
#model IEnumerable<MySample.Models.ExtPropertyViewModel>
#if (Model != null)
{
<legend>External User Properties</legend>
<table class="table">
<tbody>
#foreach (var claim in Model)
{
<tr>
<td>#claim.Issuer</td>
<td>#claim.Type</td>
<td>#claim.Value</td>
</tr>
}
</tbody>
</table>
}
Again, the working example and complete code is on GitHub: https://github.com/johndpalm/IdentityUserPropertiesSample
And any feedback, corrections, or improvements would be appreciated.
So this article explains how this all works pretty well: Decoupling owin external auth
But the short answer is, when you get authenticated from facebook, that is giving you an external identity. You then need to take that external identity and 'sign in' a local app identity, its in that stepthat you need to add any claims you want from the external identity to the ClaimsIdentity that becomes User.Identity.
Edit: To clarify further, you could do it inside of ExternalLoginCallback:
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl) {
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null) {
return RedirectToAction("Login");
}
// Sign in this external identity if its already linked
var user = await UserManager.FindAsync(loginInfo.Login);
if (user != null) {
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent) {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
So you will need to pass in extra data to the SignIn, which will look something like this:
ClaimsIdentity id = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
This ClaimsIdentity will have your added claim, and you will need to add that claim to the identity created in the SignInAsync method for it to show up.
In short the line that is required once AddClaim is used is as follows:
Taken from johns answer above.
ClaimsIdentity ext = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

MVC5 ApplicationUser custom properties

I am trying to get to grips with the new Membership system introduced in ASP.NET MVC 5 and I've come across a small issue which I am pretty sure you will be able to help me with.
I am going based off this tutorial and have introduced custom properties to ApplicationUser such as Name, Surname, DOB, etc.
However, instead of creating the user, I am trying to update the currently logged in one. I am looking at the controller method which is currently used to change password.
public async Task<ActionResult> Manage(ManageUserViewModel model)
{
string userId = User.Identity.GetUserId();
bool hasLocalLogin = await IdentityManager.Logins.HasLocalLoginAsync(userId);
ViewBag.HasLocalPassword = hasLocalLogin;
ViewBag.ReturnUrl = Url.Action("Manage");
if (hasLocalLogin)
{
if (ModelState.IsValid)
{
IdentityResult result = await IdentityManager.Passwords.ChangePasswordAsync(User.Identity.GetUserName(), model.OldPassword, model.NewPassword);
if (result.Success)
{
return RedirectToAction("Manage", new { Message = "Your password has been changed." });
}
else
{
AddErrors(result);
}
}
}
else
{
// User does not have a local password so remove any validation errors caused by a missing OldPassword field
ModelState state = ModelState["OldPassword"];
if (state != null)
{
state.Errors.Clear();
}
if (ModelState.IsValid)
{
// Create the local login info and link it to the user
IdentityResult result = await IdentityManager.Logins.AddLocalLoginAsync(userId, User.Identity.GetUserName(), model.NewPassword);
if (result.Success)
{
return RedirectToAction("Manage", new { Message = "Your password has been set." });
}
else
{
AddErrors(result);
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
How exactly would I go on about updating an ApplicationUser's Surname for example? Do I need to call the DbContext or?
I hope my question is clear.
Explore IdentityManager.Store.UserManagement and IdentityManager.Store.Users.
ApplicationUser cUser = (ApplicationUser) await IdentityManager.Store.Users.FindByNameAsync(HttpContext.User.Identity.Name, new System.Threading.CancellationToken());
cUser.Surname = "New Something";
IdentityResult result1 = await IdentityManager.Store.SaveChangesAsync();
Above code is an example only. Basically you need to explore the Store property of IdentityManage.
When we used the Users object of our database context we ran into other tracking errors. In our application, we would retrieve users as such
var user = UserManager.FindById(userId);
Edit the properties:
user.StorageName = "gooblygook";
//whatever other properties you would like to use
And then we would save it with the UserManager in the controller:
UserManager.Update(user);
This is currently a working solution for us.
Mark the Person object as virtual in your ApplicationUser definition. That worked for me.
public class ApplicationUser : IdentityUser
{
public virtual Person Person { get; set; }

Resources