Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions aspnetcore/blazor/components/quickgrid.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,18 +138,36 @@ To provide a UI for pagination, add a [`Paginator` component](xref:Microsoft.Asp

In the running app, page through the items using a rendered `Paginator` component.

QuickGrid renders additional empty rows to fill in the final page of data when used with a `Paginator` component. In .NET 9 or later, empty data cells (`<td></td>`) are added to the empty rows. The empty rows are intended to facilitate rendering the QuickGrid with stable row height and styling across all pages. You can apply styles to the rows using [CSS isolation](xref:blazor/components/css-isolation) by wrapping the `QuickGrid` component in a wrapper element, such as a `<div>`, and applying a row style with `::deep` [pseudo-elements](https://developer.mozilla.org/docs/Web/CSS/Pseudo-elements):
QuickGrid renders additional empty rows to fill in the final page of data when used with a `Paginator` component. In .NET 9 or later, empty data cells (`<td></td>`) are added to the empty rows. The empty rows are intended to facilitate rendering the QuickGrid with stable row height and styling across all pages.

## Apply row styles

Apply styles to rows using [CSS isolation](xref:blazor/components/css-isolation), which can include styling empty rows for `QuickGrid` components that [page data with a `Paginator` component](#page-items-with-a-paginator-component).

Wrap the `QuickGrid` component in a wrapper block element, for example a `<div>`:

```diff
+ <div>
<QuickGrid ...>
...
</QuickGrid>
+ </div>
```

Apply a row style with the `::deep` [pseudo-element](https://developer.mozilla.org/docs/Web/CSS/Pseudo-elements). In the following example, row height is set to `2em`, including for empty data rows.

`{COMPONENT}.razor.css`:

```css
::deep tr {
height: 2em;
}
```

To hide the empty row data cells rendered by the QuickGrid, use CSS styling. In the following isolated CSS styles:
Alternatively, use the following CSS styling approach:

* Row cells populated with data are displayed.
* Empty row data cells aren't displayed, which avoids empty row cell borders from rendering per Bootstrap styling.
* Display row cells populated with data.
* Don't display empty row cells, which avoids empty row cell borders from rendering per Bootstrap styling.

`{COMPONENT}.razor.css`:

Expand Down
16 changes: 5 additions & 11 deletions aspnetcore/blazor/file-uploads.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,15 @@ public class UploadResult

A security best practice for production apps is to avoid sending error messages to clients that might reveal sensitive information about an app, server, or network. Providing detailed error messages can aid a malicious user in devising attacks on an app, server, or network. The example code in this section only sends back an error code number (`int`) for display by the component client-side if a server-side error occurs. If a user requires assistance with a file upload, they provide the error code to support personnel for support ticket resolution without ever knowing the exact cause of the error.

<!-- UPDATE 9.0 HOLD moniker range="< aspnetcore-9.0" -->
<!-- UPDATE 10.0 HOLD moniker range="< aspnetcore-9.0" Tracking PU bug: https://github.com/dotnet/aspnetcore/issues/47301 -->

The following `LazyBrowserFileStream` class defines a custom stream type that lazily calls <xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.OpenReadStream%2A> just before the first bytes of the stream are requested. The stream isn't transmitted from the browser to the server until reading the stream begins in .NET.

`LazyBrowserFileStream.cs`:

<!-- UPDATE 9.0 HOLD moniker-end -->
<!-- UPDATE 10.0 HOLD moniker-end -->

<!-- UPDATE 9.0 HOLD for next line: < aspnetcore-9.0 -->
<!-- UPDATE 10.0 HOLD for next line: < aspnetcore-9.0 -->

:::moniker range=">= aspnetcore-8.0"

Expand Down Expand Up @@ -332,7 +332,7 @@ The following `FileUpload2` component:

:::moniker-end

<!-- UPDATE 9.0 HOLD for the next line: < aspnetcore-9.0 -->
<!-- UPDATE 10.0 HOLD for the next line: < aspnetcore-9.0 -->

:::moniker range=">= aspnetcore-8.0"

Expand Down Expand Up @@ -909,10 +909,6 @@ For more information on SignalR configuration and how to set <xref:Microsoft.Asp

## Maximum parallel invocations per client hub setting

<!-- UPDATE 9.0 Check on a fix for this per
https://github.com/dotnet/aspnetcore/issues/53951
and version if fixed. -->

Blazor relies on <xref:Microsoft.AspNetCore.SignalR.HubOptions.MaximumParallelInvocationsPerClient%2A> set to 1, which is the default value.

Increasing the value leads to a high probability that `CopyTo` operations throw `System.InvalidOperationException: 'Reading is not allowed after reader was completed.'`. For more information, see [MaximumParallelInvocationsPerClient > 1 breaks file upload in Blazor Server mode (`dotnet/aspnetcore` #53951)](https://github.com/dotnet/aspnetcore/issues/53951).
Expand All @@ -925,13 +921,11 @@ The line that calls <xref:Microsoft.AspNetCore.Components.Forms.IBrowserFile.Ope

Possible causes:

<!-- UPDATE 9.0 HOLD: in versions of ASP.NET Core earlier than 9.0 -->

* Using the [Autofac Inversion of Control (IoC) container](https://autofac.org/) instead of the built-in ASP.NET Core dependency injection container. To resolve the issue, set <xref:Microsoft.AspNetCore.SignalR.HubOptions.DisableImplicitFromServicesParameters%2A> to `true` in the [server-side circuit handler hub options](xref:blazor/fundamentals/signalr#server-side-circuit-handler-options). For more information, see [FileUpload: Did not receive any data in the allotted time (`dotnet/aspnetcore` #38842)](https://github.com/dotnet/aspnetcore/issues/38842#issuecomment-1342540950).

* Not reading the stream to completion. This isn't a framework issue. Trap the exception and investigate it further in your local environment/network.

<!-- UPDATE 9.0 HOLD in versions of ASP.NET Core earlier than 9.0
<!-- UPDATE 10.0 HOLD in versions of ASP.NET Core earlier than 9.0
adopt ***either*** of the following approaches: * Upgrade the app to ASP.NET Core 9.0 or later.
with the article version selector set to "ASP.NET Core in .NET 8" or earlier -->

Expand Down
7 changes: 1 addition & 6 deletions aspnetcore/blazor/host-and-deploy/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,7 @@ The service isn't required for Blazor apps hosted in Azure App Service or Azure

:::moniker range=">= aspnetcore-8.0"

<!-- UPDATE 9.0 Update section to only cross-link stateful
reconnect guidance after the feature is
supported with Azure SignalR Service. -->

> [!NOTE]
> [Stateful reconnect](xref:signalr/configuration#configure-stateful-reconnect) (<xref:Microsoft.AspNetCore.SignalR.Client.HubConnectionBuilderHttpExtensions.WithStatefulReconnect%2A>) was released with .NET 8 but isn't currently supported for the Azure SignalR Service. For more information, see [Stateful Reconnect Support? (`Azure/azure-signalr` #1878)](https://github.com/Azure/azure-signalr/issues/1878).
The Azure SignalR Service with SDK [v1.26.1](https://github.com/Azure/azure-signalr/releases/tag/v1.26.1) or later supports [SignalR stateful reconnect](xref:signalr/configuration#configure-stateful-reconnect) (<xref:Microsoft.AspNetCore.SignalR.Client.HubConnectionBuilderHttpExtensions.WithStatefulReconnect%2A>).

:::moniker-end

Expand Down
15 changes: 5 additions & 10 deletions aspnetcore/blazor/hybrid/class-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,20 @@ For more information, see the following articles:

:::moniker-end

<!-- UPDATE 9.0 Ask Beth on a replacement for this

## Sample app

For an example of the scenarios described in this article, see the .NET Podcasts sample app:
For an example of the scenarios described in this article, see the [eShop Reference Application (AdventureWorks) (`dotnet/eShop` GitHub repository)](https://github.com/dotnet/eShop). The .NET MAUI Blazor Hybrid app is in the `src/HybridApp` folder.

* [GitHub repository (`microsoft/dotnet-podcasts`)](https://github.com/microsoft/dotnet-podcasts)
* [Running sample app (Azure Container Apps Service)](https://dotnetpodcasts.azurewebsites.net/)
For a version of the sample app tailored for Azure hosting, see the [`Azure-Samples/eShopOnAzure` GitHub repository](https://github.com/Azure-Samples/eShopOnAzure).

The .NET Podcasts app showcases the following technologies:
The sample app showcases the following technologies:

* [.NET](https://dotnet.microsoft.com/download/dotnet)
* [ASP.NET Core](https://dotnet.microsoft.com/apps/aspnet)
* [Blazor](https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor)
* [.NET MAUI](https://dotnet.microsoft.com/apps/maui)
* [Azure Container Apps](https://azure.microsoft.com/services/container-apps/)
* [Orleans](/dotnet/orleans/overview)

-->
* [.NET Aspire](/dotnet/aspire/get-started/aspire-overview)
* [Docker](https://docs.docker.com/get-started/docker-overview/)

## Share web UI Razor components, code, and static assets

Expand Down
10 changes: 3 additions & 7 deletions aspnetcore/blazor/hybrid/reuse-razor-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,8 @@ For an example, see <xref:blazor/hybrid/tutorials/maui-blazor-web-app#using-inte

:::moniker-end

<!-- UPDATE 9.0 Ask Beth on a replacement for this

## Additional resources

* .NET MAUI Blazor podcast sample app
* [Source code (`microsoft/dotnet-podcasts` GitHub repository)](https://github.com/microsoft/dotnet-podcasts)
* [Live app](https://dotnetpodcasts.azurewebsites.net/)

-->
* eShop Reference Application (AdventureWorks): The .NET MAUI Blazor Hybrid app is in the `src/HybridApp` folder.
* For Azure hosting: [`Azure-Samples/eShopOnAzure` GitHub repository](https://github.com/Azure-Samples/eShopOnAzure)
* For general non-Azure hosting: [`dotnet/eShop` GitHub repository](https://github.com/dotnet/eShop).
4 changes: 2 additions & 2 deletions aspnetcore/blazor/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public static RenderFragment SayHello = @<h1>Hello!</h1>;
}
```

The preceding approach reuses rendering logic without per-component overhead. However, the approach doesn't permit refreshing the subtree of the UI independently, nor does it have the ability to skip rendering the subtree of the UI when its parent renders because there's no component boundary. Assignment to a <xref:Microsoft.AspNetCore.Components.RenderFragment> delegate is only supported in Razor component files (`.razor`), and [event callbacks](xref:blazor/components/event-handling#eventcallback) aren't supported.
The preceding approach reuses rendering logic without per-component overhead. However, the approach doesn't permit refreshing the subtree of the UI independently, nor does it have the ability to skip rendering the subtree of the UI when its parent renders because there's no component boundary. Assignment to a <xref:Microsoft.AspNetCore.Components.RenderFragment> delegate is only supported in Razor component files (`.razor`).

For a non-static field, method, or property that can't be referenced by a field initializer, such as `TitleTemplate` in the following example, use a property instead of a field for the <xref:Microsoft.AspNetCore.Components.RenderFragment>:

Expand Down Expand Up @@ -248,7 +248,7 @@ The [`CascadingValue` component](xref:blazor/components/cascading-values-and-par

Setting `IsFixed` to `true` improves performance if there are a large number of other components that receive the cascaded value. Wherever possible, set `IsFixed` to `true` on cascaded values. You can set `IsFixed` to `true` when the supplied value doesn't change over time.

Where a component passes `this` as a cascaded value, `IsFixed` can also be set to `true`:
Where a component passes `this` as a cascaded value, `IsFixed` can also be set to `true`, because `this` never changes during the component's lifecycle:

```razor
<CascadingValue Value="this" IsFixed="true">
Expand Down
5 changes: 0 additions & 5 deletions aspnetcore/blazor/security/blazor-web-app-with-oidc.md
Original file line number Diff line number Diff line change
Expand Up @@ -731,11 +731,6 @@ At this point, Razor components can adopt [role-based and policy-based authoriza

Use the guidance in this section to implement application roles, ME-ID security groups, and ME-ID built-in administrator roles for apps using [Microsoft Entra ID (ME-ID)](https://www.microsoft.com/security/business/microsoft-entra).

<!-- UPDATE 9.0 If we end up with a BWA + MS Identity Web article
and sample, this section will be removed in favor
of coverage of using Graph for groups/roles in the
new article. -->

The approach described in this section configures ME-ID to send groups and roles in the authentication cookie header. When users are only a member of a few security groups and roles, the following approach should work for most hosting platforms without running into a problem where headers are too long, for example with IIS hosting that has a default header length limit of 16 KB (`MaxRequestBytes`). If header length is a problem due to high group or role membership, we recommend not following the guidance in this section in favor of implementing [Microsoft Graph](/graph/sdks/sdks-overview) to obtain a user's groups and roles from ME-ID separately, an approach that doesn't inflate the size of the authentication cookie. For more information, see [Bad Request - Request Too Long - IIS Server (`dotnet/aspnetcore` #57545)](https://github.com/dotnet/aspnetcore/issues/57545).

Configure the role claim type (<xref:Microsoft.IdentityModel.Tokens.TokenValidationParameters.RoleClaimType?displayProperty=nameWithType>) in <xref:Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions> of `Program.cs`. Set the value to `roles`:
Expand Down
4 changes: 0 additions & 4 deletions aspnetcore/blazor/security/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,6 @@ For a description on how global interactive render modes are applied to non-Iden

For more information on persisting prerendered state, see <xref:blazor/components/prerender#persist-prerendered-state>.

<!-- UPDATE 9.0 Remove blog post cross-link -->

For more information on the Blazor Identity UI and guidance on integrating external logins through social websites, see [What's new with identity in .NET 8](https://devblogs.microsoft.com/dotnet/whats-new-with-identity-in-dotnet-8/#the-blazor-identity-ui).

[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)]

### Manage authentication state in Blazor Web Apps
Expand Down
4 changes: 1 addition & 3 deletions aspnetcore/blazor/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,5 @@ The following actions take place at each step of the test:

## Additional resources

<!-- UPDATE 9.0 Check on staleness of Oslo talk link -->

* [Getting Started with bUnit](https://bunit.dev/docs/getting-started/): bUnit instructions include guidance on creating a test project, referencing testing framework packages, and building and running tests.
* [How to create maintainable and testable Blazor components - Egil Hansen - NDC Oslo 2022](https://www.youtube.com/watch?v=L_n-12FglLI)
* [Blazor Testing from A to Z - Egil Hansen - Swetugg Stockholm 2024](https://youtu.be/GU_XbWjrP_g?si=1SrjeaP1T9LdFegN) ([Swetugg](https://www.swetugg.se/sthlm-2025))
2 changes: 1 addition & 1 deletion aspnetcore/blazor/tutorials/movie-database-app/part-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ When you select the **:::no-loc text="Create":::** button, the movie data is pos

:::moniker range=">= aspnetcore-9.0"

<!-- UPDATE 9.0 Revert when debugger is updated -->
<!-- UPDATE 10.0 Revert when https://github.com/dotnet/aspnetcore/issues/53996 is addressed -->

:::zone pivot="vs"

Expand Down
42 changes: 40 additions & 2 deletions aspnetcore/blazor/tutorials/movie-database-app/part-8.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ Run the app and navigate to the movies `Index` page. You can page through the mo

The component is *interactive*. The page doesn't reload for paging to occur. The paging is performed live over the SignalR connection between the browser and the server, where the paging operation is performed on the server with the rendered result sent back to the client for the browser to display.

Change <xref:Microsoft.AspNetCore.Components.QuickGrid.PaginationState.ItemsPerPage%2A> to a more reasonable value, such as 10 items per page:
Change <xref:Microsoft.AspNetCore.Components.QuickGrid.PaginationState.ItemsPerPage%2A> to a more reasonable value, such as five items per page:

```diff
- private PaginationState pagination = new PaginationState { ItemsPerPage = 2 };
+ private PaginationState pagination = new PaginationState { ItemsPerPage = 10 };
+ private PaginationState pagination = new PaginationState { ItemsPerPage = 5 };
```

## Sortable `QuickGrid`
Expand Down Expand Up @@ -199,6 +199,44 @@ Filtering database records is performed on the server, and the server interactiv

Instead of an HTML form, submitting a GET request in this scenario could've also used JavaScript to submit the request to the server, either using the [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API)` or [XMLHttpRequest API](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest). In most cases, JavaScript can be replaced by using Blazor and C# in an interactive component.

## Style the `QuickGrid` component

You can apply styles to the rendered `QuickGrid` component with a stylesheet isolated to the `Index` component using *CSS isolation*.

CSS isolation is applied by adding a stylesheet file using the file name format `{COMPONENT NAME}.razor.css`, where the `{COMPONENT NAME}` placeholder is the component name.

To apply styles to a child component, such as the `QuickGrid` component of the `Index` component, use the `::deep` pseudo-element.

In the `MoviePages` folder, add the following stylesheet for the `Index` component. Use `::deep` pseudo-elements to make the row height `3em` and vertically center the table cell content.

`Components/Pages/MoviePages/Index.razor.css`:

```css
::deep tr {
height: 3em;
}

::deep tr > td {
vertical-align: middle;
}
```

The `::deep` pseudo-element only works with descendant elements, so the `QuickGrid` component must be wrapped with a `<div>` or some other block-level element in order to apply the styles to it.

In `Components/Pages/MoviePages/Index.razor`, place `<div>` tags around the `QuickGrid` component:

```diff
+ <div>
<QuickGrid ...>
...
</QuickGrid>
+ </div>
```

Blazor rewrites CSS selectors to match the markup rendered by the component. The rewritten CSS styles are bundled and produced as a static asset, so you don't need to take further action to apply the styles to the rendered `QuickGrid` component.

![Movie list showing row heights at 3em with vertically-centered content](~/blazor/tutorials/movie-database-app/part-8/_static/styled-quickgrid.png)

## Clean up

:::zone pivot="vs"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading