---
title: ASP.NET Core Blazor component virtualization
author: guardrex
description: Learn how to use component virtualization in ASP.NET Core Blazor apps.
monikerRange: '>= aspnetcore-3.1'
ms.author: riande
ms.custom: mvc
ms.date: 10/02/2020
no-loc: ["ASP.NET Core Identity", cookie, Cookie, Blazor, "Blazor Server", "Blazor WebAssembly", "Identity", "Let's Encrypt", Razor, SignalR]
uid: blazor/components/virtualization
---
# ASP.NET Core Blazor component virtualization
By [Daniel Roth](https://github.com/danroth27)
Improve the perceived performance of component rendering using the Blazor framework's built-in virtualization support. Virtualization is a technique for limiting UI rendering to just the parts that are currently visible. For example, virtualization is helpful when the app must render a long list of items and only a subset of items is required to be visible at any given time. Blazor provides the `Virtualize` component that can be used to add virtualization to an app's components.
::: moniker range=">= aspnetcore-5.0"
Without virtualization, a typical list might use a C# [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop to render each item in the list:
```razor
@foreach (var employee in employees)
{
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
}
```
If the list contains thousands of items, then rendering the list may take a long time. The user may experience a noticeable UI lag.
Instead of rendering each item in the list all at one time, replace the [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop with the `Virtualize` component and specify a fixed item source with `Items`. Only the items that are currently visible are rendered:
```razor
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
```
If not specifying a context to the component with `Context`, use the `context` value (`@context.{PROPERTY}`) in the item content template:
```razor
@context.FirstName @context.LastName has the
job title of @context.JobTitle.
```
The `Virtualize` component calculates how many items to render based on the height of the container and the size of the rendered items.
The item content for the `Virtualize` component can include:
* Plain HTML and Razor code, as the preceding example shows.
* One or more Razor components.
* A mix of HTML/Razor and Razor components.
## Item provider delegate
If you don't want to load all of the items into memory, you can specify an items provider delegate method to the component's `ItemsProvider` parameter that asynchronously retrieves the requested items on demand:
```razor
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
```
The items provider receives an `ItemsProviderRequest`, which specifies the required number of items starting at a specific start index. The items provider then retrieves the requested items from a database or other service and returns them as an `ItemsProviderResult` along with a count of the total items. The items provider can choose to retrieve the items with each request or cache them so that they're readily available. Don't attempt to use an items provider and assign a collection to `Items` for the same `Virtualize` component.
The following example loads employees from an `EmployeeService`:
```csharp
private async ValueTask> LoadEmployees(
ItemsProviderRequest request)
{
var numEmployees = Math.Min(request.Count, totalEmployees - request.StartIndex);
var employees = await EmployeesService.GetEmployeesAsync(request.StartIndex,
numEmployees, request.CancellationToken);
return new ItemsProviderResult(employees, totalEmployees);
}
```
## Placeholder
Because requesting items from a remote data source might take some time, you have the option to render a placeholder (`...`) until the item data is available:
```razor
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
Loading…
```
## Item size
The size of each item in pixels can be set with `ItemSize` (default: 50px):
```razor
...
```
## Overscan count
`OverscanCount` determines how many additional items are rendered before and after the visible region. This setting helps to reduce the frequency of rendering during scrolling. However, higher values result in more elements rendered in the page (default: 3):
```razor
...
```
::: moniker-end
::: moniker range="< aspnetcore-5.0"
For example, a grid or list that renders hundreds of rows containing components is processor intensive to render. Consider virtualizing a grid or list layout so that only a subset of the components is rendered at any given time.
The following `Virtualize` component (`Virtualize.cs`) implements to render child content based on user scrolling:
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.JSInterop;
public class Virtualize : ComponentBase
{
[Parameter]
public string TagName { get; set; } = "div";
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public ICollection Items { get; set; }
[Parameter]
public double ItemHeight { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary Attributes { get; set; }
[Inject]
IJSRuntime JS { get; set; }
ElementReference contentElement;
int numItemsToSkipBefore;
int numItemsToShow;
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
// Render actual content
builder.OpenElement(0, TagName);
builder.AddMultipleAttributes(1, Attributes);
var translateY = numItemsToSkipBefore * ItemHeight;
builder.AddAttribute(2, "style", $"transform: translateY({ translateY }px);");
builder.AddAttribute(2, "data-translateY", translateY);
builder.AddElementReferenceCapture(3, @ref => { contentElement = @ref; });
// As an important optimization, *don't* use builder.AddContent(seq, ChildContent, item)
// because that implicitly wraps a new region around each item, which in turn means that
// @key does nothing (because keys are scoped to regions). Instead, create a single
// container region and then invoke the fragments directly.
builder.OpenRegion(4);
foreach (var item in Items.Skip(numItemsToSkipBefore).Take(numItemsToShow))
{
ChildContent(item)(builder);
}
builder.CloseRegion();
builder.CloseElement();
// Also emit a spacer that causes the total vertical height to add up to
// Items.Count()*numItems
builder.OpenElement(5, "div");
var numHiddenItems = Items.Count - numItemsToShow;
builder.AddAttribute(6, "style",
$"width: 1px; height: { numHiddenItems * ItemHeight }px;");
builder.CloseElement();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var objectRef = DotNetObjectReference.Create(this);
var initResult = await JS.InvokeAsync(
"VirtualizedComponent._initialize", objectRef, contentElement);
OnScroll(initResult);
}
}
[JSInvokable]
public void OnScroll(ScrollEventArgs args)
{
var relativeTop = args.ContainerRect.Top - args.ContentRect.Top;
numItemsToSkipBefore = Math.Max(0, (int)(relativeTop / ItemHeight));
var visibleHeight = args.ContainerRect.Bottom -
(args.ContentRect.Top + numItemsToSkipBefore * ItemHeight);
numItemsToShow = (int)Math.Ceiling(visibleHeight / ItemHeight) + 3;
StateHasChanged();
}
public class ScrollEventArgs
{
public DOMRect ContainerRect { get; set; }
public DOMRect ContentRect { get; set; }
}
public class DOMRect
{
public double Top { get; set; }
public double Bottom { get; set; }
public double Left { get; set; }
public double Right { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
}
```
The following `FetchData` component (`FetchData.razor`) uses the preceding `Virtualize` component to display 25 rows of weather data at a time:
```razor
@page "/"
@page "/fetchdata"
@inject HttpClient Http
Weather forecast
This component demonstrates fetching data from a service.
@if (forecasts == null)
{
Loading...
}
else
{
Using table-layout: fixed
@context.Date.ToShortDateString() |
@context.TemperatureC |
@context.TemperatureF |
@context.Summary |
Using position: sticky
Date |
Temperature (C) |
Temperature (F) |
Summary |
@context.Date.ToShortDateString() |
@context.TemperatureC |
@context.TemperatureF |
@context.Summary |
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync(
"sample-data/weather.json");
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
```
In the preceding example, the approach is about avoiding absolute positioning for each individual item. Absolute positioning would have some advantages (we can be sure the items do take up the specified amount of Y space) but has the bad disadvantage that you lose the normal widths and can't get table columns to line up across rows/header when based on content sizing.
The concept behind the design of the `Virtualize` component is that the component doesn't change how the items are laid out within the DOM. There are no added wrapper elements, besides the single one whose `TagName` you specify.
The best approach is to avoid even the `TagName` wrapper element. Have the `Virtualize` component emit no elements of its own. All the component does is render however many of the template instances are required to fill up any remaining visible space in the closest scrollable ancestor, plus add a following spacer element that makes the scrollable ancestor have the right scrolling range. As far as the layout is concerned, it's the same as if the full range of children were physically in the DOM. It does require you to specify an accurate `ItemHeight` though. If you get it wrong (for example, because you're confused and think it's valid to specify `style.height` on a ``), then the component ends up rendering the wrong number of template instances and either not fill up the UI or inefficiently render too many. Additionally, the scroll range on the parent won't be correct.
::: moniker-end
## State changes
When making changes to items rendered by the `Virtualize` component, call to force re-evaluation and rerendering of the component.