Skip to content
Back to skills

Umbrella Dotnet Scaffold File Upload Controller

ASecurity

Create or migrate a raw-body temporary-file upload controller using UmbrellaFileUploadController, preserving application authorization, client contracts, streaming and extension hooks. Not for multipart form uploads or file-download endpoints.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
toolsrusttestingapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add umbrella-libraries/Umbrella --skill umbrella-dotnet-scaffold-file-upload-controller --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Umbrella Dotnet Scaffold File Upload Controller?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Umbrella Dotnet Scaffold File Upload Controller
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/umbrella-libraries-umbrella-dotnet-scaffold-file-upload-controller/badge)](https://www.skillsdirectory.com/skills/umbrella-libraries-umbrella-dotnet-scaffold-file-upload-controller)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: umbrella-dotnet-scaffold-file-upload-controller
description: 'Create or migrate a raw-body temporary-file upload controller using UmbrellaFileUploadController, preserving application authorization, client contracts, streaming and extension hooks. Not for multipart form uploads or file-download endpoints.'
---

# Scaffold File Upload Controller

## Scope and discovery

Use this skill for a new upload endpoint or migration of a hand-written one to
`UmbrellaFileUploadController<TUploadType>`. Keep application rules in the concrete
controller and reusable storage orchestration in the base.

Before editing, inspect:

- The current controller and client: route, raw-body format, header names, enum values,
  authorization policy, request limit, response body/Location, metadata and permissions.
- The installed `Umbrella.AspNetCore.WebUtilities.FileSystem` API, provider and temp-handler
  registrations. This API and the non-seekable disk fix shipped in preview-0106;
  verify availability in the installed version rather than copying a local assembly.
- The app's controller bases, error mapping and middleware. Moving away from an app base
  can lose inherited routing, authorization, filters or helpers; preserve relevant behavior.
- Existing upload tests and any content-specific validation. An enum value being defined
  does not imply it is an allowed upload purpose (for example, an `Undefined` sentinel).

If the API is missing, use the available `umbrella-nuget-safe-upgrade` workflow when an
upgrade is in scope. Upgrade related Umbrella packages together and verify restored
versions from the configured feed; do not infer availability from nuget.org alone.
Do not publish packages or commit/push as part of scaffolding unless requested.

## Concrete controller

The base lives in `Umbrella.AspNetCore.WebUtilities.FileSystem.Mvc`. The generic argument
is the application's upload-purpose enum (`where TUploadType : struct, Enum`), not a
MIME type or a framework-owned enum. Preserve existing enum values used by clients.

- Derive directly from this specialized base, or an existing app upload base derived
  from it. Do not use an unrelated generic API base or add an intermediate layer solely
  to satisfy the generic custom-controller skill's conventions.
- Put the route and explicit authorization decision on the concrete controller. The
  Umbrella upload base deliberately has **no `[Authorize]`**. Preserve existing policies;
  use `[AllowAnonymous]` only when anonymous uploading is explicitly intended.
- Put the application's `[RequestSizeLimit(...)]` on the concrete class so it applies
  to the inherited action. Preserve the existing limit; do not adopt a universal 1 GiB
  limit. Check server/proxy limits when end-to-end limits are part of the task.
- Inject `ILogger<ConcreteController>`, `IWebHostEnvironment`,
  `IUmbrellaFileStorageProvider` and `IUmbrellaTempFileHandler`, forwarding them to
  `base(logger, hostingEnvironment, fileProvider, tempFileHandler)`.
- Inherit the non-virtual `[HttpPost] PostAsync`. Do not hide it with another action,
  duplicate its orchestration or register the abstract controller in DI.

Default requests carry raw bytes with `X-FileName`, `X-FileContentType` and
`X-FileUploadType` headers. Success is 201 with the generated temporary filename in
the response body and temporary URL in Location. Header changes require matching
client changes; multipart `IFormFile` requests are not this protocol.

## Extension points

Only override what the application needs:

| Member | Contract |
|---|---|
| `FileNameHeaderName`, `ContentTypeHeaderName`, `UploadTypeHeaderName` | Protected virtual string properties; retain defaults unless the client uses different names. |
| `DevelopmentUploadDelay` | Protected virtual `TimeSpan`; defaults to zero and is used only in Development. Preserve an existing UI-testing delay, not a mandatory two seconds. |
| `IsUploadTypeAllowed(TUploadType)` | Call base to retain defined-enum validation, then reject sentinel or unsupported purposes. |
| `ValidateUploadAsync(context, cancellationToken)` | `Task<IActionResult?>`; return a rejection via Umbrella status helpers or null/base to continue. Inspect metadata before reading the body. |
| `PrepareUploadStreamAsync(context, cancellationToken)` | `Task<UmbrellaFileUploadStreamResult>`; default forwards `Request.Body` unbuffered and unowned. Return `Success(stream, ownsStream)` or `Reject(response)`. |
| `WriteUploadMetadataAsync(context, fileInfo, cancellationToken)` | `Task`; preserve base filename/purpose metadata, temp permissions and metadata persistence. Call base, and persist any extra changes made afterwards. |
| `CreateUploadResult(context, fileInfo)` | `IActionResult`; success-only response customization. Reject in validation/preparation instead: returning an error here still marks storage complete. |

`context` is the immutable `UmbrellaFileUploadContext<TUploadType>` record with
`FileName`, `FileExtension`, `ContentType`, nullable `ContentLength`, `UploadType`,
`TempFileName`, `TempPath` and `TempUrl`. Do not mutate it or trust its declared
content type as content validation. The storage provider still infers content type
from the extension.

The lifecycle is validation, preparation, save, actual-empty-file check, metadata,
then response. Keep application rejections before save where practical. The base
propagates cancellation, maps known exceptions and handles remaining failures.
Do not add a broad catch that converts cancellation to a validation error.

## Streams, limits and cleanup

- Ordinary uploads should remain forward-only. Do not access `Request.Body.Length`
  or `Position`, call `EnableBuffering`, or copy every body to memory.
- Missing `Content-Length` is supported. A declared length can reject early, but cannot
  establish an actual content bound; count bytes while reading when enforcing one.
- Use content-specific buffering only when a rule needs it. For a small bounded
  transcript example, read [references/bounded-content.md](references/bounded-content.md).
  Do not add transcript enums, .vtt rules or a 1 MiB cap to apps that do not need them.
- Rewind owned replacement buffers before returning `Success(..., ownsStream: true)`.
  The base disposes them. If preparation rejects or throws before transfer, the
  override must dispose its own allocation. `Reject` transfers no stream ownership.
- Do not dispose the request body. Any owned wrapper around it must leave it open.
  Earlier middleware must not consume it without restoring readable content.
- After a storage attempt that does not complete, the base tries to delete its generated
  temporary path using an independent bounded cancellation token. Cleanup is best-effort;
  preserve the app's normal temporary-file expiry process. Do not duplicate deletion
  in hooks or broaden cleanup to other files.
- Verify the selected provider supports non-seekable streams; a MemoryStream-only test
  does not prove real HTTP uploads work. Provider-specific buffering may still occur.

## Migration and verification

For migration, map existing guards and special cases to the hooks above, remove the old
action, and remove dependencies only when no longer used. Preserve the client-visible
contract and any inherited app behavior intentionally. For a new endpoint, agree on
the application-specific policy, enum and limits before inventing them.

Read `.ai-shared\bundles\umbrella\analyzer-compatibility.md` when available and build the
affected server with its installed analyzers. Match existing test infrastructure.
Cover the changed contract, including:

- Concrete authorization (401 when required, policy 403 where applicable), routing and
  successful 201 filename/Location responses.
- Missing/blank filename, invalid or disallowed purpose, and declared/actual empty bodies.
- Exact stored bytes, standard metadata and temp permissions for ordinary and lengthless uploads.
- Each content-specific rule; if bounded, test actual length at the limit and one byte
  over without Content-Length as well as declared oversize rejection.
- Owned-stream disposal on success/rejection/failure, request-body lifetime, cancellation
  and failed-upload cleanup for custom hooks. Reuse base tests for unchanged base behavior.

When adding provider or streaming coverage, include a real Kestrel non-seekable request
body: in-memory test hosts may expose seekable streams. Run container-backed tests on a
CI job with the required container support; do not silently skip them on unsupported hosts.
Keep inherited response declarations in mind; use `UmbrellaProducesResponseType` for any
additional contract declarations without introducing a duplicate action.

Report changed files, validation results and any remaining compatibility limits.
Scaffolding an upload endpoint does not authorize replacing storage or widening access.

## Companion workflows

- `umbrella-dotnet-scaffold-file-handler`: application file handlers and registrations.
- `umbrella-dotnet-scaffold-file-authorization-handler`: stored-file authorization,
  separate from controller authentication; do not disable it to make uploads pass.
- `umbrella-dotnet-scaffold-aspnetcore-integration-tests`: test infrastructure when absent.

Files in this skill

  • SKILL.md9.1 KB
  • agents/openai.yaml328 B
  • references/bounded-content.md2.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.