We are currently developing an ASP.NET Core Web API and using ASP.NET Core Identity for role management.
If something changes in a role or a role is removed or added to a user, another service has to be called to customize some objects for all affected users.
This is the class that is called when a role assignment changes:
public class RoleHandler{
public RoleHandler(IUserService userService, IOtherService otherService, IUnitOfWork unitOfWork)
{
_otherService = otherService;
_unitOfWork = unitOfWork;
_userService = userService;
}
public async Task<int> AddToRolesAsync(Guid userId, IEnumerable<string> roleNames)
{
using var transaction = _unitOfWork.Context.Database.BeginTransaction();
try
{
await _userService.AddToRolesAsync(userId, roleNames);
await _unitOfWork.SaveChangesAsync();
_otherService.DoPermissionStuff(userId);
await transaction.CommitAsync();
}
catch (Exception e)
{
transaction.Rollback();
throw new DbUpdateException("Role could not be updated", e);
}
return await Task.FromResult(1);
}
}
If either the role assignment or editing fails, or something goes wrong when the other service is called, none of the transactions may be written to the database.
The main problem is that the other service is designed to read all the assigned roles of the user from the database. So the new assignments should already be in the database.
public class OtherService : IOtherService
{
private readonly IUserRoleService _userRoleService;
public OtherService(IUserRoleService userRoleService)
{
_userRoleService = userRoleService;
}
public void DoPermissionStuff(Guid userId)
{
var userRoles = _userRoleService.GetUserRoles(userId);
//... do stuff with newly added roles
}
}
// service to load the newly added rolls
public class UserRoleService : IUserRoleService
{
private readonly IUserService _userService;
private readonly IRoleService _roleService;
public UserRoleService(IUserService userService, IRoleService roleService)
{
_userService = userService;
_roleService = roleService;
}
public IEnumerable<TRole> GetRolesByUser(Guid userId)
{
var roleNames = _userService.GetUserRoles(userId);
var roles = _roleService.GetRolesByName(roleNames);
return roles;
}
}
What solution for this problem would you recommend me?
I have been playing around with the Unit of Work pattern by disabling the AutoSaveChanges option in the RoleManager and UserManager. I have reorganized my services several times but the main problem is the database access of the other service. Now I'm thinking about solving the whole thing via a transaction, but does that work when multiple services access the DbContext via dependency injection?
This is the unit of work i tried to implement:
public class UnitOfWork : IUnitOfWork
{
public MyDbContext Context { get; }
public UnitOfWork(MyDbContext dbContext)
{
this.Context = dbContext;
}
public int SaveChanges()
{
return Context.SaveChanges();
}
public Task<int> SaveChangesAsync()
{
return Context.SaveChangesAsync();
}
}