Image Upload
An image picker that uploads the picked file straight away, with progress, and posts only the value the server returns
<sa-image-upload name="CoverPhoto" value="kyoto.jpg" src="/cities/kyoto.jpg" upload-url="/ImageUpload?handler=Upload"
label="Cover photo" description="Shown at the top of the tour page." />The demos on this page answer uploads in the browser instead of sending them to a server, so nothing you pick leaves your machine.
Usage
<sa-image-upload> is a self-closing tag. Bind it with asp-for, or give it a name and value, and point upload-url at the endpoint that stores images. Set src to the current image's URL when the stored value is a file name or key rather than a URL.
<sa-image-upload asp-for="..." src="..." upload-url="..." />A picked or dropped file uploads straight away, with its progress over the image, and the value the endpoint returns goes in a hidden input, which is all the form posts. Replacing and removing the image are staged until the form is saved, and the note under the control offers Undo until then. While an upload is running, the control holds the form's submit and asks the user to wait.
The upload endpoint
The control posts the file as the multipart field file to upload-url, with the form's antiforgery token both as the __RequestVerificationToken field and in the RequestVerificationToken header. Outside a form, such as in an editing table row, it uses the nearest token around the control. The endpoint stores the file and answers with JSON:
{ "value": "lisbon-4f2a.jpg", "url": "https://cdn.voyager.example/photos/lisbon-4f2a.jpg" }value is what the form saves. For a failed upload, answer with an error status and { "error": "..." }, which the control shows under the image.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhoto(IFormFile file, CancellationToken cancellationToken)
{
if (file.Length > 5 * 1024 * 1024)
{
return StatusCode(StatusCodes.Status413PayloadTooLarge, new { error = $"{file.FileName} is larger than 5 MB." });
}
var value = await photoStore.SaveAsync(file, cancellationToken);
return Json(new { value, url = photoStore.GetUrl(value) });
}The control checks the file's type and size in the browser before uploading it, but a request can come from anywhere, so check both again on the server.
Examples
Compact
Set layout to ImageUploadLayout.Compact for a small thumbnail beside Replace and Remove buttons, where the image is a detail of the record rather than its subject.
<sa-image-upload name="Thumbnail" value="lisbon.jpg" src="/cities/lisbon.jpg" upload-url="/ImageUpload?handler=Upload"
layout="ImageUploadLayout.Compact" label="Destination thumbnail" />Attachment
ImageUploadLayout.Attachment shows the image as an attachment row with the file's name, for forms that list files.
<sa-image-upload name="HotelPhoto" value="chiang-mai.jpg" src="/cities/chiang-mai.jpg" upload-url="/ImageUpload?handler=Upload"
layout="ImageUploadLayout.Attachment" label="Hotel photo" />File size and types
accept takes a comma-separated list of media types, and max-file-size a size in bytes. They default to JPEG, PNG, WebP, GIF and AVIF, up to 5 MB, and the hint under the control describes them. A file that doesn't match is rejected with a message before it is uploaded. SVG isn't accepted by default, since an SVG file can carry scripts.
<sa-image-upload name="MapScan" upload-url="/ImageUpload?handler=Upload" label="Route map"
accept="image/png,image/webp" max-file-size="1048576" />Table cell
ImageUploadLayout.Cell fits an editing table row: the thumbnail is the button that picks a file, a small badge on its corner removes the image, and a ring marks a change until the row is saved. It renders no label, so name it with aria-label. Errors appear under the thumbnail.
<sa-table>
<sa-table-header>
<sa-table-row>
<sa-table-head>Photo</sa-table-head>
<sa-table-head>Destination</sa-table-head>
</sa-table-row>
</sa-table-header>
<sa-table-body>
<sa-table-row>
<sa-table-cell>
<sa-image-upload name="rows[1].Photo" value="cape-town.jpg" src="/cities/cape-town.jpg" upload-url="/ImageUpload?handler=Upload"
layout="ImageUploadLayout.Cell" aria-label="Photo of Cape Town" />
</sa-table-cell>
<sa-table-cell>Cape Town</sa-table-cell>
</sa-table-row>
<sa-table-row>
<sa-table-cell>
<sa-image-upload name="rows[2].Photo" upload-url="/ImageUpload?handler=Upload"
layout="ImageUploadLayout.Cell" aria-label="Photo of Reykjavík" />
</sa-table-cell>
<sa-table-cell>Reykjavík</sa-table-cell>
</sa-table-row>
</sa-table-body>
</sa-table>The control holds a form's submit while it uploads, but a row saved with htmx or fetch doesn't submit a form. Check the element's uploading property before sending such a request.
Disabled
Set disabled to show the image without letting the user change it. A disabled control needs no upload-url.
<sa-image-upload name="ArchivedPhoto" value="cape-town.jpg" src="/cities/cape-town.jpg" layout="ImageUploadLayout.Compact"
label="Archived tour photo" disabled="true" />Accessibility
The control renders with role="group", named by its label. Its buttons are real buttons reached with Tab, and the drop zone is a button too, so a file can be picked without dragging. The note under the control is a polite live region that announces the upload's progress, errors and staged changes. The current image opens larger in a dialog, as an image does.
Part classes
Pass an ImageUploadClassNames instance from the StellarAdmin.UI namespace using class-names. Each property accepts an optional string of space-separated CSS classes, which are added to the part's existing classes.
| Property | Target |
|---|---|
Root | The generated field wrapper, when the control renders an implicit field. |
Label | The generated field label, when rendered. |
Description | The generated help text, when rendered. |
Error | The generated validation message element, when rendered. |
Content | The generated container around the label and supporting text in a horizontal implicit field. |
Control | The element that holds the parts of the layout. |
Image | The image. |
Note | The note under the control, with the hint, progress, errors and staged changes. |
Root, Label, Description, Error, and Content are inherited from FieldClassNames; see Field part classes.
API Reference
<sa-image-upload>
Renders a web component containing a hidden file input, a hidden <input> that posts the value, and the parts of the chosen layout.
Prop
Type