IUserStore.CreateAsync : how to indicate fa

2019-06-22 02:14发布

问题:

I am writing a custom implementation for IUserStore. The signature of the create method is:

public async virtual Task CreateAsync(TUser user)

And that makes sense considering the core interface IUserStore in Microsoft.AspNet.Identity is (which is the same).

However the interface of the UserManager class defined in Microsoft.AspNet.Identity is :

public virtual Task<IdentityResult> CreateAsync(TUser user);

My problem is I don't see how I should pass this IdentityResult to the UserManager since the return type in the store is simply "Task". I have a custom logic to determine if a user can or cannot be created, so I really need to tell the outcome of CreateAsync to the UserManager.

Any idea ?

回答1:

Looking at the source code for UserManager.CreateAsync (this is for Identity 2.0) you can see that prior to calling IUserStore.CreateAsync, it makes a call to IIdentityValidator<TUser>.ValidateAsync, which is responsible to actually return the relevant IdentityResult object:

public virtual async Task<IdentityResult> CreateAsync(TUser user)
{
        ThrowIfDisposed();
        await UpdateSecurityStampInternal(user).ConfigureAwait(false);
        var result = await UserValidator.ValidateAsync(user).ConfigureAwait(false);
        if (!result.Succeeded)
        {
            return result;
        }
        if (UserLockoutEnabledByDefault && SupportsUserLockout)
        {
            await GetUserLockoutStore().SetLockoutEnabledAsync(user, true).ConfigureAwait(false);
        }
        await Store.CreateAsync(user).ConfigureAwait(false);
        return IdentityResult.Success;
}

The main purpose of IUserStore.CreateAsync is to make the call to the underlying data source which saves the data. It seems that you make actually want to implement IIdentityValidator<TUser> and set it on your UserManager instance.



回答2:

The answer is in the source code, here's a part of the implementation in the UserManager at time of writing :

public virtual async Task<IdentityResult> CreateAsync(TUser user,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        ThrowIfDisposed();
        await UpdateSecurityStampInternal(user, cancellationToken);
        var result = await ValidateUserInternal(user, cancellationToken);
        if (!result.Succeeded)
        {
            return result;
        }
        if (Options.Lockout.EnabledByDefault && SupportsUserLockout)
        {
            await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken);
        }
        await UpdateNormalizedUserNameAsync(user, cancellationToken);
        await Store.CreateAsync(user, cancellationToken);
        return IdentityResult.Success;
    }

So basically they always return true. This means that in the current version, putting my creation checks in the UserStore goes against the intended usage of the framework.

However I have noticed that this will be changed in the next release. The IUserStore interface will become :

Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken);

And the UserManager implementation :

public virtual async Task<IdentityResult> CreateAsync(TUser user)
    {
        ThrowIfDisposed();
        await UpdateSecurityStampInternal(user);
        var result = await ValidateUserInternal(user);
        if (!result.Succeeded)
        {
            return result;
        }
        if (Options.Lockout.AllowedForNewUsers && SupportsUserLockout)
        {
            await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, CancellationToken);
        }
        await UpdateNormalizedUserNameAsync(user);
        await UpdateNormalizedEmailAsync(user);

        return await Store.CreateAsync(user, CancellationToken);
    }

So putting the creation logic in the UserStore will be possible at that time. This will be a way better design in my opinion as the client shouldn't have to handle the integrity concerns.