Skip to content
Shannon Deminick edited this page Dec 15, 2020 · 2 revisions

If you wish to add support for the features that are not added by default that will generally mean extending the user store to add this functionality.

In some cases, this could mean extending the user manager and overriding some of the methods there too if they don't behave the way that you want.

The best resource for understanding how the user manager interacts with the user store is to look at the ASP.NET source

In some cases you may need to have a custom user and extend the default UmbracoIdentityMember to add custom properties to it. That is fully supported and it's why the TMember generic type is used everywhere.

You can configure these custom stores, managers and users on startup

Custom user store

This is an example of a custom user store implementation that implements the IUserLockoutStore feature which persists the

public class CustomUmbracoMembersUserStore<TMember> : UmbracoMembersUserStore<TMember>,
    IUserLockoutStore<TMember, int>
    where TMember : UmbracoIdentityMember, IUser<int>, new()
{
    public CustomUmbracoMembersUserStore(ILogger logger, IMemberService memberService, IMemberTypeService memberTypeService, IMemberGroupService memberGroupService, IdentityEnabledMembersMembershipProvider membershipProvider, IExternalLoginStore externalLoginStore)
        : base(logger, memberService, memberTypeService, memberGroupService, membershipProvider, externalLoginStore)
    {
    }

    public Task<int> GetAccessFailedCountAsync(TMember user) => Task.FromResult(user.AccessFailedCount);

    public Task<bool> GetLockoutEnabledAsync(TMember user) => Task.FromResult(user.LockoutEnabled);

    public Task<DateTimeOffset> GetLockoutEndDateAsync(TMember user) => Task.FromResult(new DateTimeOffset(user.LockoutEndDateUtc ?? DateTime.MinValue));

    public Task<int> IncrementAccessFailedCountAsync(TMember user)
    {
        user.AccessFailedCount++;
        return Task.FromResult(user.AccessFailedCount);
    }

    public Task ResetAccessFailedCountAsync(TMember user)
    {
        user.AccessFailedCount = 0;
        return Task.CompletedTask;
    }

    public Task SetLockoutEnabledAsync(TMember user, bool enabled)
    {
        user.LockoutEnabled = enabled;
        return Task.CompletedTask;
    }

    public Task SetLockoutEndDateAsync(TMember user, DateTimeOffset lockoutEnd)
    {
        user.LockoutEndDateUtc = lockoutEnd.UtcDateTime;
        return Task.CompletedTask;
    }

    /// <summary>
    /// Override to map custom properties for persistence
    /// </summary>
    /// <param name="member"></param>
    /// <param name="user"></param>
    /// <returns></returns>
    protected override bool UpdateMemberProperties(IMember member, TMember user)
    {
        var hasChanged = base.UpdateMemberProperties(member, user);

        // NOTE: AccessFailedCount is already handled on the base class

        // Assumes your lockoutEnabled member type property is a true/false property type, in which case
        // i think it stores 0 or 1 as a int
        var lockoutEnabled = member.GetValue<int>("lockoutEnabled") == 1;
        if (lockoutEnabled != user.LockoutEnabled)
        {
            member.SetValue("lockoutEnabled", user.LockoutEnabled ? 1 : 0);
            hasChanged = true;
        }

        // Assumes your lockoutEndDateUtc member type property is a date property type
        var lockoutEndDateUtc = member.GetValue<DateTime>("lockoutEndDateUtc");
        if (lockoutEndDateUtc != (user.LockoutEndDateUtc ?? DateTime.MinValue))
        {
            member.SetValue("lockoutEndDateUtc", user.LockoutEndDateUtc);
            hasChanged = true;
        }

        return hasChanged;
    }

    /// <summary>
    /// Override to map custom properties when retrieved from the DB
    /// </summary>
    /// <param name="member"></param>
    /// <returns></returns>
    protected override TMember MapFromMember(IMember member)
    {
        var mapped = base.MapFromMember(member);
        mapped.LockoutEnabled = member.GetValue<int>("lockoutEnabled") == 1;
        mapped.LockoutEndDateUtc = member.GetValue<DateTime>("lockoutEndDateUtc");
        return mapped;
    }
}