Summary#
Image is a renderer primitive — no using, nothing to install. It renders one picture, and the argument you give
it says where the picture is:
| you have | write | what it is |
|---|---|---|
| a URL, or a path in the app's own file store | Image(src: File.Url(item.ImagePath)) | a plain address the browser fetches |
a FileAsset row — what File.Create and Image.Thumbnail answer with | Image(fileAsset: item.Photo.Id) | the platform works out the address |
Give it one or the other, never both and never neither — an Image shows one picture, and an element with no
address draws a broken-image glyph rather than nothing, which reads as a failure of the app.
Signature#
Image("/logo.png") // the first positional IS `src`
Image(src: File.Url(item.ImagePath), alt: "A wheel") // a file in the app's own PATH store
Image(fileAsset: item.Photo.Id, alt: item.Photo.AltText) // a stored FileAsset ROWEverything else an Image takes is the ambient vocabulary every element has — the style props (w, h, rounded,
objectFit), the accessibility props, the event props. osy kit atoms Image prints the lot.
Description#
Why is there a second way at all?#
Because the platform stores files two ways, and only one of them has a path.
A path-keyed file lives at an app-relative address you chose — public/logo.png — and File.Url turns
that into a URL. That is what the upload control produces, and src is how you show it.
A FileAsset is a row. It has an id, a name, a MIME type and its own security, and it has no path — which
is why File.Url/File.SignedUrl, whose argument is a path, cannot address one. It is what File.Create answers
with, what Image.Thumbnail/Resize/Convert answer with (see Image.Thumbnail, Resize and Convert (a stored image, transformed)), and what a FileAsset field on
your entity holds. fileAsset: is how you show one.
Why the row, and not a URL#
The address a browser can fetch a non-public FileAsset from is a short-lived signed grant, minted for one
caller and expiring in minutes — see File.SignedUrl for the same idea over a path. So it is not a value
an app can compute while a page is being drawn, and it is not something you would want to hold: it would go stale
under a reader who left the page open.
So the element carries the row, and the renderer asks for an address when it actually needs one, renews it before
it expires, and asks once however many places on the page show the same file. The access decision is your entity's
own security {} block and nothing else: a reader who may not read the asset is refused the address.
What a reader sees when they may not see it#
A refused image shows nothing, and says so in its alternative text, rather than the browser's broken-image icon.
That distinction is deliberate: "you may not see this" and "this app is broken" look identical on screen and are
different facts. Write an alt and the refusal is appended to it.
Showing a photograph somebody uploaded#
The whole loop — a row that holds a picture, and a page that shows it:
using Osysharp.Storage;
using Osysharp.Ui;
entity Listing {
[Required, MaxLength(140)] string Title;
FileAsset? Photo;
security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}
[Page("/listings")]
[AllowAnonymous]
component Listings() {
live var rows = Listing.OrderBy(l => l.Title);
render {
Stack(gap: 4, p: 8) {
foreach (var l in rows) {
Row(gap: 3, align: Align.Center) {
Image(fileAsset: l.Photo.Id, alt: l.Title, w: 120, rounded: Radius.Md);
Text(l.Title);
}
}
}
}
}l.Photo is the row and l.Photo.Id is what the element takes — the same spelling PdfViewer and PdfThumbnail
take, so every stored file is addressed the same way.
A thumbnail rather than the full picture#
A list of twenty photographs should not send twenty full-size images to a phone. Image.Thumbnail makes a smaller
variant, stores it, and answers a FileAsset of its own — which is another thing to show:
using Osysharp.Images;
entity ListingThumb {
[Required] Listing Of;
FileAsset? Small;
security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}
ListingThumb MakeThumb(Listing listing) {
var thumb = new ListingThumb { Of = listing, Small = Image.Thumbnail(listing.Photo, 128) };
return thumb;
}Nothing is generated on upload: the variant exists the moment you ask for it, is deduplicated by content, and is
addressed by Image(fileAsset: t.Small.Id) exactly like the original.
A picture in the app's own file store#
When the file is one the app put somewhere by path — a logo, a seeded asset, an upload result — there is no
row and no signing. File.Url builds the address and src takes it:
using Osysharp.Storage;
entity Brand {
[Required, MaxLength(200)] string LogoPath;
security { allow read when IsAuthenticated || IsAnonymous; }
}
string LogoUrl(Brand brand) { return File.Url(brand.LogoPath); }See also#
- Files (addressing something the app stores) — the two ways a file is stored, and which address each one has
- Image.Thumbnail, Resize and Convert (a stored image, transformed) —
Image.Thumbnail,ResizeandConvert: a stored image transformed into another one - File.Url — the public address of a PATH-keyed file, which is what
srcwants - File.SignedUrl — a time-limited grant to one caller, over a path
- upload — where a picture usually comes from
- camera and microphone — a photograph from the camera, arriving as the same
UploadedFile