fullstackhero/dotnet-starter-kit · error · CustomException
Group with name ' ' already exists.
Error message
Group with name '{name}' already exists. What it means
ValidateUniqueNameAsync checks whether another group (Id != excludeId) already has the requested Name and, if so, throws CustomException with HTTP 409 Conflict. Group names are unique within the store, so a rename to an existing name is rejected.
Solutions
- Choose a different, unique group name for the update.
- Add a client-side uniqueness check (list groups, compare names case-insensitively) before submitting the update.
- If the name should be free but isn't, find the group holding it and rename/delete that group first, or merge the two groups.
Example fix
// before
await mediator.Send(new UpdateGroupCommand { Id = id, Name = "Sales" }); // 409: 'Sales' exists
// after
var groups = await groupApi.ListAsync();
if (groups.Any(g => g.Name.Equals("Sales", StringComparison.OrdinalIgnoreCase) && g.Id != id))
{
showValidationError("A group named 'Sales' already exists.");
return;
}
await mediator.Send(new UpdateGroupCommand { Id = id, Name = "Sales" }); Defensive patterns
Strategy: validation
Validate before calling
var taken = (await groupApi.ListAsync())
.Any(g => g.Name.Trim().Equals(newName.Trim(), StringComparison.OrdinalIgnoreCase) && g.Id != groupId);
if (taken) showValidationError("Group name already in use."); Type guard
bool isUniqueName(IEnumerable<Group> groups, string name, Guid excludeId) =>
!groups.Any(g => g.Id != excludeId && string.Equals(g.Name, name, StringComparison.OrdinalIgnoreCase)); Try / catch
try
{
await mediator.Send(new UpdateGroupCommand { Id = id, Name = newName });
}
catch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Conflict)
{
showValidationError("A group with that name already exists. Choose another name.");
} Prevention
- Check name availability client-side before submit and show inline validation.
- Trim/normalize names consistently on both client and server.
- Guard against concurrent renames by re-checking after any long editing session.
When it happens
Trigger: Sending UpdateGroupCommand whose Name matches a different existing group in the same tenant — typically a rename colliding with an already-taken group name.
Common situations: Two admins concurrently rename groups to the same value; a UI that doesn't pre-check name availability; renaming a group back to a name it previously held before being renamed away while another group took it.
Related errors
- Group with name ' ' already exists.
- Another category with name
- A product with SKU ' ' already exists.
- A product with name ' ' already exists.
- Another product with name
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/6733f1c4d8760a29.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Groups/UpdateGroup/UpdateGroupCommandHandler.cs:82
return await BuildResponseAsync(group, newRoleIds, cancellationToken);
}
private async Task<Group> GetGroupAsync(Guid id, CancellationToken cancellationToken)
{
return await _dbContext.Groups
.Include(g => g.GroupRoles)
.FirstOrDefaultAsync(g => g.Id == id, cancellationToken)
?? throw new NotFoundException($"Group with ID '{id}' not found.");
}
private async Task ValidateUniqueNameAsync(Guid excludeId, string name, CancellationToken cancellationToken)
{
var nameExists = await _dbContext.Groups
.AnyAsync(g => g.Name == name && g.Id != excludeId, cancellationToken);
if (nameExists)
{
throw new CustomException($"Group with name '{name}' already exists.", (IEnumerable<string>?)null, System.Net.HttpStatusCode.Conflict);
}
}
private async Task ValidateRoleIdsAsync(IReadOnlyList<string>? roleIds, CancellationToken cancellationToken)
{
if (roleIds is not { Count: > 0 })
{
return;
}
var existingRoleIds = await _dbContext.Roles
.Where(r => roleIds.Contains(r.Id))
.Select(r => r.Id)
.ToListAsync(cancellationToken);
var invalidRoleIds = roleIds.Except(existingRoleIds).ToList();
if (invalidRoleIds.Count > 0)
{View on GitHub (pinned to 3f2959e683)