---
title: "Resize images on the fly in ASP.NET Core"
url: https://recursiveai.org/oneimage/cookbook/resize-images-aspnet-core/
description: "Resize, crop and convert images on request in ASP.NET Core with URLs like photo.jpg?width=400: middleware with a disk cache, presets and signed URLs. Runnable code with OneImage.Web."
version: "1.10.4"
---

# Resize images on the fly in ASP.NET Core

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:

```shell
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:

```csharp
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:

```shell
dotnet run --urls http://localhost:5000
```

and request a few sizes of NASA’s “Blue Marble” photograph (4579×4579, 1.9 MB) from a second terminal:

```shell
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
```

```text
?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:

```csharp
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();
```

```shell
dotnet run --urls http://localhost:5000
```

```shell
base=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
```

```text
?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](https://recursiveai.org/oneimage/docs/web/) 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-cache` in the content root, outside `wwwroot`); set `ConfigureCache(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=manual` isn’t supported, and signed URLs need new tokens. See the [ImageSharp migration guide](https://recursiveai.org/oneimage/articles/migrate-from-imagesharp/).

The `OnBeforeSaveAsync` hook can [watermark](https://recursiveai.org/oneimage/cookbook/add-watermark/) every image as it is served. When you know the sizes up front, resizing once at upload is simpler: see [Resize uploaded images](https://recursiveai.org/oneimage/cookbook/resize-uploaded-images-aspnet-core/).
