Prevent collision on multiple database calls in Web API - c#

I have a table of random unique number that they served as account Id for a user.
This scenario will use in Register method of Web API to get first unique Number and attached it into a user and after a successful creation, the fetched unique Number will be removed from database:
public async Task<ActionResult> Register([FromBody] RegisterDto model)
{
//get the first unique number from the database
var UniqueNumber = _context.UniqueNumbers.First();
var user = new User
{
UserName = model.Email,
Email = model.Email,
PhoneNumber = model.PhoneNumber,
FirstName = model.FirstName,
LastName = model.LastName,
UserProfile = new UserProfile()
{
AccountNumber = UniqueNumber.Number,
},
};
//creating user
var createResult = await _userManager.CreateAsync(user, model.Password);
if (!createResult.Succeeded) return BadRequest(new { isSucceeded = createResult.Succeeded, errors = createResult.Errors });
//Delete the fetched UniqueId from the database
_context.UniqueNumbers.Remove(UniqueNumber);
_context.SaveChanges();
return Ok(new
{
isSucceeded = true
});
}
My question is how do I prevent collision in multiple calls on API since it may return same unique number for multiple calls?

On EF Core and Microsoft SQL Server you should simply use a SEQUENCE object to generate your keys. See Sequences - EF Core
If really, really want to proceed with your original design you could use FromSql to run a DELETE … OUTPUT, something like:
var UniqueNumber = _context.UniqueNumbers.FromSql("delete top (1) from UniqueNumbers output deleted.*").Single();

Related

Asp.NET Identity Roles not working when attempting to add Role to User

I am currently adding Roles to our Database using the RoleManager with the CreateAsync(newRoleName) Method - which works correctly. But when I try to query that Role, it always returns that it doesn't exist (even though I can see it in the database).
Can anyone provide some insight on why I am not able to use the Role?
var roleExists = roleManager.RoleExistsAsync(role);
if (!roleExists.Result)
{
var newRole = new IdentityRole(role)
{
Name = role,
NormalizedName = role.ToUpper(),
};
var roleCreated = roleManager.CreateAsync(newRole);
Thread.Sleep(500); // Used to get result back first.
var roleExistsYet = roleManager.RoleExistsAsync(role);
if (!roleExists.Result)
{
// ALWAYS Returns [False]
}
}
The initial problem came about when we were creating a new User with the UserManager, and the following method would result in an error
var roleAddResult = userManager.AddToRoleAsync(newUser, "TestRole123");
Exception Error: Role [TESTROLE123] does not exist.
Note: I can see the entry for the Role 'TestRole123' (or any other role) in the Database in the table dbo.AspNetRoles.
Any insight or help is appreciated.
Environment: Visual Studio 2017, Asp.NET Core, C#
One of the issues I see - you need to use keyword await in front of *Async() methods:
var roleExists = await roleManager.RoleExistsAsync(role);
and
var roleCreated = await roleManager.CreateAsync(newRole);
etc. This will remove your need to do Thread.Sleep(500); - most likely the problem is with this line.
If you can't do async methods, use non-async versions of the methods:
var roleCreated = roleManager.Create(newRole);
I don't know how you declared your rolemanager, but following code works for me. It's configured in startup.cs and automatically creates a superuser if the roles haven't been created. Perhaps this can help you?
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
if (!roleManager.RoleExists("SuperUser"))
{
roleManager.Create(new IdentityRole("SuperUser"));
//superuser
var user = new ApplicationUser
{
UserName = " Name",
Email = "Email",
Firstname = "Firstname",
Lastname = "Lastname"
};
var pass = "AwesomePasswordOverHere";
var chkUser = await userManager.CreateAsync(user, pass);
//make superuser
if (chkUser.Succeeded)
{
await userManager.AddToRoleAsync(user.Id, "SuperUser");
}
}

userManager.AddToRoleAsync() - Error: role does not exist

I'm creating a user registration system using .NET Core, Identity Core, and MVC Core. I'm able to create users and create roles in the database.
Here's the form on the view that lets me select a user and select a role to add:
#using (Html.BeginForm("AddRoleToUser", "Roles"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<p>
Username : #Html.DropDownList("UserName", (IEnumerable<SelectListItem>)ViewBag.Users, "Select ...")
Role Name: #Html.DropDownList("RoleName", (IEnumerable<SelectListItem>)ViewBag.Roles, "Select ...")
</p>
<input type="submit" value="Save" />
}
These drop-down lists are populated with users and roles that already exist in the database. They allow me to select Users , and the name of a role that I've already created. For example, I have a role with the name "admin", this form lets me select the string "admin".
Here's the action that handles adding a role to a user:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddRoleToUser(string UserName, string RoleName)
{
try
{
ApplicationUser user = _db.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
await _userManager.AddToRoleAsync(user, RoleName);
PrepopulateDropDownMenus();
ViewBag.ResultMessage = "Role created successfully!";
return View("Manage", "Roles");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return View("Manage");
}
}
The action never adds the role to the user, and the exception reads "Role "ADMIN" does not exist." with no inner exception. I've tried turning the RoleName in the action parameters to all-caps, but it still does not find the role. I've also tried using the role ID instead of the name, which was also unsuccessful.
This exact code worked when I built this app using Identity 3.0 with MVC 6. It seems like something has changed in moving over to Identity Core.
Any thoughts?
Edit
Here's the code I'm using to populate the drop-down lists in RolesController via the Viewbag:
private void PrepopulateDropDownMenus()
{
var rolesList = _db.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
var usersList = _db.Users.OrderBy(u => u.UserName).ToList().Select(uu => new SelectListItem { Value = uu.UserName.ToString(), Text = uu.UserName }).ToList();
ViewBag.Roles = rolesList;
ViewBag.Users = usersList;
}
Here's how I add Identity in Startup.cs in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddEntityFramework()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
}
Here's the route in RolesController.cs I use to create a new role in the database:
[HttpPost]
public IActionResult Create(string rolename)
{
_db.Roles.Add(new IdentityRole()
{
Name = rolename
});
_db.SaveChanges();
ViewBag.ResultMessage = "Role created successfully!";
return RedirectToAction("Index");
}
I can't post comments yet to ask you, so, does your error say user admin does not exist, or role does not exist? I tried to duplicate your code on my end, and if the user doesn't exist you'll get a "user can't be null" error. However, if the role doesn't exist, you'll get a "Role [role] does not exist" error.
I assume that you already have the role added into your database? Here is some code I used in my seed method that essentially does what you want, minus using the views to do it:
// Add the Admin role to the database
IdentityResult roleResult;
bool adminRoleExists = await _roleManager.RoleExistsAsync("Admin");
if (!adminRoleExists)
{
_logger.LogInformation("Adding Admin role");
roleResult = await _roleManager.CreateAsync(new IdentityRole("Admin"));
}
// Select the user, and then add the admin role to the user
ApplicationUser user = await _userManager.FindByNameAsync("sysadmin");
if (!await _userManager.IsInRoleAsync(user, "Admin"))
{
_logger.LogInformation("Adding sysadmin to Admin role");
var userResult = await _userManager.AddToRoleAsync(user, "Admin");
}
EDIT
The way you're adding roles right now leaves the NormalizedName field in the Role table null, which I believe is used by the framework for adding roles to users. Try one of the following to add a role to the database instead of what you're currently doing:
var result = await _roleManager.CreateAsync(new IdentityRole(rolename));
Or this may also work (haven't tested this one though):
[HttpPost]
public IActionResult Create(string rolename)
{
_db.Roles.Add(new IdentityRole()
{
Name = rolename,
NormalizedName = rolename.ToUpper()
});
_db.SaveChanges();
ViewBag.ResultMessage = "Role created successfully!";
return RedirectToAction("Index");
}
Make sure when you are creating an AspNetRole the NormalizedName should not be null in order for the UserManager to work properly.
NormalizedName should be upper-case using .ToUpper()
Adding it straight to the database is a bad idea and violates every concept of encapsulation, and NormalizedName is not something you should be computing yourself.
See this answer
Replace this code:
var roleStore = new RoleStore<IdentityRole>(context);
await roleStore.CreateAsync(new IdentityRole(role));
with following:
var roleManager = services.GetService<RoleManager<IdentityRole>>();
await roleManager.CreateAsync(new IdentityRole(role));
This is the thing: if you are using major versions of identity (for example 6.0), the table AspNetRoles contains a Column named NormalizedName, if the string is null or differes from the role name written in capital letters you wont't be able to find the role name. You may insert manually (MMSMI) the string or delete per procedure all roles and recreate them.

EntityFrameworks inserts already inserted related item

Im trying to create a user, then connect that user to an model called Event.
var user = new ApplicationUser { name = "test", email = "test#test.com" };
var userResult = UserManager.Create(user, "password");
if (userResult.Succeeded)
{
SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: true);
#event.CreatedBy = user;
#event.CreatedById = new Guid(user.Id);
db.Events.Add(#event);
db.SaveChanges();
}
But db.SaveChanges() throws an validation error on the ApplicationUser model: user with the email already exists: test#test.com (even though it didnt exists before this request)
So it would seem as EF is trying to reinserted the already inserted user.
The Event model have these setters/getters
public virtual ApplicationUser CreatedBy { get; set; }
public virtual Guid CreatedById { get; set; }
And it works great if i would get the current singned in user instead of signing up a new one.
I have tried only to set #event.CreatedById and skip #event.CreatedBy, but then EF doesnt save the reference to the database.
This line creates a new user (and probably saves it to your database):
var userResult = UserManager.Create(user, "password");
So you don't have the result you got back from the database (return value of the Add method), which is a proxy that EF knows exists in database. In fact it's a link between the database record and the object you have in memory. Then you have the following lines:
#event.CreatedBy = user;
db.Events.Add(#event);
db.SaveChanges();
You assign the user (which is a simple poco, no connection to EF whatsoever) to your event. When you add the event to the database, EF will try to create a new user again because it's not know yet, but then of course you get an error. You will have to get the user you created from the database and assign that to your event. If you would be able to change your method UserManager.Create so it also accepts a DbContext, you could pass the context, create the user and when you need the user you can use the same context again and use the Find method (which looks for objects in the context in memory, no db call) to find the user you've added.
Edit: Maybe the most simple solution in your case is to attach the user to your context:
var user = new ApplicationUser { name = "test", email = "test#test.com" };
var userResult = UserManager.Create(user, "password");
if (userResult.Succeeded)
{
SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: true);
db.Users.Attach(user);
#event.CreatedBy = user;
#event.CreatedById = new Guid(user.Id);
db.Events.Add(#event);
db.SaveChanges();
}
This basically tells EF that you know the object already exists in the database.

ASP.NET 5 MVC 6 and Entity Framework 7 issues

We can't add Owin and Entity framework 7 together. As we do so then there will be ambiguity between Microsoft.AspNet.Identity.core 2.0.0.0 and Microsoft.AspNet.Identity 3.0.0 Beta1
And hence I am not able to implement role provider in my application to manage the user roles.
After facing this issue I removed Owin references and created UserManager using
Microsoft.AspNet.Identity 3.0.0 and EF 7 but UserManager.AddToRoleAsync(user, roleName) always throws exception as below:-
InvalidOperationException: The instance of entity type 'Mozaics.DAL.Models.ApplicationUser' cannot be tracked because another
instance of this type with the same key is already being tracked. For
new entities consider using an IIdentityGenerator to generate unique
key values.
Code snippet is like this.
public async Task<ActionResult> RoleAddToUser(string UserName, string RoleName)
{
var user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var result = await UserManager.AddToRoleAsync(user, RoleName );
ViewBag.ResultMessage = "Role created successfully !";
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("ManageUserRoles");
}
#sanjaypujari
Just try that one:
var user = await context.FindByNameAsync(UserName);
ApplicationUser appuser = (ApplicationUser)user;
var result = await UserManager.AddToRoleAsync(appuser, RoleName);
When executed like this, ApplicationUser shouldn't be tracked twice.
Same thing helps, too, if you want to update a ApplicationUser.
I run to this issue. One solution that worked for me was that getting the user via the UserManager and then adding the role:
instead of
var user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var result = await UserManager.AddToRoleAsync(user, RoleName );
use the following
var user = await UserManager.FindByNameAsync(UserName);
var result = await UserManager.AddToRoleAsync(user, RoleName );

How to create a user and get the newly created ID with ASP.NET Identity

I am new to the ASP.NET Identity framework and am trying to do some things that I used to do in the older FormsAuthentication framework.
What I want to do is allow an administrative user to create a new user using the existing Register view (or similar) from within the app. Once this is complete, I would like to relationally associate that user (possibly using the ID that is generated) to other areas of the system.
How do I get access to the ID that is generated when calling UserManager.CreateAsync()?
EDIT: I am wanting existing users with "administrative" roles to create users from within the system in a User Management area. The answers below so far have explained how to get the ID for the "current" user which is not what I am looking for.
Using the IdentityUser or using a class that inherits from IdentityUser, makes the model having an UserId attribute. Using following code, passing the user to the method, will fill up the Id.
var user = model.GetUser();
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
result = UserManager.AddToRole(user.Id, "User");
The model.GetUser() returns an object of the ApplicationUser or IdentityUser
public ApplicationUser GetUser()
{
var user = new ApplicationUser
{
UserName = UserName,
FirstName = FirstName,
LastName = LastName,
Email = Email,
...
};
return user;
}
The accepted answer was not clear to me so I thought I would share this.
If you look at the source for the IdentityUser class you will find the following.
public class IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser, IUser<string>
{
/// <summary>
/// Constructor which creates a new Guid for the Id
/// </summary>
public IdentityUser()
{
this.Id = Guid.NewGuid().ToString();
}
... code omitted
}
As you can see, when creating a new IdentityUser instance the constructor generates a new GUID and populates the Id field.
This means if you create a new instance of a derived class such as ApplicationUser a new Id will still automatically be generated for you.
So, after you check that the user has been successfully added you can safely use the Id in the ApplicationUser class for your data association.
Example.
var user = new ApplicationUser
{
UserName = "franko",
FirstName = "Frank",
LastName = "Ouimette",
Email = "franko#emailservice.com"
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded){
result = UserManager.AddToRole(user.Id, "User");
// User added successfully, you can safely use the Id now.
var id = user.Id;
}
What worked for me was something slightly different:
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var user = new ApplicationUser() { Email = "informatyka4444#wp.pl", UserName = "informatyka4444#wp.pl" };
var result = manager.Create(user, "TestPass44!");
if (result.Succeeded)
{
string newId = user.Id;
}
Some extension methods have been added to the Identity framework, including this one:
public static string GetUserId(this IIdentity identity);
After performing the sign-in, you should be able to retrieve the ID easily enough:
await SignInAsync(user, isPersistent: false);
var id = this.User.Identity.GetUserId()
This thread is already a few years old, but I encountered this problem and needed to find another solution because I do not use the provided Guids, but a standard integer ID, provided by the database (auto-incremented index).
The problem that I had was that if I created the user with UserManager.CreateAsync, the user object wasn't updated with the ID. I then created a related object for that user, and since the ID wasn't updated, Entity Framework tried to save the user to the database another time which resulted in a primary key violation.
To solve the problem, I needed to read/update the user object like so:
var user = new User
{
UserName = UserName,
FirstName = FirstName,
LastName = LastName,
Email = Email,
...
};
var result = await UserManager.CreateAsync(user, model.Password);
// At this point, the user has been created, but the ID not set
// Read the full user record from the database
user = db.Users.Where(u => u.Email == user.Email).FirstOrDefault();
// At this point, the user has the correct ID set, we are good to go...
In the event for user created, add:
string user = RegisterUser.UserName; or to use later
Session["user"] = RegisterUser.UserName;

Categories