I have chosen to define ApplicationUser class like this in the infrastructure-layer:
namespace MySolution.Infrastructure.Context
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public EmployeeId EmployeeId { get; set; }
public Employee Employee { get; set; }
}
}
I also created a table for connecting a user to an employee
namespace MySolution.Infrastructure.Context
{
public class EmployeeUserMap
{
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public EmployeeId EmployeeId { get; set; }
public Employee Employee { get; set; }
}
}
and here’s the entity from my domain layer:
namespace MySolution.Domain.Entities
{
public class Employee
{
//empty constructor
private Employee() { } // Required by EF core
//properties
public EmployeeId Id { get; init; }
public required VatNumber WorkplaceVAT { get; init; }
public required string EmployeeName { get; set; }
public required Address Address { get; set; }
public required ContactInfo ContactInfo { get; set; }
public required UserRole Role { get; set; }
public string? ApplicationUserId { get; set; }
public CompanyId CompanyId { get; init; }
//Navigational properties
public Company Company { get; set; }
[SetsRequiredMembers]
public Employee(string name, ContactInfo contactInfo, Address address, VatNumber workplaceVAT, UserRole role, CompanyId companyId, string applicationUserId)
{
Id = EmployeeId.Create();
EmployeeName = name ?? throw new ArgumentNullException(nameof(name));
ContactInfo = contactInfo ?? throw new ArgumentNullException(nameof(contactInfo));
Address = address ?? throw new ArgumentNullException(nameof(address));
WorkplaceVAT = workplaceVAT ?? throw new ArgumentNullException(nameof(workplaceVAT));
Role = role;
CompanyId = companyId;
ApplicationUserId = applicationUserId;
Validate(this);
}
private static void Validate(Employee employee)
{
var context = new ValidationContext(employee);
Validator.ValidateObject(employee, context, validateAllProperties : true);
}
public void ChangeContactDetails(ContactInfo newContactDetails)
{
ContactInfo = newContactDetails ?? throw new ArgumentNullException(nameof(newContactDetails));
}
public void ChangeAddress(Address newAddress)
{
Address = newAddress ?? throw new ArgumentNullException(nameof(newAddress));
}
}
}
I can’t wrap my head around how to keep the domain layer independent but to pass an ApplicationUserId to the entity. I want a one-to-one relationship between Employee-ApplicationUser, and I want to be able to create an Employee first and have the applicationId nullable, and next i want to be able to register a user only if the Employee with same mail exists.
public async Task RegisterUser(EditContext editContext)
{
var employee = await _mediator.Send(new GetEmployeeByEmailQuery(Input.Email), default);
if (employee != null)
{
var user = CreateUser();
await UserStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
var emailStore = GetEmailStore();
await emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
user.EmployeeId = employee.Id;
var result = await UserManager.CreateAsync(user, Input.Password);
if (!result.Succeeded)
{
identityErrors = result.Errors;
return;
}
await UserManager.UpdateAsync(user);
employee.ApplicationUserId = user.Id;
await _mediator.Send(new UpdateEmployeeCommand(new VatNumber(employee.WorkplaceVat), employee.Name, AddressMapper.ToValueObject(employee.Address), ContactInfoMapper.ToValueObject(employee.ContactInfo), employee.ApplicationUserId!, employee.Role, employee.CompanyId), default);
Logger.LogInformation("User created a new account with password.");
var userId = await UserManager.GetUserIdAsync(user);
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = NavigationManager.GetUriWithQueryParameters(
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });
await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
if (UserManager.Options.SignIn.RequireConfirmedAccount)
{
RedirectManager.RedirectTo(
"Account/RegisterConfirmation",
new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl });
}
await SignInManager.SignInAsync(user, isPersistent: false);
RedirectManager.RedirectTo(ReturnUrl);
}
else
{
identityErrors = new List<IdentityError> { new IdentityError { Description = "Employee not found for the provided email." } };
}
}