Skip to content

Conversation

@rolandVi
Copy link
Contributor

@rolandVi rolandVi commented Aug 21, 2025

Image Component

Summary

This PR introduces an Image component that renders images from non-HTTP sources (bytes or streams) with streaming JS interop and optional browser-side caching.

Features

  • Simple source model
    • ImageSource from byte[] or Stream with MimeType and CacheKey properties
    • Single-use design: one ImageSource instance corresponds to exactly one image load
    • Properties: MimeType, CacheKey, Length.
  • Automatic caching
    • Caching backed by the browser Cache Storage API, keyed by CacheKey.
  • Stream-based transfer
    • Uses DotNetStreamReference for efficient streaming from .NET to JS
    • Supports progress tracking via CSS custom property --blazor-image-progress
    • Robust cancellation/race handling: only the latest request for an element is finalized
  • Minimal markup and CSS-driven UX
    • Renders a single with "data-blazor-image" attribute.
    • data-state="loading" or "error" enables styling via CSS.
    • Container can read a CSS custom property --blazor-image-progress for simple progress UI.
  • Lifecycle and memory safety
    • Blob URL creation/revocation handled in JS.

API surface

  • Parameters
    • Source: ImageSource
    • AdditionalAttributes: forwarded to the underlying (e.g., alt, class, style)
  • Render output
    • <img data-blazor-image data-state="loading|error" ... />

Implementation

  • Component and model
    • src/Components/Web/src/Image/Image.cs
    • src/Components/Web/src/Image/ImageSource.cs
  • JavaScript interop
    • src/Components/Web.JS/src/Rendering/BinaryImageComponent.ts
  • Tests
    • Unit: src/Components/Web/test/Image/ImageTest.cs
    • E2E test component: src/Components/test/testassets/BasicTestApp/ImageTest/ImageTestComponent.razor
    • E2E: src/Components/test/E2ETest/Tests/ImageTest.cs

Performance considerations

  • Stream-based transfer avoids large memory allocations
  • Blob URLs are revoked when no longer needed.
  • Cache-first load avoids redundant streaming for repeated images
@using Microsoft.AspNetCore.Components.Web.Image

@code {
    private ImageSource? _photo;

    protected override async Task OnInitializedAsync()
    {
        var bytes = await PhotoService.GetBytesAsync(id: 42);
        _photo = new ImageSource(bytes, "image/jpeg", cacheKey: "photo-42");
    }
}

<Image Source="@_photo"
       alt="Product photo"
       style="max-width: 100%; height: auto;" />

Related to -> #25274

rolandVi and others added 30 commits July 21, 2025 13:43
…for loading and error styling using css data-state
rolandVi and others added 2 commits August 21, 2025 17:59
…helper, use AbortController for stale stream cancelation
Initial image component is in good shape, I'll compare with ASP.NET core now
Copilot AI review requested due to automatic review settings August 21, 2025 16:04
@rolandVi rolandVi requested a review from a team as a code owner August 21, 2025 16:04
@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Aug 21, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new Image component for Blazor that efficiently renders images from non-HTTP sources such as byte arrays or streams. The component provides automatic browser-side caching, streaming support with progress tracking, and proper memory management through blob URL lifecycle management.

  • Implements ImageSource class for single-use image data with automatic caching
  • Adds Image component with streaming JS interop and CSS-driven loading states
  • Provides comprehensive test coverage including unit tests and E2E scenarios

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Components/Web/src/Image/ImageSource.cs Core image source model with stream and byte array constructors
src/Components/Web/src/Image/Image.cs Main Image component implementation with rendering and lifecycle management
src/Components/Web.JS/src/Rendering/BinaryImageComponent.ts TypeScript implementation for browser-side streaming and caching
src/Components/Web.JS/src/GlobalExports.ts Exports BinaryImageComponent to global Blazor namespace
src/Components/Web/test/Image/ImageTest.cs Unit tests for Image component functionality
src/Components/test/E2ETest/Tests/ImageTest.cs End-to-end integration tests
src/Components/test/testassets/BasicTestApp/ImageTest/ImageTestComponent.razor Test component for E2E scenarios
src/Components/Web/src/PublicAPI.Unshipped.txt Public API surface additions
src/Components/test/testassets/BasicTestApp/_Imports.razor Adds namespace import for test app
src/Components/test/testassets/BasicTestApp/Index.razor Adds test component to navigation
src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj Adds project reference

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Aug 21, 2025
@javiercn javiercn removed the community-contribution Indicates that the PR has been added by a community member label Aug 22, 2025
Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Have some minimal feedback on a couple things and a couple things on the tests, but other than that it looks great!

@rolandVi
Copy link
Contributor Author

updated

@Pinox
Copy link

Pinox commented Aug 27, 2025

I have a general question about this component. Does it support any kind of retry functionality?

Sometimes when there are lots of images on the same page (say like 100), a few of them might not render properly. I was wondering if there's a mechanism that allows the component to retry fetching the image data, ideally without needing to refresh the entire page or trigger a full rerender.

@javiercn
Copy link
Member

I have a general question about this component. Does it support any kind of retry functionality?

Sometimes when there are lots of images on the same page (say like 100), a few of them might not render properly. I was wondering if there's a mechanism that allows the component to retry fetching the image data, ideally without needing to refresh the entire page or trigger a full rerender.

Retry is up to you. Same as if you loaded an img tag and the fetch request failed for some reason.

We can't automagically retry because we don't require the stream to be seekable and we don't plan on buffering

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Congrats @rolandVi on getting this done!

@javiercn javiercn merged commit 2366f3d into dotnet:main Aug 28, 2025
29 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 11.0-preview1 milestone Aug 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants