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
CreateUser
method 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
base
with the required interfaces- Because the AdminUI DI container has already registered the services needed to extend
SSOUserStore
you 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
CustomIdentityFactory
that implementsISSOStoreFactory
- In the constructor, pass in classes needed to create the new
CustomUserStore
andCustomIdentityFactory
- Override the
CreateUserStore
method, 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
SSOUserStore
like above - Overwrite
Delete
method - Initialise an instance of the unit of work
- Call the
HardDelete
method 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.