IUserStore <TUser> .CreateAsync: how to indicate a failure in a custom implementation?
I am writing a custom implementation for IUserStore. The signature of the create method:
public async virtual Task CreateAsync(TUser user) And that makes sense, given the main IUserStore interface in Microsoft.AspNet.Identity (it's the same thing).
However, the interface of the UserManager class defined in Microsoft.AspNet.Identity:
public virtual Task<IdentityResult> CreateAsync(TUser user); My problem is that I donβt see how to pass this IdentityResult to the UserManager, since the return type to the repository is just a βTaskβ. I have user logic to determine if a user can be created or cannot be created, so I really need to report the result of CreateAsync to the UserManager.
Any idea?
Looking at the source code for UserManager.CreateAsync (this is for Identity 2.0), you can see that before calling IUserStore.CreateAsync he makes a call to IIdentityValidator<TUser>.ValidateAsync , which is responsible for actually returning the corresponding 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 IUserStore.CreateAsync a primary data source that stores data. It seems that you really want to implement IIdentityValidator<TUser> and install it on your UserManager instance.
The answer is in the source code , here is part of the implementation in the UserManager at the 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, installing my creation checks in UserStore runs counter to the intended use of the framework.
However, I noticed that this will be changed in the next version. The IUserStore interface will become:
Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken); And the implementation of UserManager:
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); } Thus, creating creation logic in the UserStore will be possible at that time. In my opinion, this will be the best design since the client does not need to deal with integrity issues.