5

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 ?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
tobiak777
  • 3,175
  • 1
  • 32
  • 44
  • In your application flow, when a user cant be created, is that an exceptional case or is that part of normal flow of execution? – Yuval Itzchakov Oct 03 '15 at 21:28
  • @YuvalItzchakov Thank you for your interest ! It is part of the normal flow of execution. To give more context, I am doing my custom implementation of ASP.Identity in a different assembly, this is why I want to handle this case directly in the UserStore to keep the client focused on the business logic – tobiak777 Oct 03 '15 at 21:44

2 Answers2

4

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Ahah we posted at the exact same time ! Thanks for your help, I'll definitely have a look at IUserValidator – tobiak777 Oct 03 '15 at 22:09
  • @red2nb I also browsed the source on GitHub, then noticed it was for ASP.NET 5. So I looked on SymbolSource instead. The interface is actually called `IIdentityValidator`, i've updated my answer – Yuval Itzchakov Oct 03 '15 at 22:12
  • Actually there's still an issue with that. The validation I am doing, is an integrity check. By relying on the UserValidator I am lacking the context in which this Validator is called (creation, update, etc...). I guess this validator is mainly there to check if Properties have been set and this sort of things. – tobiak777 Oct 03 '15 at 22:14
  • 1
    I'll just put the checks in the client, let's compromise on the design. I'll create an ad-hoc extension method there, thanks a lot for your help – tobiak777 Oct 03 '15 at 22:19
  • Can you please help me with this question:http://stackoverflow.com/questions/39275597/how-to-give-custom-implementation-of-updateasync-method-of-asp-net-identity – I Love Stackoverflow Sep 01 '16 at 15:29
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.

tobiak777
  • 3,175
  • 1
  • 32
  • 44
  • 1
    *So basically they always return true.* If you look again at the code, you'll see that if the call to `ValidateAsync` returns anything other than success, then it's `IdentityResult` will return which will contain the errors. So it isn't actually always returning true. – Yuval Itzchakov Oct 03 '15 at 22:14
  • Can you please help me with this question:http://stackoverflow.com/questions/39275597/how-to-give-custom-implementation-of-updateasync-method-of-asp-net-identity – I Love Stackoverflow Sep 01 '16 at 15:30