Nell’era digitale di oggi, l’efficiente gestione dell’immagine è cruciale per le applicazioni web e le API. Uno degli aspetti chiave della gestione delle immagini è la compressione, che aiuta a ridurre le dimensioni dei file senza comprometterne significativamente la qualità. Questa guida vi guida attraverso la costruzione di un’API dinamica di compresione delle immagine utilizzando Aspose.Imaging per .NET. Al termine, avrete un funzionale ASP.NET Core Web API che accetta le immagini e restituisce la produzione compresa secondo i parametri della domanda (formato, qualità, resuscita, e altro ancora).

Aspose.Imaging è una potente biblioteca per lavorare con le immagini in .NET. supporta molti formati e fornisce funzionalità di manipolazione robuste, tra cui flussi di lavoro senza perdite (JPEG) e PNG. Lo utilizzeremo per costruire un servizio di compressione efficiente e scalabile.

Che cosa costruisci

  • Il punto finale *: POST /api/images/compress?format=jpeg&quality=75&maxWidth=1280&maxHeight=1280
  • Input: file multipart (immagine), parametri di query opzionali per formato/qualità/resize
  • Outputs: flusso di immagine compreso con corretto Content-Type e capi di caching
  • Sicurezza: Validazione del tipo di contenuto, limiti di dimensioni e codice/incodice salvato

Prerequisiti

  • .net 8 (o .NET 6+)
  • Progetto ASP.NET Core Web API
  • di NuGet: Aspose.Imaging
  • Opzionale: inizializzazione di licenza in app startup (se stai utilizzando una costruzione con licenza)

Struttura del progetto (minimo)

/Controllers
  ImageController.cs
/Services
  ImageCompressionService.cs
/Models
  CompressionRequest.cs
Program.cs
appsettings.json

Esempio completo (Service + Controller)

Sostituisci gli spazi di nomina del locatore con lo spazio di nome del tuo progetto.

/Models/CompressionRequest.cs

namespace ImageApi.Models;

public sealed class CompressionRequest
{
    // "jpeg" or "png"
    public string Format { get; init; } = "jpeg";

    // 1..100 (applies to JPEG only; PNG is lossless)
    public int? Quality { get; init; } = 80;

    // Optional resize bounds; image is resized preserving aspect ratio if either is provided.
    public int? MaxWidth { get; init; }
    public int? MaxHeight { get; init; }

    // If true, strip metadata (EXIF, IPTC) where applicable to reduce size further.
    public bool StripMetadata { get; init; } = true;

    // Guardrails
    public void Validate()
    {
        var fmt = Format?.ToLowerInvariant();
        if (fmt is not "jpeg" and not "png")
            throw new ArgumentException("Unsupported format. Use 'jpeg' or 'png'.");

        if (Quality is { } q && (q < 1 || q > 100))
            throw new ArgumentException("Quality must be between 1 and 100.");

        if (MaxWidth is { } w && w <= 0) throw new ArgumentException("MaxWidth must be positive.");
        if (MaxHeight is { } h && h <= 0) throw new ArgumentException("MaxHeight must be positive.");
    }
}

/Services/ImageCompressionService.cs

using Aspose.Imaging;
using Aspose.Imaging.ImageOptions;
using ImageApi.Models;

namespace ImageApi.Services;

public interface IImageCompressionService
{
    Task<(MemoryStream output, string contentType, string fileExt)> CompressAsync(
        Stream input, CompressionRequest req, CancellationToken ct = default);
}

public sealed class ImageCompressionService : IImageCompressionService
{
    private readonly ILogger<ImageCompressionService> _logger;

    public ImageCompressionService(ILogger<ImageCompressionService> logger)
    {
        _logger = logger;
    }

    public async Task<(MemoryStream output, string contentType, string fileExt)> CompressAsync(
        Stream input, CompressionRequest req, CancellationToken ct = default)
    {
        req.Validate();

        // Defensive copy to a seekable stream
        var inbound = new MemoryStream();
        await input.CopyToAsync(inbound, ct).ConfigureAwait(false);
        inbound.Position = 0;

        // Load image via Aspose.Imaging
        using var image = Image.Load(inbound);

        // Optional: strip metadata (where applicable)
        if (req.StripMetadata)
        {
            TryStripMetadata(image);
        }

        // Optional resize (preserve aspect ratio)
        if (req.MaxWidth.HasValue || req.MaxHeight.HasValue)
        {
            ResizeInPlace(image, req.MaxWidth, req.MaxHeight);
        }

        // Choose encoder and options
        string fmt = req.Format.ToLowerInvariant();
        var (options, contentType, ext) = BuildOptions(fmt, req.Quality);

        // Save to output
        var output = new MemoryStream();
        image.Save(output, options);
        output.Position = 0;

        _logger.LogInformation("Compressed image to {Bytes} bytes as {Ext}", output.Length, ext);
        return (output, contentType, ext);
    }

    private static void ResizeInPlace(Image image, int? maxW, int? maxH)
    {
        var w = image.Width;
        var h = image.Height;

        double scaleW = maxW.HasValue ? (double)maxW.Value / w : 1.0;
        double scaleH = maxH.HasValue ? (double)maxH.Value / h : 1.0;
        double scale = Math.Min(scaleW, scaleH);

        if (scale < 1.0)
        {
            int newW = Math.Max(1, (int)Math.Round(w * scale));
            int newH = Math.Max(1, (int)Math.Round(h * scale));
            image.Resize(newW, newH);
        }
    }

    private static (ImageOptionsBase options, string contentType, string ext) BuildOptions(string fmt, int? quality)
    {
        switch (fmt)
        {
            case "jpeg":
            {
                var q = quality ?? 80;
                var jpeg = new JpegOptions { Quality = q };
                return (jpeg, "image/jpeg", "jpg");
            }
            case "png":
            {
                // PNG is lossless; using defaults ensures broad compatibility.
                // Many PNG tunables exist, but defaults are safe and effective.
                var png = new PngOptions();
                return (png, "image/png", "png");
            }
            default:
                throw new ArgumentOutOfRangeException(nameof(fmt), "Unsupported format.");
        }
    }

    private static void TryStripMetadata(Image image)
    {
        try
        {
            // Not every format exposes EXIF/IPTC the same way; a best-effort clear:
            if (image is RasterImage raster)
            {
                raster.RemoveAllFonts();
                raster.SetPropertyItems(Array.Empty<PropertyItem>());
            }
        }
        catch
        {
            // Non-fatal; ignore if format doesn't support these operations
        }
    }
}

Notes

  • JpegOptions.Quality (1-100) controlla la compressione della perdita.
  • I default PNG sono di solito buoni per una prima versione; se hai bisogno di pNG extra piccole, puoi aggiungere tuning avanzato più tardi.
  • TryStripMetadata è un approccio di miglior sforzo; i metadati API variano in base al formato sorgente.

/Controllers/ImageController.cs

using ImageApi.Models;
using ImageApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace ImageApi.Controllers;

[ApiController]
[Route("api/images")]
public sealed class ImageController : ControllerBase
{
    private static readonly HashSet<string> AllowedContentTypes = new(StringComparer.OrdinalIgnoreCase)
    {
        "image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/tiff"
    };

    private readonly IImageCompressionService _svc;
    private readonly ILogger<ImageController> _logger;

    public ImageController(IImageCompressionService svc, ILogger<ImageController> logger)
    {
        _svc = svc;
        _logger = logger;
    }

    // POST /api/images/compress?format=jpeg&quality=75&maxWidth=1280&maxHeight=1280
    [HttpPost("compress")]
    [RequestSizeLimit(25_000_000)] // 25 MB cap; adjust to your needs
    public async Task<IActionResult> Compress(
        [FromQuery] string? format,
        [FromQuery] int? quality,
        [FromQuery] int? maxWidth,
        [FromQuery] int? maxHeight,
        [FromQuery] bool stripMetadata = true,
        IFormFile? file = null,
        CancellationToken ct = default)
    {
        if (file is null || file.Length == 0)
            return BadRequest("No file uploaded.");

        if (!AllowedContentTypes.Contains(file.ContentType))
            return BadRequest("Unsupported content type. Upload a common raster image (JPEG, PNG, GIF, WebP, BMP, TIFF).");

        var req = new CompressionRequest
        {
            Format = string.IsNullOrWhiteSpace(format) ? "jpeg" : format!,
            Quality = quality,
            MaxWidth = maxWidth,
            MaxHeight = maxHeight,
            StripMetadata = stripMetadata
        };

        await using var input = file.OpenReadStream();
        var (output, contentType, ext) = await _svc.CompressAsync(input, req, ct);

        // Strong caching for immutable responses (tune for your app/CDN)
        Response.Headers.CacheControl = "public,max-age=31536000,immutable";

        return File(output, contentType, fileDownloadName: BuildDownloadName(file.FileName, ext));
    }

    private static string BuildDownloadName(string originalName, string newExt)
    {
        var baseName = Path.GetFileNameWithoutExtension(originalName);
        return $"{baseName}-compressed.{newExt}";
    }
}

Program.cs (Registro DI + licenza opzionale)

using Aspose.Imaging;
using ImageApi.Services;

var builder = WebApplication.CreateBuilder(args);

// Optional: initialize Aspose license from a file or stream if you have one
// var license = new Aspose.Imaging.License();
// license.SetLicense("Aspose.Total.lic");

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IImageCompressionService, ImageCompressionService>();

var app = builder.Build();

app.UseRouting();
app.UseAuthorization();
app.MapControllers();

// Enable for local testing
app.UseSwagger();
app.UseSwaggerUI();

app.Run();

Guida passo dopo passo

Passo 1: Imposta il progetto

Creare un progetto ASP.NET Core Web API. Aspose.Imaging NuGet pacchetto. creare il Models, Services, e Controllers I documenti come mostrato sopra.

Passo 2: Configurare Aspose.Imaging (licenza opzionale)

Se hai una licenza, iniziala a startup (vedere Program.csQuesto evita la valutazione dei marchi d’acqua e garantisce piena funzionalità.

Passo 3: Implementa il servizio di compressione

Il ImageCompressionService:

  • Scarica le immagini tramite Image.Load(Stream)
  • Opzionale taglio dei metadati
  • Opzionale resisione con rapporto di aspetto preservato
  • Risparmio a JPEG o PNG con opzioni adeguate al formato

Passo 4: Costruisci il Controller API

ImageController esposizioni POST /api/images/compress Prendere un file e query parametri:

  • format: jpeg o png (di default jpeg)
  • quality1 – 100 (solo JPEG; default 80)
  • maxWidth/maxHeightI limiti per il declino
  • stripMetadatadi default true Per una produzione più piccola

Passo 5: Testare l’API

Utilizzare qualsiasi client HTTP per inviare un multipart/form-data Richiesta con un singolo campo di file chiamato file, più parametri di query opzionali. verificare:

  • Response Content-Type Il formato delle partite
  • La dimensione del file restituito è ridotta
  • Ristrutturazione dei lavori come previsto

Le scelte di design e le migliori pratiche

  • Impostazioni di accertamento di formato: JPEG utilizza QualityPNG rimane senza perdite per la produzione prevedibile.
  • Downscale prima di codificare: il ripristino riduce i pixel prima (la maggior dimensione vince), poi il codifica raccoglie ulteriori byte.
  • Sanitize input: Tipo di contenuto, dimensione del file, limiti di consultazione.
  • Streaming: Evita di leggere l’intero file nella memoria ripetutamente; mantenere i flussi a breve durata e ricercabili.
  • Caching: segna le risposte come immutabili se deriva nome/contenuto da input deterministici; altrimenti toni i titoli di cache al tuo caso di utilizzo.
  • Sicurezza: Validare il tipo di contenuto e rifiutare i rimborsi sospetti.
  • Observabilità: dimensioni di registrazione prima/dopo e parametri utilizzati; questo ti aiuta a risolvere i default.
  • Trotling: se esposto pubblicamente, limite di tasso o richiede auth per prevenire l’abuso.

Extensioni comuni (drop-in più tardi)

  • WebP/AVIF codificatori per immagini ancora più piccole (aggiungi nuove opzioni/contentTypeL’estensione del file in BuildOptions).
  • PNG tuning (il livello di filtraggio/compressione) se hai bisogno di beni extra piccoli senza perdite.
  • Profili di presentazione come thumbnail, preview, hires Mappa dei parametri conosciuti.
  • ETags o hashing dei contenuti per servire risposte identiche dal cache.
  • Async batch endpoint per comprimere più file contemporaneamente.

Troubleshooting

  • Gli ingressi elevati: aumento RequestSizeLimit o il flusso al temp storage.
  • Colori ornati: Lo spazio colorato assicurato è gestito da default; i casi avanzati potrebbero richiedere un tipo di colore esplicito.
  • Nessuna riduzione di dimensioni (PNG): PNG è senza perdite; consente di riassumere o passare a JPEG per risparmi di byte più forti.

Il riassunto

Ora avete un’API di compressione immagine dinamica pronta per la produzione utilizzando Aspose.Imaging. Il controller gestisce le upload e i parametri; il servizio applica la compresione sicura, informata del formato e la resuscita opzionale, poi ritorna una risposta correttamente tipizzata con le headers cache. Da qui, puoi aggiungere più formati, pre-set e strategie di caching per adattare il tuo web stack.

More in this category