Issue with /api/register - Tenant and User Creation Flow
Hello, I am implementing a very common scenario for a multi-tenant SaaS application where users can sign up by providing their email and password. The flow involves the following steps:
A tenant is created for the user upon registration. A user is created for that tenant with the provided email and password. An email verification is sent to the user to verify their account.
However, I am encountering a NullReferenceException during Step 2 when creating a user var result = await _userManager.CreateAsync(user, request.Password);
public async Task<ErrorOr<RegisterResponseDTO>> RegisterAsync(RegisterRequestDTO request)
{
try
{
// Step 1 - Create Tenant
var tenant = await _multiTenantService.CreateTenantAsync();
if (tenant.IsError)
{
return tenant.Errors;
}
// Step 2 - Set Tenant Context
var scope = _serviceScopeFactory.CreateScope();
var accessor = scope.ServiceProvider.GetRequiredService<IMultiTenantContextAccessor<TenantInfo>>();
var setter = scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>();
var resolver = scope.ServiceProvider.GetRequiredService<ITenantResolver>();
var httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
var httpContext = httpContextAccessor.HttpContext ?? new DefaultHttpContext();
httpContext.Request.Headers.Append("X-Tenant", tenant.Value.Identifier);
var mtContext = await resolver.ResolveAsync(httpContext);
setter.MultiTenantContext = mtContext;
// Step 3 - Create a user
var user = new AppIdentityUser
{
Email = request.Email,
UserName = request.Email,
FirstName = request.FirstName,
LastName = request.LastName
};
var result = await _userManager.CreateAsync(user, request.Password);
.
.
.
//Send verification email
}
catch (Exception ex)
{
return Error.Internal($"An error occurred: {ex.Message}");
}
}
What I Need Help With: Guidance on how to properly ensure the tenant context is set up before invoking _userManager. Any suggestions on debugging why _userManager or its dependencies might be null. Best practices for handling this type of tenant and user creation flow.
Extras
services.AddMultiTenant<TenantInfo>() .WithHeaderStrategy("X-Tenant") .WithEFCoreStore<TenantDbContext, TenantInfo>();
public class TenantDbContext : EFCoreStoreDbContext<TenantInfo> { public TenantDbContext (DbContextOptions<TenantDbContext > options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new TenantConfiguration());
}
}
public class AppIdentityDbContext : MultiTenantIdentityDbContext<AppIdentityUser> { public AppIdentityDbContext (IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions<AppIdentityDbContext > options) : base(multiTenantContextAccessor, options) { } public ZerpIdentityDbContext(IMultiTenantContextAccessor multiTenantContextAccessor) : base(multiTenantContextAccessor) { }
public AppIdentityDbContext (ITenantInfo tenantInfo) : base(tenantInfo)
{
// used for the design-time factory and progammatic migrations in program.cs
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new UserRoleConfiguration());
modelBuilder.ApplyConfiguration(new ClaimConfiguration());
}
}
[MultiTenant] public class AppIdentityUser : IdentityUser { public string FirstName { get; set; } public string LastName { get; set; } }
Also, if someone can advise what is the best way to handle such scenario? I am fairly new to programming.
Hi, there are two approaches you can go with here:
- after registration and tenant creation redirect to a page to finish the setup -- in the redirected request the tenant should get picked up accordingly so the
UserManagergets the correct database connection. - create a new DI scope from the
RequestServicesservice provider onHttpContext, get an instance ofIMultiTenantContextSetterfrom it, use it to set the tenant, then resolve an instance ofUserManagerfrom the new scope and it should have the correct tenantAppIdentityDbContext.
See if that helps and if you run into more issues let me know.
This issue has been labeled inactive because it has been open 30 days with no activity. This will be closed in 7 days without further activity.
edit: No this will not be closed--sorry about that!
This issue has been labeled inactive because it has been open 180 days with no activity. Please consider closing this issue if no further action is needed.