Skip to content

Browser Detection

MediaMatic uses DeviceDetector.NET for browser and device detection, enabling intelligent format negotiation to serve optimal media formats.

Why Browser Detection?

Modern image formats like WebP and AVIF offer better compression but aren't supported by all browsers:

FormatChromeFirefoxSafariEdgeIE 11
JPEG
PNG
WebP✅ (14+)
AVIF✅ (16+)

By detecting the browser, you can serve the best supported format.

Format Negotiation

Basic Usage

csharp
using MJCZone.MediaMatic.AspNetCore;

// In a controller or endpoint
var optimalFormat = BrowserFormatSelector.SelectOptimalFormat(
    userAgent: context.Request.Headers["User-Agent"].ToString(),
    accept: context.Request.Headers["Accept"].ToString()
);

Console.WriteLine($"Optimal format: {optimalFormat}"); // WebP, Avif, Jpeg, etc.

How It Works

  1. Parse Accept Header - Check for image/avif, image/webp
  2. Detect Browser - Identify browser and version
  3. Select Best Format - Return best supported format
csharp
public static ImageFormat SelectOptimalFormat(string userAgent, string accept)
{
    // Check Accept header first
    if (accept.Contains("image/avif"))
    {
        var device = DeviceDetector.Parse(userAgent);
        if (SupportsAvif(device))
            return ImageFormat.Avif;
    }

    if (accept.Contains("image/webp"))
        return ImageFormat.WebP;

    return ImageFormat.Jpeg;
}

Device Detection

Detecting Device Type

csharp
using DeviceDetectorNET;

var detector = new DeviceDetector(userAgent);
detector.Parse();

// Device type
var deviceType = detector.GetDeviceName(); // "smartphone", "desktop", etc.

// Browser info
var browser = detector.GetBrowserClient();
Console.WriteLine($"Browser: {browser.Name} {browser.Version}");

// OS info
var os = detector.GetOs();
Console.WriteLine($"OS: {os.Name} {os.Version}");

Device Types

TypeExamples
desktopWindows PC, Mac
smartphoneiPhone, Android phone
tabletiPad, Android tablet
tvSmart TV, Apple TV
consolePlayStation, Xbox
carTesla browser
botGooglebot, Bingbot

ASP.NET Core Middleware

Image Format Middleware

Automatically serve optimal image format:

csharp
public class ImageFormatMiddleware
{
    private readonly RequestDelegate _next;

    public ImageFormatMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Only for image requests
        if (context.Request.Path.StartsWithSegments("/images"))
        {
            var userAgent = context.Request.Headers["User-Agent"].ToString();
            var accept = context.Request.Headers["Accept"].ToString();

            var format = BrowserFormatSelector.SelectOptimalFormat(userAgent, accept);

            // Set format in request items for downstream use
            context.Items["OptimalImageFormat"] = format;
        }

        await _next(context);
    }
}

// Register in Program.cs
app.UseMiddleware<ImageFormatMiddleware>();

Usage in Endpoints

csharp
app.MapGet("/images/{*path}", async (
    string path,
    HttpContext context,
    IBlobStorage storage) =>
{
    var format = context.Items["OptimalImageFormat"] as ImageFormat?
        ?? ImageFormat.Jpeg;

    // Get path with correct extension
    var optimizedPath = GetOptimizedPath(path, format);

    using var stream = await storage.OpenReadAsync(optimizedPath);
    return Results.File(stream, GetContentType(format));
});

Responsive Image Serving

Device-Based Sizing

Serve different image sizes based on device:

csharp
public static int GetOptimalWidth(string userAgent)
{
    var detector = new DeviceDetector(userAgent);
    detector.Parse();

    return detector.GetDeviceName() switch
    {
        "smartphone" => 640,
        "tablet" => 1024,
        "desktop" => 1920,
        _ => 1280,
    };
}

Picture Element Support

Generate <picture> element with multiple sources:

csharp
public string GeneratePictureElement(string imagePath, string alt)
{
    return $@"
        <picture>
            <source srcset=""{imagePath}.avif"" type=""image/avif"">
            <source srcset=""{imagePath}.webp"" type=""image/webp"">
            <img src=""{imagePath}.jpg"" alt=""{alt}"">
        </picture>
    ";
}

Srcset Generation

csharp
public string GenerateSrcset(string basePath)
{
    var sizes = new[] { 320, 640, 960, 1280, 1920 };

    var srcset = sizes
        .Select(w => $"{basePath}-{w}w.webp {w}w")
        .ToList();

    return string.Join(", ", srcset);
}

// Output: "image-320w.webp 320w, image-640w.webp 640w, ..."

Common Patterns

API Endpoint

csharp
app.MapGet("/api/images/{id}", async (
    int id,
    HttpContext context,
    IImageService imageService) =>
{
    var userAgent = context.Request.Headers["User-Agent"].ToString();
    var accept = context.Request.Headers["Accept"].ToString();

    var format = BrowserFormatSelector.SelectOptimalFormat(userAgent, accept);
    var width = BrowserFormatSelector.GetOptimalWidth(userAgent);

    var image = await imageService.GetOptimizedAsync(id, format, width);

    return Results.File(
        image.Stream,
        image.ContentType,
        enableRangeProcessing: true
    );
});

Content Negotiation Header

Set Vary header for proper caching:

csharp
app.MapGet("/images/{*path}", async (HttpContext context) =>
{
    // Important: Tell CDN/proxy to cache by Accept header
    context.Response.Headers["Vary"] = "Accept";

    // ... serve image
});

Cache-Control

csharp
context.Response.Headers["Cache-Control"] = "public, max-age=31536000"; // 1 year
context.Response.Headers["Vary"] = "Accept";

Bot Detection

Handle search engine bots specially:

csharp
public static bool IsBot(string userAgent)
{
    var detector = new DeviceDetector(userAgent);
    detector.Parse();

    return detector.IsBot();
}

// In endpoint
if (BrowserFormatSelector.IsBot(userAgent))
{
    // Serve original JPEG for SEO
    return Results.File(jpegStream, "image/jpeg");
}

Common Bots

BotUser Agent Contains
GooglebotGooglebot
Bingbotbingbot
Facebookfacebookexternalhit
TwitterTwitterbot
LinkedInLinkedInBot

Performance Tips

Caching Device Detection

csharp
services.AddMemoryCache();

public class CachedDeviceDetector
{
    private readonly IMemoryCache _cache;

    public DeviceInfo Detect(string userAgent)
    {
        return _cache.GetOrCreate(userAgent, entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromHours(1);

            var detector = new DeviceDetector(userAgent);
            detector.Parse();

            return new DeviceInfo
            {
                DeviceType = detector.GetDeviceName(),
                BrowserName = detector.GetBrowserClient()?.Name,
                BrowserVersion = detector.GetBrowserClient()?.Version,
            };
        });
    }
}

Pre-generation

Pre-generate all format variants during upload:

csharp
// During upload
var formats = new[] { ImageFormat.Jpeg, ImageFormat.WebP, ImageFormat.Avif };

foreach (var format in formats)
{
    var result = await processor.ConvertFormatAsync(stream, format);
    await storage.WriteAsync($"{basePath}.{format.Extension}", result.Stream);
}

// During serving - just select the right file
var path = $"{basePath}.{optimalFormat.Extension}";

Testing

Mock User Agents

csharp
// Chrome (WebP + AVIF)
var chrome = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0";

// Safari (WebP only in newer versions)
var safari = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15";

// IE 11 (JPEG only)
var ie11 = "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko";

Next Steps