I'm working on an ASP.NET Core application that uses Identity for user management. I have an AccountService with a Register function, where I accept a RegisterBaseDto that contains the role information. Depending on the role (e.g., STUDENT, TEACHER), I need to map the DTO to a specific user model (Student, Teacher, etc.). Here's my current implementation:
public async Task<OperationResult<AppUser>> Register(RegisterBaseDto registerDto)
{
var userRole = await RoleManager.FindByIdAsync(registerDto.RoleId.ToString());
if (userRole == null)
{
return OperationResult<AppUser>.Failure("User Role Not Found.");
}
AppUser? mappedUser = null;
if (userRole.NormalizedName == "STUDENT")
{
mappedUser = Mapper.Map<Student>(registerDto);
}
else if(userRole.NormalizedName == "TEACHER")
{
mappedUser = Mapper.Map<Teacher>(registerDto);
}
IdentityResult result = await UserManager.CreateAsync(mappedUser, registerDto.Password);
if (result.Succeeded)
{
return OperationResult<AppUser>.Success(mappedUser);
}
return OperationResult<AppUser>.Failure("User is not created, Please try again.");
}
The problem is that this feels very rigid, with hard-coded conditions based on the role names. I believe this violates both the Single Responsibility Principle (SRP) and the Open-Closed Principle (OCP).
Is there a better way to handle this dynamically and improve the design of this method? How can I refactor this code to reduce the conditionals and make it more maintainable, while keeping the mapping flexible for different roles?
1 Answer 1
To address that problem, I would consider using the Factory Pattern. It will help you encapsulating the creation logic of different user types based on the role that you have making it more maintainable and adhearing to the SRP and OCP.
Here is an example of the Factory Pattern implementation in your code:
- Define an interface for the user creation logic:
public interface IUserFactory
{
AppUser CreateUser(RegisterBaseDto registerDto);
}
- Implement the interface for each user type:
public class StudentFactory : IUserFactory
{
public AppUser CreateUser(RegisterBaseDto registerDto)
{
return Mapper.Map<Student>(registerDto);
}
}
public class TeacherFactory : IUserFactory
{
public AppUser CreateUser(RegisterBaseDto registerDto)
{
return Mapper.Map<Teacher>(registerDto);
}
}
- Create a factory provider to get the appropriate factory based on the role:
public class UserFactoryProvider
{
private readonly IDictionary<string, IUserFactory> _factories;
public UserFactoryProvider()
{
_factories = new Dictionary<string, IUserFactory>
{
{ "STUDENT", new StudentFactory() },
{ "TEACHER", new TeacherFactory() }
};
}
public IUserFactory GetFactory(string roleName)
{
if (_factories.TryGetValue(roleName, out var factory))
{
return factory;
}
throw new ArgumentException("Invalid role name", nameof(roleName));
}
}
- Refactor the Register method to use the factory provider:
public async Task<OperationResult<AppUser>> Register(RegisterBaseDto registerDto)
{
var userRole = await RoleManager.FindByIdAsync(registerDto.RoleId.ToString());
if (userRole == null)
{
return OperationResult<AppUser>.Failure("User Role Not Found.");
}
var factoryProvider = new UserFactoryProvider();
var userFactory = factoryProvider.GetFactory(userRole.NormalizedName);
var mappedUser = userFactory.CreateUser(registerDto);
IdentityResult result = await UserManager.CreateAsync(mappedUser, registerDto.Password);
if (result.Succeeded)
{
return OperationResult<AppUser>.Success(mappedUser);
}
return OperationResult<AppUser>.Failure("User is not created, Please try again.");
}
I would also store the type of relationship somewhere in code instead of relying on a plain string. That would make it less error prone and easier to find all references to it throughout the code. For that you could either use an enum
public enum UserRole
{
Student,
Teacher
}
Or a static class
public static class UserRoles
{
public const string Student = "STUDENT";
public const string Teacher = "TEACHER";
}
Explore related questions
See similar questions with these tags.
AppUser
model and a discriminator field?