Skip to documentation
Heimdall Docs
Heimdall Docs

File Uploads

Submit ordinary HTML file inputs through Heimdall and bind them with ASP.NET Core's native IFormFile pipeline.

Heimdall adds the interaction layer; form encoding, binding, request limits, validation, and storage remain familiar ASP.NET Core concepts.

1. Build the Form

Use MultipartFormData and the typed file input. Accept, Capture, and Multiple emit the corresponding native HTML attributes.

C#
return FluentHtml.Form(form => form
    .Id("profile-upload")
    .MultipartFormData()
    .Heimdall(h => h
        .Submit("profile.save")
        .PayloadFromClosestForm()
        .Target("#upload-result")
        .SwapInner()
        .Disable())
    .Input(Html.InputType.text, input => input
        .Name("DisplayName")
        .Required())
    .Input(Html.InputType.file, input => input
        .Name("avatar")
        .Accept("image/png", "image/jpeg")
        .Required())
    .Button(button => button
        .Type("submit")
        .Text("Upload")));

2. Runtime Encoding

A form containing a selected file is sent as multipart/form-data. Forms without files continue to use JSON. The browser owns the multipart boundary, so application code should not set Content-Type manually.

JavaScript
const data = new FormData(
  document.querySelector("#profile-upload"));

await Heimdall.invoke("profile.save", data, {
  target: "#upload-result",
  swap: "inner"
});

3. Bind Fields and Files

The normal payload and file parameters can appear together without attributes when their names match the form fields. Heimdall recognizes the multipart request and binds the payload plus IFormFile by convention. Use FromForm only when you want a form-only contract or need Name to map a different input name.

C#
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

[ContentInvocation("profile.save")]
public static IHtmlContent Save(
    ProfileRequest request,
    IFormFile avatar)
{
    return UploadResult.Render(
        request.DisplayName,
        avatar.Length);
}

4. Supported File Shapes

Bind one file, all files, an array, or a common generic collection. File parameters resolve by parameter name. Add FromForm(Name = ...) only when the HTML input uses a different name.

Text
IFormFile
IFormFileCollection
IFormFile[]
IEnumerable<IFormFile>
IReadOnlyList<IFormFile>

[FromForm(Name = "attachments")]
IReadOnlyList<IFormFile> files

5. Native ASP.NET Core Limits

Heimdall honors request and multipart limit metadata on the action method or declaring type. Method metadata wins over type metadata, while configured FormOptions remain the baseline. Limit violations return 413.

JSON
[RequestSizeLimit(10_000_000)]
[RequestFormLimits(
    MultipartBodyLengthLimit = 8_000_000)]
[ContentInvocation("profile.save")]
public static IHtmlContent Save(...)

// Available when intentionally required:
[DisableRequestSizeLimit]

6. Form-Only Payloads

FromForm on the payload is optional for an ordinary multipart submission, but it prevents a registered payload type from being mistaken for a service and rejects JSON with 415 Unsupported Media Type.

C#
public static IHtmlContent Save(
    [FromForm] ProfileRequest request,
    IFormFile avatar)

multipart request -> binds
JSON request      -> 415 Unsupported Media Type

7. Synchronization and Snapshots

With queue-latest, form fields and selected file objects are snapshotted when submit occurs. Later edits do not change the queued request. Disable is still useful when duplicate submission should be impossible rather than queued.

Text
form.Heimdall(h => h
    .Submit("profile.save")
    .PayloadFromClosestForm()
    .SyncQueueLatest("profile-upload")
    .Disable());

8. Security Checklist

File names and Content-Type are untrusted metadata. Validate actual content, generate storage names, keep finite limits, store outside executable/static roots by default, authorize the action, and apply malware scanning appropriate to the application.

Text
Validate:
- declared type and file signature
- allowed size and count
- authorization and ownership

Store:
- generated server-side name
- non-executable location
- outside wwwroot unless serving is intentional

9. Buffered, Not Streaming

Uploads use ASP.NET Core's buffered IFormFile model. Use a dedicated streaming endpoint for very large files instead of forcing that workflow through a Heimdall content action.

Text
Typical profile image or attachment -> Heimdall + IFormFile
Multi-gigabyte streaming upload      -> dedicated endpoint