fullstackhero/dotnet-starter-kit · error · InvalidOperationException

File type ' ' is not allowed. Allowed

Error message

File type '{extension}' is not allowed. Allowed: {string.Join(", ", rules.AllowedExtensions)}

What it means

S3StorageService.UploadAsync resolves FileTypeRules and rejects uploads whose FileName extension is missing or not in rules.AllowedExtensions, throwing InvalidOperationException before any S3 call. Identical policy to the local provider — file types are whitelisted per FileType for security.

Solutions

  1. Ensure the uploaded FileName has a whitelisted extension for the target FileType.
  2. Add the required extension to rules.AllowedExtensions in FileTypeMetadata if legitimate.
  3. Normalize file names on the client (trim, append correct extension) before upload.
  4. Pre-validate extensions in the frontend to fail fast.

Example fix

// before
await s3.UploadAsync(new UploadRequest { FileName = "document.v1", FileType = FileType.Document });

// after
await s3.UploadAsync(new UploadRequest { FileName = "document.pdf", FileType = FileType.Document }); // valid extension
Defensive patterns

Strategy: validation

Validate before calling

var ext = Path.GetExtension(fileName);
var rules = FileTypeMetadata.GetRules(fileType);
if (string.IsNullOrWhiteSpace(ext) || !rules.AllowedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase))
    throw new ArgumentException($"Extension '{ext}' not allowed for {fileType}.");

Type guard

bool IsAllowedExtension(string? ext, FileTypeRules rules) =>
    !string.IsNullOrWhiteSpace(ext) && rules.AllowedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);

Try / catch

try {
    await s3Storage.UploadAsync(request);
} catch (InvalidOperationException ex) when (ex.Message.StartsWith("File type")) {
    logger.LogWarning("Rejected disallowed upload: {Message}", ex.Message);
    return Results.BadRequest(ex.Message);
}

Prevention

When it happens

Trigger: UploadAsync against the S3 provider with a FileName lacking an extension or carrying an extension not whitelisted for that FileType.

Common situations: Switching from local to S3 storage with clients that previously bypassed validation; generated/synthetic files saved without extensions; extension casing or trailing whitespace; new file kinds added without updating FileTypeMetadata.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/BuildingBlocks/Storage/S3/S3StorageService.cs:51

        _logger = logger;
        _contentTypeProvider = new FileExtensionContentTypeProvider();

        if (string.IsNullOrWhiteSpace(_options.Bucket))
        {
            throw new InvalidOperationException("Storage:S3:Bucket is required when using S3 storage.");
        }
    }

    public async Task<string> UploadAsync<T>(FileUploadRequest request, FileType fileType, CancellationToken cancellationToken = default) where T : class
    {
        ArgumentNullException.ThrowIfNull(request);

        var rules = FileTypeMetadata.GetRules(fileType);
        var extension = Path.GetExtension(request.FileName);

        if (string.IsNullOrWhiteSpace(extension) || !rules.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException($"File type '{extension}' is not allowed. Allowed: {string.Join(", ", rules.AllowedExtensions)}");
        }

        if (request.Data.Count > rules.MaxSizeInMB * 1024 * 1024)
        {
            throw new InvalidOperationException($"File exceeds max size of {rules.MaxSizeInMB} MB.");
        }

        var key = BuildKey<T>(SanitizeFileName(request.FileName));

        using var stream = new MemoryStream([.. request.Data]);

        var putRequest = new PutObjectRequest
        {
            BucketName = _options.Bucket,
            Key = key,
            InputStream = stream,
            ContentType = request.ContentType
        };

View on GitHub (pinned to 3f2959e683)