Recursive AI

Resize uploaded images in ASP.NET Core

All oneimage recipes

In a minimal API endpoint, pass the upload’s stream to Image.Thumbnail and save the result. That one call turns phone photos upright, shrinks them to a maximum width, converts to sRGB and removes EXIF, so no GPS position is stored. Wrap it in a try for ImageException to answer 400 when the upload isn’t an image.

Terminal
dotnet new web -o Uploads && cd Uploads
dotnet add package RecursiveAI.OneImage --version 1.10.4
curl -L -o earth.jpg \
  https://images-assets.nasa.gov/image/as17-148-22727/as17-148-22727~orig.jpg
repo=https://raw.githubusercontent.com/recurser/exif-orientation-examples
curl -L -o phone.jpg $repo/master/Portrait_6.jpg

OneImage needs the .NET 10 SDK. Replace Program.cs with:

C#
using OneImage;
 
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
 
string folder = Path.Combine(app.Environment.ContentRootPath, "uploads");
Directory.CreateDirectory(folder);
 
app.MapPost("/photos", async (IFormFile file) =>
{
    try
    {
        await using Stream upload = file.OpenReadStream();
        // Upright, at most 1600 px wide, sRGB, EXIF and GPS removed.
        Image photo = Image.Thumbnail(upload, 1600);
 
        string path = Path.Combine(folder, $"{Guid.NewGuid():N}.webp");
        await photo.SaveAsync(path);
        long bytes = new FileInfo(path).Length;
        return Results.Ok(new { photo.Width, photo.Height, bytes });
    }
    catch (ImageException ex)   // not an image, damaged, or over the size limits
    {
        return Results.BadRequest(ex.Message);
    }
}).DisableAntiforgery();
 
app.Run();

Start it:

Terminal
dotnet run --urls http://localhost:5000

and post a photo, a phone photo stored sideways, and a file that isn’t an image, from a second terminal:

Terminal
for f in earth.jpg phone.jpg Program.cs
do
  echo "$f: $(curl -s -w ' (%{http_code})' -F "file=@$f" http://localhost:5000/photos)"
done
Text
earth.jpg: {"width":1600,"height":1600,"bytes":176776} (200)
phone.jpg: {"width":1200,"height":1800,"bytes":174880} (200)
Program.cs: "The data is not in a recognized image format." (400)

The 4579 px photo is stored 1600 px wide as WebP. The phone photo, stored as 1800×1200 with an EXIF orientation of 6, comes out upright and isn’t enlarged. Size limits are on by default and are checked from the file headers before large buffers are allocated, so an oversized upload fails with an ImageException too.

Crop a user-picked square

Avatar pickers send the square the user dragged in the browser with the file. Browsers show photos upright, and so does OneImage, so the coordinates line up. This program has only the avatar endpoint; in your app it sits next to the first one:

C#
using OneImage;
 
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
 
app.MapPost("/avatar", async (IFormFile file, int x, int y, int size) =>
{
    try
    {
        await using Stream upload = file.OpenReadStream();
        Image photo = Image.Load(upload);   // upright, like the preview the user saw
 
        // Keep the square inside the image instead of failing on a stray pixel.
        x = Math.Clamp(x, 0, photo.Width - 1);
        y = Math.Clamp(y, 0, photo.Height - 1);
        size = Math.Min(size, Math.Min(photo.Width - x, photo.Height - y));
 
        Image avatar = photo.Crop(x, y, size, size)
                            .Resize(width: 256, height: 256)
                            .StripMetadata();
        var webp = new MemoryStream();
        await avatar.SaveAsync(webp, WebPFormat.Instance);   // streams name the format
        webp.Position = 0;
        return Results.File(webp, "image/webp");
    }
    catch (ImageException ex)
    {
        return Results.BadRequest(ex.Message);
    }
}).DisableAntiforgery();
 
app.Run();
Terminal
dotnet run --urls http://localhost:5000
Terminal
curl -s -o avatar.webp -w '%{http_code} %{content_type}, %{size_download} bytes\n' \
  -F [email protected] 'http://localhost:5000/avatar?x=100&y=300&size=800'
Text
200 image/webp, 2452 bytes

Limits

  • Uploads are untrusted. Keep the try: damaged data can surface at SaveAsync, not only at load. Lower LoadOptions.Limits if you accept smaller images than the defaults.
  • ASP.NET Core’s request size limit (about 28.6 MB by default in Kestrel) applies before OneImage sees the file.

To make many sizes on request instead of one at upload, see Resize images on the fly. For the options of Thumbnail, see Create a thumbnail.