Add the RecursiveAI.OneImage.Web middleware before the static files. Every image under wwwroot can then be requested in another size or format, as in /photos/earth.jpg?width=400: it is made on the first request and served from a disk cache after that. Two lines in Program.cs set it up:
dotnet new web -o ImageServer && cd ImageServer dotnet add package RecursiveAI.OneImage.Web --version 1.10.4 mkdir -p wwwroot/photos curl -L -o wwwroot/photos/earth.jpg \ https://images-assets.nasa.gov/image/as17-148-22727/as17-148-22727~orig.jpg
OneImage needs the .NET 10 SDK. Replace Program.cs with:
using OneImage.Web; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOneImage(); var app = builder.Build(); app.UseOneImage(); // before UseStaticFiles, so it sees image requests first app.UseStaticFiles(); app.Run();
Start it:
dotnet run --urls http://localhost:5000and request a few sizes of NASA’s “Blue Marble” photograph (4579×4579, 1.9 MB) from a second terminal:
base=http://localhost:5000/photos/earth.jpg fmt="%{http_code} %{content_type}, %{size_download} bytes\n" for q in "width=400" "w=300&h=300&fit=cover" "width=800&format=webp&q=80" \ "" "width=abc" do curl -s -o /dev/null -w "?$q: $fmt" "$base?$q" done
?width=400: 200 image/jpeg, 22885 bytes ?w=300&h=300&fit=cover: 200 image/jpeg, 13927 bytes ?width=800&format=webp&q=80: 200 image/webp, 69596 bytes ?: 200 image/jpeg, 1915552 bytes ?width=abc: 400 text/plain; charset=utf-8, 26 bytes
A request without commands falls through to the static file middleware untouched, and an invalid value is a 400, never guessed at. Each result goes through the thumbnail pipeline: a large JPEG is decoded at reduced scale, phone photos are turned upright, and EXIF, XMP and IPTC (which can hold a GPS position) are removed. Results carry an ETag, Last-Modified and a 7-day Cache-Control.
| Command | Values |
|---|---|
width, height (w, h) | Pixels; with one of them, the other follows the aspect ratio |
fit | contain (default), cover, stretch, pad |
anchor, focus | What a crop keeps: an edge, a corner, smart, or a point such as focus=0.3,0.6 |
format | jpg, png, webp, gif, bmp |
quality (q) | 1 to 100, for JPEG and WebP |
bg | Padding color, and the color transparency is flattened onto |
Presets and signed URLs for public sites
On a public site, anyone can ask for any size, and every distinct size becomes a cache entry. Allow only named presets, or sign the URLs your pages generate. With presets only:
using OneImage.Web; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOneImage(o => { o.Presets["thumb"] = "width=200&height=200&fit=cover&format=webp"; o.Presets["hero"] = "width=1600&format=webp&quality=80"; o.PresetsOnly = true; // ?preset=thumb works; ?width=123 is ignored }); var app = builder.Build(); app.UseOneImage(); app.UseStaticFiles(); app.Run();
dotnet run --urls http://localhost:5000base=http://localhost:5000/photos/earth.jpg fmt="%{http_code} %{content_type}, %{size_download} bytes\n" for q in "preset=thumb" "preset=hero" "width=123" "preset=huge" do curl -s -o /dev/null -w "?$q: $fmt" "$base?$q" done
?preset=thumb: 200 image/webp, 5356 bytes ?preset=hero: 200 image/webp, 213684 bytes ?width=123: 200 image/jpeg, 1915552 bytes ?preset=huge: 400 text/plain; charset=utf-8, 15 bytes
?width=123 is ignored, so the original is served, and an unknown preset is a 400. To keep free-form commands but accept only URLs you generated, set o.HmacSecretKey (at least 32 random bytes, from configuration) and build URLs with the ImageUrlSigner service: unsigned or altered URLs get a 403. The OneImage.Web docs show it, with the Razor tag helper.
Limits
- Output is capped at 4096 px a side and 16.7 million pixels by default (
MaxWidth,MaxHeight), and sources at 64 MiB. - The cache is a folder (
oneimage-cachein the content root, outsidewwwroot); setConfigureCache(c => c.CacheRootPath = ...)for another location. It isn’t shared between servers. - Coming from ImageSharp.Web?
AddOneImage(o => o.UseImageSharpDefaults())accepts its query spellings (rmode,ranchor,bgcolor…), so stored URLs keep working.rmode=manualisn’t supported, and signed URLs need new tokens. See the ImageSharp migration guide.
The OnBeforeSaveAsync hook can watermark every image as it is served. When you know the sizes up front, resizing once at upload is simpler: see Resize uploaded images.