1

I using ASP.NET Core Identity, with Jmeter app I send multiple requests to my web application for create user and it created multiple users with same email, although my RequireUniqueEmail is true

services.AddIdentity<User, Role>(identityOptions =>
{
    identityOptions.User.RequireUniqueEmail = true;
});

When I send requesets one by one, everything is okay and give me duplicate email error in this code

var result = await _userManager.CreateAsync(userToCreate, userPassword);

But when I send multiple requests with Jmeter, above code can't handle duplicate email and after finish of Jmeter requests, I open SQL server and I see at least 10 records with same email are created. I searched for this problem and I get this solution Using lock

private static readonly object lockObj = new object();
lock(lockObj)
{
    var result = await _userManager.CreateAsync(userToCreate, userPassword);
}

But it gives me error and says "You can't use await in lock statement", and second problem is ASP.NET Core Identity doesn't have Sync method for create. How can I create sync method of create in ASP.NET Core Identity ?

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Milad Ahmadi
  • 267
  • 4
  • 8

3 Answers3

7

You should enforce this in the database and not with locks or semaphores in the code. Imagine if your application needs to scale to multiple servers for example.

Add a UNIQUE constraint on the email column in the database. When inserting a new user it will give you an error, if the email already exists, that you can handle in your app.

Magnus
  • 45,362
  • 8
  • 80
  • 118
  • Thanks, I make Email required, and I lock the thread with @freeman solution too. Both (Unique constraint and lock the thread) – Milad Ahmadi Aug 22 '23 at 14:05
  • 1
    I would skip the lock since it is not needed and will slow down the creation of new users. Just handle the error from the database. – Magnus Aug 23 '23 at 08:34
5

first of all you cannot use the lock statement with the await keyword! and in your case maybe it's better to use SemaphoreSlim object,because only one request can enter the critical section (between WaitAsync and Release) at a time!

private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);

// rest of your code

async Task CreateUserAsync(User userToCreate, string userPassword)
{
    await semaphore.WaitAsync();
    try
    {
        var result = await _userManager.CreateAsync(userToCreate, userPassword);
    }
    finally
    {
        semaphore.Release();
    }
}

and if you need a synchronous version, you can also create a wrapper method that encapsulates the asynchronous call and blocks the execution until the operation is completed.

Freeman
  • 9,464
  • 7
  • 35
  • 58
1

Locks are thread-bound, and await continuation can run on any thread, so it breaks .NET lock mechanism.

You should also use async methods in asp.net core, so you need to replace lock statement, not method.

There is a nice library Nito.AsyncEx that provides async mutex:

private static readonly AsyncLock _mutex = new AsyncLock();
public async Task<SomeType> Something(Type1 userToCreate,Type2 userPassword)
{
  // AsyncLock can be locked asynchronously
  using (await _mutex.LockAsync())
  {
    return await _userManager.CreateAsync(userToCreate, userPassword);
  }
}
Oleh Nechytailo
  • 2,155
  • 17
  • 26