fullstackhero/dotnet-starter-kit · error · CustomException

Group with name ' ' already exists.

Error message

Group with name '{command.Name}' already exists.

What it means

CreateGroup enforces group-name uniqueness within the tenant; when a Group with command.Name already exists it throws CustomException with HttpStatusCode.Conflict (HTTP 409). Unlike NotFound/Forbidden, this surfaces as a 409 with the message in the response body.

Solutions

  1. Query existing groups first and reuse the group if the name matches (idempotent create)
  2. Catch the 409 and surface 'name already in use' in the UI to let the user pick another name
  3. Uniquify the name (e.g. suffix) in seeding/import scripts

Example fix

// before
await api.CreateGroup(new CreateGroupCommand { Name = "Admins" });
// after
var existing = await api.SearchGroups("Admins");
var group = existing.FirstOrDefault() ?? await api.CreateGroup(new CreateGroupCommand { Name = "Admins" });
Defensive patterns

Strategy: try-catch

Validate before calling

var clash = (await api.SearchGroups(name)).Any(g => g.Name.Equals(name, StringComparison.Ordinal));
if (clash) throw new InvalidOperationException("Group name already in use");

Try / catch

try { await api.CreateGroup(cmd); }
catch (ApiException e) when (e.Status == 409) { NotifyUser("A group with that name already exists."); }

Prevention

When it happens

Trigger: POST /groups with a Name identical to an existing group in the same tenant (exact string match).

Common situations: Double-submitting a create form; seeding scripts run twice; re-running an import that creates default groups.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/5adb398bcb640d91. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Groups/CreateGroup/CreateGroupCommandHandler.cs:33

    private readonly ICurrentUser _currentUser;

    public CreateGroupCommandHandler(IdentityDbContext dbContext, ICurrentUser currentUser)
    {
        _dbContext = dbContext;
        _currentUser = currentUser;
    }

    public async ValueTask<GroupDto> Handle(CreateGroupCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        // Validate name is unique within tenant
        var nameExists = await _dbContext.Groups
            .AnyAsync(g => g.Name == command.Name, cancellationToken);

        if (nameExists)
        {
            throw new CustomException($"Group with name '{command.Name}' already exists.", (IEnumerable<string>?)null, System.Net.HttpStatusCode.Conflict);
        }

        // Validate role IDs exist — fetch Id+Name in a single query to avoid a second roundtrip later
        List<(string Id, string Name)> resolvedRoles = [];
        if (command.RoleIds is { Count: > 0 })
        {
            var rawRoles = await _dbContext.Roles
                .Where(r => command.RoleIds.Contains(r.Id))
                .Select(r => new { r.Id, r.Name })
                .ToListAsync(cancellationToken);
            resolvedRoles = rawRoles.Select(r => (r.Id, r.Name!)).ToList();

            var invalidRoleIds = command.RoleIds.Except(resolvedRoles.Select(r => r.Id)).ToList();
            if (invalidRoleIds.Count > 0)
            {
                throw new NotFoundException($"Roles not found: {string.Join(", ", invalidRoleIds)}");
            }
        }

View on GitHub (pinned to 3f2959e683)