Using AdminUIs base stores
Samples of the below code can be found on our GitHub
For this part of the documentation the code in Base Installation project will be considered the "Before".
Extending Functionality
The example functionality extension will be performed on the CreateUser method on the ISSOUserStore.
For this, you must:
- Create a class that derives from SSOUserStore
- Create a factory that creates this class
- Overwrite the CreateUsermethod and include a notifier
Creating a new class
- To start, create a new class in your project call CustomUserStore.cs
- Make your class extend the base class SSOUserStore
- Create a constructor that calls basewith the required interfaces- Because the AdminUI DI container has already registered the services needed to extend SSOUserStoreyou don't need to do any DI configuration on your side, you only need to import the correct interfaces.
 
- Because the AdminUI DI container has already registered the services needed to extend 
After these steps you will end up with a class that looks like the following:
public class CustomUserStore : SSOUserStore
{
    public CustomUserStore(IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory) : base(factory, normaliser, userQueryFactory)
    {
    }
}
To use this class instead of using the default implementation, you now need to provide an implementation of the ISSOStoreFactory that returns this implementation of the ISSOUserStore and not AdminUI's
- In a new file create a class called CustomIdentityFactorythat implementsISSOStoreFactory
- In the constructor, pass in classes needed to create the new CustomUserStoreandCustomIdentityFactory
- Override the CreateUserStoremethod, returning theCustomUserStore
After these steps you will have a new class that looks like this:
public class CustomStoreFactory : ISSOStoreFactory
{
    private readonly IIdentityUnitOfWorkFactory _factory;
    private readonly ILookupNormalizer _normaliser;
    private readonly IUserQueryFactory _userQueryFactory;
    public CustomStoreFactory(IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        _normaliser = normaliser ?? throw new ArgumentNullException(nameof(normaliser));
        _userQueryFactory = userQueryFactory ?? throw new ArgumentNullException(nameof(userQueryFactory));
    }
    public ISSOUserStore CreateUserStore()
    {
        return new CustomUserStore(_factory, _normaliser, _userQueryFactory);
    }
    public ISSORoleStore CreateRoleStore()
    {
        return new SSORoleStore(_factory, _normaliser, _userQueryFactory);
    }
    public ISSOClaimTypeStore CreateClaimTypeStore()
    {
        return new SSOClaimTypeStore(_factory, _normaliser);
    }
}
Now register this factory during pipeline initialisation. In your Program.cs or Startup.cs, update the UseAdminUI call by adding WithIdentityStore.
This method accepts a type that needs to implement ISSOStoreFactory
var builder = WebApplication.CreateBuilder(args);
builder.Services
        .AddAdminUI()
        .WithIdentityStore<CustomStoreFactory>();
If you run the code and put a breakpoint in your CustomUserStore constructor, you can see that you are now using your own code. Now you can extend it with your own functionality
Now: 
- Create a NotificationService interface that has a Dispatch method, create a class that inherits it and register it in the DI container
- Pass it into CustomUserStore. You will also need to include it in the CustomStoreFactory constructor.
- Overwrite the CreateUser method to use the notification service.
Now, when AdminUI creates a user, it will also call off to the new NotificationService.
public class CustomUserStore : SSOUserStore
{
    private readonly INotificationService _notificationService;
    public CustomUserStore(INotificationService notificationService, IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory) : base(factory, normaliser, userQueryFactory)
    {
        _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));
    }
    public override Task<ISSOUser> CreateUser(ISSOUser user)
    {
        var createdUser = base.CreateUser(user);
        _notificationService.Dispatch("CreateUser");
        return createdUser;
    }
}
The finished code can be found here
Overriding certain calls
In this section you will create a stub service to show how you overwrite certain methods if you need your own logic surrounding a method.
A good example of this is the Delete method in the ISSOUserStore. By default, AdminUI performs a 'soft' deletion when deleting a user, wherein the IsDeleted flag is set to true. 
However, there is functionality within the UserManager used by AdminUI to perform a hard deletion wherein a user is completely removed from the database.
If you preferred a user to be completely removed during deletion:
- Create a class that inherits from SSOUserStorelike above
- Overwrite Deletemethod
- Initialise an instance of the unit of work
- Call the HardDeletemethod on theUserManager
public class CustomUserStore : SSOUserStore
{
    private readonly INotificationService _notificationService;
    private readonly IIdentityUnitOfWorkFactory _factory;
    public CustomUserStore(INotificationService notificationService, IIdentityUnitOfWorkFactory factory, ILookupNormalizer normaliser, IUserQueryFactory userQueryFactory) : base(factory, normaliser, userQueryFactory)
    {
        _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    }
    ...
    public override async Task DeleteUser(ISSOUser user)
    {
        if (user == null)
        {
            throw new ArgumentNullException(nameof(user));
        }
        using var uow = _factory.Create();
        var databaseUser = user.ToIdentityExpressUser();
        await uow.UserManager.HardDeleteAsync(databaseUser);
    }
}
As a result, when AdminUI's service layer calls the Delete method on the user store, instead of the default delete functionality, your new functionality will be called and the user will be completely removed 
from the database.