diff --git a/.github/workflows/check-for-build-warnings.yml b/.github/workflows/check-for-build-warnings.yml index 26eb1e2eec77..a8ed666c7b47 100644 --- a/.github/workflows/check-for-build-warnings.yml +++ b/.github/workflows/check-for-build-warnings.yml @@ -8,7 +8,14 @@ jobs: status_checker_job: name: Look for build warnings runs-on: ubuntu-latest + permissions: + statuses: write + issues: write + pull-requests: write steps: + - uses: actions/checkout@v3 - uses: dotnet/docs-actions/actions/status-checker@main with: - repo-token: ${{ secrets.GITHUB_TOKEN }} + repo_token: ${{ secrets.GITHUB_TOKEN }} + docs_path: "aspnetcore" + url_base_path: "aspnet/core" diff --git a/.github/workflows/dependencies/dotnet-whatsnew.2.1.4.nupkg b/.github/workflows/dependencies/dotnet-whatsnew.2.1.4.nupkg deleted file mode 100644 index 86c7ca702b81..000000000000 Binary files a/.github/workflows/dependencies/dotnet-whatsnew.2.1.4.nupkg and /dev/null differ diff --git a/.github/workflows/dependencies/run-dotnet-whatsnew.sh b/.github/workflows/dependencies/run-dotnet-whatsnew.sh deleted file mode 100755 index 496ae05fc413..000000000000 --- a/.github/workflows/dependencies/run-dotnet-whatsnew.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -set -e - -# This script runs the .NET CLI, invoking the what's new global tool -# $1 is the -# $2 is the -# $3 is the savedir - -while getopts o:r:s: option -do -case "${option}" -in -o) OWNER=${OPTARG};; -r) REPO=${OPTARG};; -s) SAVEDIR=${OPTARG};; -esac -done - -dotnet whatsnew \ - --owner $OWNER \ - --repo $REPO \ - --savedir $SAVEDIR diff --git a/.github/workflows/holiday-blazor-comment.yml b/.github/workflows/holiday-blazor-comment.yml new file mode 100644 index 000000000000..37ea49fd033f --- /dev/null +++ b/.github/workflows/holiday-blazor-comment.yml @@ -0,0 +1,35 @@ +name: Add comment +on: + issues: + types: + - labeled +jobs: + add-comment: + if: github.event.label.name == 'Blazor2' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add comment + uses: peter-evans/create-or-update-comment@v2.1.0 + with: + issue-number: ${{ github.event.issue.number }} + token: ${{ secrets.GITHUB_TOKEN }} + body: | + ### :snowman: ***OOF for the holidays*** :snowflake: + + This issue has been marked for triage on the `Blazor.Docs` project, and I'll respond as soon as I return from the holidays. I'll get back to you *ASAP* 🏃. + + We only work on documentation on this repo. If you need product support, close this issue and seek assistance through one or more of the following support channels: + + * [Stack Overflow (tagged: `blazor`)](https://stackoverflow.com/questions/tagged/blazor) + * [General ASP.NET Core Slack Team](https://join.slack.com/t/aspnetcore/shared_invite/zt-1mv5487zb-EOZxJ1iqb0A0ajowEbxByQ) + * [Blazor Gitter](https://gitter.im/aspnet/Blazor) + + If you think that you found a potential bug in the framework or have product feedback, close this issue and open a new issue for the ASP.NET Core product unit at [dotnet/aspnetcore issues](https://github.com/dotnet/aspnetcore/issues). Bug reports require a clear explanation of the problem, usually including a minimal repro project placed on GitHub for the product unit engineers to download and run. If you determine with the product unit that it isn't a bug but merely requires documentation, please re-open this docs issue and place a cross-link to your engineering issue discussion. I'll take it up with you when I return from vacation. + + For problems or feedback on Visual Studio or Visual Studio for Mac, close this issue and use the [**Report a Problem**](https://docs.microsoft.com/visualstudio/ide/how-to-report-a-problem-with-visual-studio) or [**Suggest a Feature**](https://docs.microsoft.com/visualstudio/ide/suggest-a-feature) processes from within VS, which open internal issues for VS teams. For more information, see [Visual Studio Feedback](https://developercommunity.visualstudio.com/home) or [How to report a problem in Visual Studio for Mac](https://docs.microsoft.com/visualstudio/mac/report-a-problem). + + For problems with Visual Studio Code, close this issue and ask for support on community support forums. For bug reports and product feedback, open an issue on the [microsoft/vscode GitHub repo](https://github.com/microsoft/vscode/issues). + + 💃🕺🥳 Happy New Year! 🎈🎆🍾🥂🎉 See you in 2023! diff --git a/.github/workflows/oof-blazor-comment.yml b/.github/workflows/oof-blazor-comment.yml new file mode 100644 index 000000000000..7232966fa682 --- /dev/null +++ b/.github/workflows/oof-blazor-comment.yml @@ -0,0 +1,33 @@ +name: Add comment +on: + issues: + types: + - labeled +jobs: + add-comment: + if: github.event.label.name == 'Blazor' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Add comment + uses: peter-evans/create-or-update-comment@v2.1.0 + with: + issue-number: ${{ github.event.issue.number }} + token: ${{ secrets.GITHUB_TOKEN }} + body: | + ## 🏖️ ***OOF until Wednesday, June 28*** 🍹 + + Hello! I'm out of the office for a couple of days on vacation. This issue has been marked for triage on the `Blazor.Docs` project, and I'll respond as soon as I return from vacation. I'll get back to you *ASAP* 🏃. + + We only work on documentation on this repo. If you need product support, close this issue and seek assistance through one or more of the following support channels: + + * [Stack Overflow (tagged: `blazor`)](https://stackoverflow.com/questions/tagged/blazor) + * [General ASP.NET Core Slack Team](https://join.slack.com/t/aspnetcore/shared_invite/zt-1mv5487zb-EOZxJ1iqb0A0ajowEbxByQ) + * [Blazor Gitter](https://gitter.im/aspnet/Blazor) + + If you think that you found a potential bug in the framework or have product feedback, close this issue and open a new issue for the ASP.NET Core product unit at [dotnet/aspnetcore issues](https://github.com/dotnet/aspnetcore/issues). Bug reports require a clear explanation of the problem, usually including a minimal repro project placed on GitHub for the product unit engineers to download and run. If you determine with the product unit that it isn't a bug but merely requires documentation, please re-open this docs issue and place a cross-link to your engineering issue discussion. I'll take it up with you when I return from vacation. + + For problems or feedback on Visual Studio or Visual Studio for Mac, close this issue and use the [**Report a Problem**](https://docs.microsoft.com/visualstudio/ide/how-to-report-a-problem-with-visual-studio) or [**Suggest a Feature**](https://docs.microsoft.com/visualstudio/ide/suggest-a-feature) processes from within VS, which open internal issues for VS teams. For more information, see [Visual Studio Feedback](https://developercommunity.visualstudio.com/home) or [How to report a problem in Visual Studio for Mac](https://docs.microsoft.com/visualstudio/mac/report-a-problem). + + For problems with Visual Studio Code, close this issue and ask for support on community support forums. For bug reports and product feedback, open an issue on the [microsoft/vscode GitHub repo](https://github.com/microsoft/vscode/issues). diff --git a/.github/workflows/quest-bulk.yml b/.github/workflows/quest-bulk.yml new file mode 100644 index 000000000000..444e3f8a52d6 --- /dev/null +++ b/.github/workflows/quest-bulk.yml @@ -0,0 +1,35 @@ +name: "bulk quest import" +on: + schedule: + - cron: '0 10 * * *' # UTC time, that's 5:00 am EST, 2:00 am PST. + workflow_dispatch: + inputs: + reason: + description: "The reason for running the bulk import workflow" + required: true + default: "Initial import into Quest (Azure DevOps)" + +jobs: + bulk-import: + runs-on: ubuntu-latest + permissions: + issues: write + if: ${{ github.repository_owner == 'dotnet' }} + + steps: + - name: "Print manual bulk import run reason" + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "Reason: ${{ github.event.inputs.reason }}" + + - name: bulk-sequester + id: bulk-sequester + uses: dotnet/docs-tools/actions/sequester@main + env: + ImportOptions__ApiKeys__GitHubToken: ${{ secrets.GITHUB_TOKEN }} + ImportOptions__ApiKeys__OSPOKey: ${{ secrets.OSPO_KEY }} + ImportOptions__ApiKeys__QuestKey: ${{ secrets.QUEST_KEY }} + with: + org: ${{ github.repository_owner }} + repo: ${{ github.repository }} + issue: '-1' diff --git a/.github/workflows/quest.yml b/.github/workflows/quest.yml new file mode 100644 index 000000000000..7c2467a612e5 --- /dev/null +++ b/.github/workflows/quest.yml @@ -0,0 +1,59 @@ +name: "quest import" +on: + workflow_dispatch: + inputs: + reason: + description: "The reason for running the workflow" + required: true + default: "Manual run" + issue: + description: "The issue number to manually test" + required: true + +jobs: + import: + if: | + github.event_name == 'workflow_dispatch' || + github.event.label.name == 'reQUEST' || + github.event.label.name == 'seQUESTered' || + contains(github.event.issue.labels.*.name, 'reQUEST') || + contains(github.event.issue.labels.*.name, 'seQUESTered') + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + - name: "Print manual run reason" + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "Reason: ${{ github.event.inputs.reason }}" + echo "Issue number: ${{ github.event.inputs.issue }}" + + # This step occurs when ran manually, passing the manual issue number input + - name: manual-sequester + if: ${{ github.event_name == 'workflow_dispatch' }} + id: manual-sequester + uses: dotnet/docs-tools/actions/sequester@main + env: + ImportOptions__ApiKeys__GitHubToken: ${{ secrets.GITHUB_TOKEN }} + ImportOptions__ApiKeys__OSPOKey: ${{ secrets.OSPO_KEY }} + ImportOptions__ApiKeys__QuestKey: ${{ secrets.QUEST_KEY }} + with: + org: ${{ github.repository_owner }} + repo: ${{ github.repository }} + issue: ${{ github.event.inputs.issue }} + + # This step occurs automatically, passing the issue number from the event + - name: auto-sequester + if: ${{ github.event_name != 'workflow_dispatch' }} + id: auto-sequester + uses: dotnet/docs-tools/actions/sequester@main + env: + ImportOptions__ApiKeys__GitHubToken: ${{ secrets.GITHUB_TOKEN }} + ImportOptions__ApiKeys__OSPOKey: ${{ secrets.OSPO_KEY }} + ImportOptions__ApiKeys__QuestKey: ${{ secrets.QUEST_KEY }} + with: + org: ${{ github.repository_owner }} + repo: ${{ github.repository }} + issue: ${{ github.event.issue.number }} + diff --git a/.github/workflows/whats-new.yml b/.github/workflows/whats-new.yml index 8dc515a3986e..2d01371893e1 100644 --- a/.github/workflows/whats-new.yml +++ b/.github/workflows/whats-new.yml @@ -14,9 +14,6 @@ on: required: true default: 'Manual run' -env: - DOTNET_VERSION: '6.0.x' # set this to the dot net version to use - # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "create-what-is-new" @@ -29,41 +26,26 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - # Runs a single command using the runners shell - - name: 'Print manual run reason' + - name: "Print manual run reason" if: ${{ github.event_name == 'workflow_dispatch' }} run: | echo "Reason: ${{ github.event.inputs.reason }}" - # Print dotnet info - - name: Display .NET info - run: dotnet --info - - # Install dotnet-whatsnew global tool - - name: Install dotnet-whatsnew tool - run: | - dotnet tool install --global --add-source ./.github/workflows/dependencies/ dotnet-whatsnew - - # Run dotnet-whatsnew tool - - name: Generate what's new - id: dotnet-whatsnew + - uses: dotnet/docs-tools/WhatsNew.Cli@main env: GitHubKey: ${{ secrets.GITHUB_TOKEN }} OspoKey: ${{ secrets.OSPO_KEY }} - run: | - ./.github/workflows/dependencies/run-dotnet-whatsnew.sh -o dotnet -r AspNetCore.Docs -s './aspnetcore/whats-new' + with: + owner: dotnet + repo: AspNetCore.Docs + savedir: './aspnetcore/whats-new' - # Create the PR for the new article - name: create-pull-request - uses: peter-evans/create-pull-request@v3.10.0 + uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 with: - title: 'What''s new article' + branch: create-whatsnew-pull-request/patch + title: "What's new article" commit-message: 'Bot 🤖 generated "What''s new article"' - body: 'Automated creation of What''s new article.' + body: "Automated creation of What's new article." diff --git a/.gitignore b/.gitignore index b51ce4e60b32..08bf0c26bef2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ # with 2.1 templates and excluding .min & .map -> 27 files and < 1 KB # 3.0 templates should be even less # wwwroot/lib/ -# When wwwroot/lib/ is commented out, excluse .min and .map files +# When wwwroot/lib/ is commented out, exclude .min and .map files *.min.css *.min.js *.map diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index e04c75211bc0..23b7688d9cf4 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -1,5 +1,20 @@ { "redirections": [ + { + "source_path": "aspnetcore/web-api/route-to-code.md", + "redirect_url": "/aspnet/core/fundamentals/minimal-apis/overview", + "redirect_document_id": false + }, + { + "source_path": "aspnetcore/fundamentals/logging/loggermessage.md", + "redirect_url": "/dotnet/core/extensions/high-performance-logging", + "redirect_document_id": false + }, + { + "source_path": "aspnetcore/client-side/spa/react-with-redux.md", + "redirect_url": "/aspnet/core/client-side/spa/intro", + "redirect_document_id": false + }, { "source_path": "aspnet/core/host-and-deploy/azure-apps/azure-continuous-deployment.md", "redirect_url": "/azure/app-service/deploy-continuous-deployment", @@ -292,9 +307,14 @@ }, { "source_path": "aspnetcore/performance/measuring.md", - "redirect_url": "/aspnet/core/performance/", + "redirect_url": "/aspnet/core/performance/overview", "redirect_document_id": false }, + { + "source_path": "aspnetcore/performance/performance-best-practices.md", + "redirect_url": "/aspnet/core/fundamentals/best-practices", + "redirect_document_id": true + }, { "source_path": "aspnetcore/security/authentication/oauth2.md", "redirect_url": "/aspnet/core/security/authentication/", @@ -822,7 +842,7 @@ }, { "source_path": "aspnetcore/razor-components/forms-validation.md", - "redirect_url": "/aspnet/core/blazor/forms-validation", + "redirect_url": "/aspnet/core/blazor/forms-and-input-components", "redirect_document_id": false }, { @@ -1173,6 +1193,40 @@ "source_path": "aspnetcore/tutorials/signalr-blazor.md", "redirect_url": "/aspnet/core/blazor/tutorials/signalr-blazor", "redirect_document_id": false + }, + { + "source_path": "aspnetcore/blazor/forms-validation.md", + "redirect_url": "/aspnet/core/blazor/forms-and-input-components", + "redirect_document_id": false + }, + { + "source_path": "aspnetcore/grpc/httpapi.md", + "redirect_url": "/aspnet/core/grpc/json-transcoding", + "redirect_document_id": false + }, + { + "source_path": "aspnetcore/whats-new/dotnet-AspNetCore.Docs-2022-03-01.md", + "redirect_url": "/aspnet/whats-new/dotnet-AspNetCore.Docs-mod5" + }, + { + "source_path": "aspnetcore/whats-new/dotnet-AspNetCore.Docs-2022-04-01.md", + "redirect_url": "/aspnet/whats-new/dotnet-AspNetCore.Docs-mod0" + }, + { + "source_path": "aspnetcore/whats-new/dotnet-AspNetCore.Docs-2022-07-01.md", + "redirect_url": "/aspnet/whats-new/dotnet-AspNetCore.Docs-mod1" + }, + { + "source_path": "aspnetcore/whats-new/dotnet-AspNetCore.Docs-2022-08-01.md", + "redirect_url": "/aspnet/whats-new/dotnet-AspNetCore.Docs-mod2" + }, + { + "source_path": "aspnetcore/whats-new/dotnet-AspNetCore.Docs-2022-09-01.md", + "redirect_url": "/aspnet/whats-new/dotnet-AspNetCore.Docs-mod3" + }, + { + "source_path": "aspnetcore/whats-new/dotnet-AspNetCore.Docs-2022-10-01.md", + "redirect_url": "/aspnet/whats-new/dotnet-AspNetCore.Docs-mod4" } ] } diff --git a/.repoman.yml b/.repoman.yml index a88ad7479adb..50e6f3d2b74d 100644 --- a/.repoman.yml +++ b/.repoman.yml @@ -1,4 +1,4 @@ -revision: 2 +revision: 3 schema-version: 1 owner-ms-alias: adegeo @@ -31,6 +31,7 @@ issues: - labels-add: ["Blazor"] - projects-add: [35] - labels-remove: [":watch: Not Triaged"] + - assignee-add: ["guardrex"] - check: - type: metadata-comment name: technology @@ -53,6 +54,7 @@ issues: - labels-add: ["Blazor"] - projects-add: [35] - labels-remove: [":watch: Not Triaged"] + - assignee-add: ["guardrex"] - check: - type: metadata-comment name: content source @@ -61,6 +63,16 @@ issues: - labels-add: ["Blazor"] - projects-add: [35] - labels-remove: [":watch: Not Triaged"] + - assignee-add: ["guardrex"] + - check: + - type: metadata-comment + name: content source + value: "(?i).*main\/aspnetcore\/client-side\/dotnet-interop.md" + pass: + - labels-add: ["Blazor"] + - projects-add: [35] + - labels-remove: [":watch: Not Triaged"] + - assignee-add: ["guardrex", "pavelsavara"] # If the word appears anywhere in the file name path, add the label - check: @@ -71,6 +83,7 @@ issues: - labels-add: ["Blazor"] - projects-add: [35] - labels-remove: [":watch: Not Triaged"] + - assignee-add: ["guardrex"] - check: - type: metadata-comment name: content source diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed0e8cdd7a5e..181251fd4103 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contribute to the ASP.NET Core documentation -This document covers the process for contributing to the articles and code samples that are hosted on the [ASP.NET documentation site](https://docs.microsoft.com/aspnet/). Typo corrections and new articles are welcome contributions. +This document covers the process for contributing to the articles and code samples that are hosted on the [ASP.NET documentation site](https://learn.microsoft.com/aspnet/). Typo corrections and new articles are welcome contributions. ## How to make a simple correction or suggestion @@ -16,7 +16,7 @@ You need a basic understanding of [Git and GitHub.com](https://guides.github.com * If your PR has the label 'cla-required' assigned, [complete the Contribution License Agreement (CLA)](https://cla.dotnetfoundation.org/). * Respond to PR feedback. -For an example where this process led to publication of a new article, see [Issue 1477](https://github.com/dotnet/docs/issues/1477) and [Pull Request 18955](https://github.com/dotnet/docs/pull/18955) in the .NET Docs repository. The new article is [Use code coverage for unit testing](https://docs.microsoft.com/dotnet/core/testing/unit-testing-code-coverage). +For an example where this process led to publication of a new article, see [Issue 1477](https://github.com/dotnet/docs/issues/1477) and [Pull Request 18955](https://github.com/dotnet/docs/pull/18955) in the .NET Docs repository. The new article is [Use code coverage for unit testing](https://learn.microsoft.com/dotnet/core/testing/unit-testing-code-coverage). ## Docs Authoring Pack extension in Visual Studio Code @@ -84,7 +84,7 @@ To render a portion of a file as a snippet by using line numbers: [!code-html[](configuration/index/sample/Views/Home/Index.cshtml?range=1-10,20,30,40-50)] ``` -For C# snippets, reference a [C# region](https://docs.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-region). Whenever possible, use regions rather than line numbers because line numbers in a code file tend to change and become out of sync with line number references in Markdown. C# regions can be nested. If referencing the outer region, the inner `#region` and `#endregion` directives aren't rendered in a snippet. +For C# snippets, reference a [C# region](https://learn.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-region). Whenever possible, use regions rather than line numbers because line numbers in a code file tend to change and become out of sync with line number references in Markdown. C# regions can be nested. If referencing the outer region, the inner `#region` and `#endregion` directives aren't rendered in a snippet. To render a C# region named "snippet_Example": @@ -142,7 +142,7 @@ Markdown: ## Test changes with DocFX -Test your changes with the [DocFX command-line tool](https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html#2-use-docfx-as-a-command-line-tool), which creates a locally hosted version of the site. DocFX doesn't render style and site extensions created for docs.microsoft.com. +Test your changes with the [DocFX command-line tool](https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html#2-use-docfx-as-a-command-line-tool), which creates a locally hosted version of the site. DocFX doesn't render style and site extensions created for Microsoft Docs. DocFX requires: @@ -192,7 +192,7 @@ Our goal is to write documentation that is easily understandable by the widest p ## Microsoft Writing Style Guide -The [Microsoft Writing Style Guide](https://docs.microsoft.com/style-guide/welcome/) provides writing style and terminology guidance for all forms of technology communication, including the ASP.NET Core documentation. +The [Microsoft Writing Style Guide](https://learn.microsoft.com/style-guide/welcome/) provides writing style and terminology guidance for all forms of technology communication, including the ASP.NET Core documentation. ## Redirects diff --git a/README.md b/README.md index 61d00c55df17..f4773ee76039 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # ASP.NET Core Docs -This repository contains the conceptual ASP.NET Core documentation hosted at [docs.microsoft.com/aspnet/core](https://docs.microsoft.com/aspnet/core/getting-started). See the [Contributing Guide](CONTRIBUTING.md) and the [issues list](https://github.com/dotnet/AspNetCore.Docs/issues) if you would like to help. +This repository contains the ASP.NET Core documentation. See the [Contributing Guide](CONTRIBUTING.md) and the [issues list](https://github.com/dotnet/AspNetCore.Docs/issues) if you would like to help. -API documentation changes are made in the [AspNetApiDocs repository](https://github.com/dotnet/AspNetApiDocs) against the triple slash `///` comments. +API documentation changes are made in the [`dotnet/AspNetApiDocs` GitHub repository](https://github.com/dotnet/AspNetApiDocs) against the triple slash `///` comments. -ASP.NET 4.x documentation changes are made in the [dotnet/AspNetDocs repository](https://github.com/dotnet/AspNetDocs). +ASP.NET 4.x documentation changes are made in the [`dotnet/AspNetDocs` GitHub repository](https://github.com/dotnet/AspNetDocs). + +Issues for learn.microsoft.com can be opened at [github.com/MicrosoftDocs/feedback](https://github.com/MicrosoftDocs/feedback) diff --git a/aspnetcore/_static/toc-selector.png b/aspnetcore/_static/toc-selector.png new file mode 100644 index 000000000000..a922f1b828fb Binary files /dev/null and b/aspnetcore/_static/toc-selector.png differ diff --git a/aspnetcore/_static/version-selector.png b/aspnetcore/_static/version-selector.png new file mode 100644 index 000000000000..393f5ac5ff72 Binary files /dev/null and b/aspnetcore/_static/version-selector.png differ diff --git a/aspnetcore/blazor/advanced-scenarios.md b/aspnetcore/blazor/advanced-scenarios.md index b3ef6b556b11..d78592e761b2 100644 --- a/aspnetcore/blazor/advanced-scenarios.md +++ b/aspnetcore/blazor/advanced-scenarios.md @@ -5,18 +5,18 @@ description: Learn how to incorporate manual logic for building Blazor render tr monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/advanced-scenarios --- # ASP.NET Core Blazor advanced scenarios (render tree construction) +[!INCLUDE[](~/includes/not-latest-version.md)] + This article describes the advanced scenario for building Blazor render trees manually with . > [!WARNING] > Use of to create components is an *advanced scenario*. A malformed component (for example, an unclosed markup tag) can result in undefined behavior. Undefined behavior includes broken content rendering, loss of app features, and ***compromised security***. -:::moniker range=">= aspnetcore-6.0" - ## Manually build a render tree (`RenderTreeBuilder`) provides methods for manipulating components and elements, including building components manually in C# code. @@ -25,7 +25,7 @@ Consider the following `PetDetails` component, which can be manually rendered in `Shared/PetDetails.razor`: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/advanced-scenarios/PetDetails.razor"::: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/advanced-scenarios/PetDetails.razor"::: In the following `BuiltContent` component, the loop in the `CreateComponent` method generates three `PetDetails` components. @@ -33,7 +33,7 @@ In methods wi `Pages/BuiltContent.razor`: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/advanced-scenarios/BuiltContent.razor" highlight="6,16-24,28"::: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/advanced-scenarios/BuiltContent.razor" highlight="6,16-24,28"::: > [!WARNING] > The types in allow processing of the *results* of rendering operations. These are internal details of the Blazor framework implementation. These types should be considered *unstable* and subject to change in future releases. @@ -127,235 +127,3 @@ This is a trivial example. In more realistic cases with complex and deeply neste * Don't write long blocks of manually-implemented logic. Prefer `.razor` files and allow the compiler to deal with the sequence numbers. If you're unable to avoid manual logic, split long blocks of code into smaller pieces wrapped in / calls. Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region. * If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. The initial value and gaps are irrelevant. One legitimate option is to use the code line number as the sequence number, or start from zero and increase by ones or hundreds (or any preferred interval). * Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Diffing is far faster when sequence numbers are used, and Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring `.razor` files. - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -## Manually build a render tree (`RenderTreeBuilder`) - - provides methods for manipulating components and elements, including building components manually in C# code. - -Consider the following `PetDetails` component, which can be manually rendered in another component. - -`Shared/PetDetails.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/advanced-scenarios/PetDetails.razor)] - -In the following `BuiltContent` component, the loop in the `CreateComponent` method generates three `PetDetails` components. - -In methods with a sequence number, sequence numbers are source code line numbers. The Blazor difference algorithm relies on the sequence numbers corresponding to distinct lines of code, not distinct call invocations. When creating a component with methods, hardcode the arguments for sequence numbers. **Using a calculation or counter to generate the sequence number can lead to poor performance.** For more information, see the [Sequence numbers relate to code line numbers and not execution order](#sequence-numbers-relate-to-code-line-numbers-and-not-execution-order) section. - -`Pages/BuiltContent.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/advanced-scenarios/BuiltContent.razor?highlight=6,16-24,28)] - -> [!WARNING] -> The types in allow processing of the *results* of rendering operations. These are internal details of the Blazor framework implementation. These types should be considered *unstable* and subject to change in future releases. - -### Sequence numbers relate to code line numbers and not execution order - -Razor component files (`.razor`) are always compiled. Executing compiled code has a potential advantage over interpreting code because the compile step that yields the compiled code can be used to inject information that improves app performance at runtime. - -A key example of these improvements involves *sequence numbers*. Sequence numbers indicate to the runtime which outputs came from which distinct and ordered lines of code. The runtime uses this information to generate efficient tree diffs in linear time, which is far faster than is normally possible for a general tree diff algorithm. - -Consider the following Razor component file (`.razor`): - -```razor -@if (someFlag) -{ - First -} - -Second -``` - -The preceding Razor markup and text content compiles into C# code similar to the following: - -```csharp -if (someFlag) -{ - builder.AddContent(0, "First"); -} - -builder.AddContent(1, "Second"); -``` - -When the code executes for the first time and `someFlag` is `true`, the builder receives the sequence in the following table. - -| Sequence | Type | Data | -| :------: | --------- | :----: | -| 0 | Text node | First | -| 1 | Text node | Second | - -Imagine that `someFlag` becomes `false` and the markup is rendered again. This time, the builder receives the sequence in the following table. - -| Sequence | Type | Data | -| :------: | ---------- | :----: | -| 1 | Text node | Second | - -When the runtime performs a diff, it sees that the item at sequence `0` was removed, so it generates the following trivial *edit script* with a single step: - -* Remove the first text node. - -### The problem with generating sequence numbers programmatically - -Imagine instead that you wrote the following render tree builder logic: - -```csharp -var seq = 0; - -if (someFlag) -{ - builder.AddContent(seq++, "First"); -} - -builder.AddContent(seq++, "Second"); -``` - -The first output is seen in the following table. - -| Sequence | Type | Data | -| :------: | --------- | :----: | -| 0 | Text node | First | -| 1 | Text node | Second | - -This outcome is identical to the prior case, so no negative issues exist. `someFlag` is `false` on the second rendering, and the output is seen in the following table. - -| Sequence | Type | Data | -| :------: | --------- | ------ | -| 0 | Text node | Second | - -This time, the diff algorithm sees that *two* changes have occurred. The algorithm generates the following edit script: - -* Change the value of the first text node to `Second`. -* Remove the second text node. - -Generating the sequence numbers has lost all the useful information about where the `if/else` branches and loops were present in the original code. This results in a diff **twice as long** as before. - -This is a trivial example. In more realistic cases with complex and deeply nested structures, and especially with loops, the performance cost is usually higher. Instead of immediately identifying which loop blocks or branches have been inserted or removed, the diff algorithm must recurse deeply into the render trees. This usually results in building longer edit scripts because the diff algorithm is misinformed about how the old and new structures relate to each other. - -### Guidance and conclusions - -* App performance suffers if sequence numbers are generated dynamically. -* The framework can't create its own sequence numbers automatically at runtime because the necessary information doesn't exist unless it's captured at compile time. -* Don't write long blocks of manually-implemented logic. Prefer `.razor` files and allow the compiler to deal with the sequence numbers. If you're unable to avoid manual logic, split long blocks of code into smaller pieces wrapped in / calls. Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region. -* If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. The initial value and gaps are irrelevant. One legitimate option is to use the code line number as the sequence number, or start from zero and increase by ones or hundreds (or any preferred interval). -* Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Diffing is far faster when sequence numbers are used, and Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring `.razor` files. - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -## Manually build a render tree (`RenderTreeBuilder`) - - provides methods for manipulating components and elements, including building components manually in C# code. - -Consider the following `PetDetails` component, which can be manually rendered in another component. - -`Shared/PetDetails.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/advanced-scenarios/PetDetails.razor)] - -In the following `BuiltContent` component, the loop in the `CreateComponent` method generates three `PetDetails` components. - -In methods with a sequence number, sequence numbers are source code line numbers. The Blazor difference algorithm relies on the sequence numbers corresponding to distinct lines of code, not distinct call invocations. When creating a component with methods, hardcode the arguments for sequence numbers. **Using a calculation or counter to generate the sequence number can lead to poor performance.** For more information, see the [Sequence numbers relate to code line numbers and not execution order](#sequence-numbers-relate-to-code-line-numbers-and-not-execution-order) section. - -`Pages/BuiltContent.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/advanced-scenarios/BuiltContent.razor?highlight=6,16-24,28)] - -> [!WARNING] -> The types in allow processing of the *results* of rendering operations. These are internal details of the Blazor framework implementation. These types should be considered *unstable* and subject to change in future releases. - -### Sequence numbers relate to code line numbers and not execution order - -Razor component files (`.razor`) are always compiled. Executing compiled code has a potential advantage over interpreting code because the compile step that yields the compiled code can be used to inject information that improves app performance at runtime. - -A key example of these improvements involves *sequence numbers*. Sequence numbers indicate to the runtime which outputs came from which distinct and ordered lines of code. The runtime uses this information to generate efficient tree diffs in linear time, which is far faster than is normally possible for a general tree diff algorithm. - -Consider the following Razor component file (`.razor`): - -```razor -@if (someFlag) -{ - First -} - -Second -``` - -The preceding Razor markup and text content compiles into C# code similar to the following: - -```csharp -if (someFlag) -{ - builder.AddContent(0, "First"); -} - -builder.AddContent(1, "Second"); -``` - -When the code executes for the first time and `someFlag` is `true`, the builder receives the sequence in the following table. - -| Sequence | Type | Data | -| :------: | --------- | :----: | -| 0 | Text node | First | -| 1 | Text node | Second | - -Imagine that `someFlag` becomes `false` and the markup is rendered again. This time, the builder receives the sequence in the following table. - -| Sequence | Type | Data | -| :------: | ---------- | :----: | -| 1 | Text node | Second | - -When the runtime performs a diff, it sees that the item at sequence `0` was removed, so it generates the following trivial *edit script* with a single step: - -* Remove the first text node. - -### The problem with generating sequence numbers programmatically - -Imagine instead that you wrote the following render tree builder logic: - -```csharp -var seq = 0; - -if (someFlag) -{ - builder.AddContent(seq++, "First"); -} - -builder.AddContent(seq++, "Second"); -``` - -The first output is seen in the following table. - -| Sequence | Type | Data | -| :------: | --------- | :----: | -| 0 | Text node | First | -| 1 | Text node | Second | - -This outcome is identical to the prior case, so no negative issues exist. `someFlag` is `false` on the second rendering, and the output is seen in the following table. - -| Sequence | Type | Data | -| :------: | --------- | ------ | -| 0 | Text node | Second | - -This time, the diff algorithm sees that *two* changes have occurred. The algorithm generates the following edit script: - -* Change the value of the first text node to `Second`. -* Remove the second text node. - -Generating the sequence numbers has lost all the useful information about where the `if/else` branches and loops were present in the original code. This results in a diff **twice as long** as before. - -This is a trivial example. In more realistic cases with complex and deeply nested structures, and especially with loops, the performance cost is usually higher. Instead of immediately identifying which loop blocks or branches have been inserted or removed, the diff algorithm must recurse deeply into the render trees. This usually results in building longer edit scripts because the diff algorithm is misinformed about how the old and new structures relate to each other. - -### Guidance and conclusions - -* App performance suffers if sequence numbers are generated dynamically. -* The framework can't create its own sequence numbers automatically at runtime because the necessary information doesn't exist unless it's captured at compile time. -* Don't write long blocks of manually-implemented logic. Prefer `.razor` files and allow the compiler to deal with the sequence numbers. If you're unable to avoid manual logic, split long blocks of code into smaller pieces wrapped in / calls. Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region. -* If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. The initial value and gaps are irrelevant. One legitimate option is to use the code line number as the sequence number, or start from zero and increase by ones or hundreds (or any preferred interval). -* Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Diffing is far faster when sequence numbers are used, and Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring `.razor` files. - -:::moniker-end diff --git a/aspnetcore/blazor/blazor-server-ef-core.md b/aspnetcore/blazor/blazor-server-ef-core.md index 04ae4f0fb347..96e9b70799b8 100644 --- a/aspnetcore/blazor/blazor-server-ef-core.md +++ b/aspnetcore/blazor/blazor-server-ef-core.md @@ -5,14 +5,14 @@ description: Learn how to use Entity Framework Core (EF Core) in Blazor Server a monikerRange: '>= aspnetcore-3.1' ms.author: jeliknes ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 03/27/2023 uid: blazor/blazor-server-ef-core --- # ASP.NET Core Blazor Server with Entity Framework Core (EF Core) -This article explains how to use [Entity Framework Core (EF Core)](/ef/core/) in Blazor Server apps. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains how to use [Entity Framework Core (EF Core)](/ef/core/) in Blazor Server apps. Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a *circuit*. One example of user state is data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core. @@ -27,7 +27,29 @@ The sample app was built as a reference for Blazor Server apps that use EF Core. The sample uses a local [SQLite](https://www.sqlite.org/index.html) database so that it can be used on any platform. The sample also configures database logging to show the SQL queries that are generated. This is configured in `appsettings.Development.json`: -:::code language="json" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/appsettings.Development.json" highlight="8"::: +:::moniker range=">= aspnetcore-7.0" + +:::code language="json" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/appsettings.Development.json" highlight="8"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="json" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/appsettings.Development.json" highlight="8"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="json" source="~/../blazor-samples/5.0/BlazorServerEFCoreSample/appsettings.Development.json" highlight="8"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="json" source="~/../blazor-samples/3.1/BlazorServerEFCoreSample/appsettings.Development.json" highlight="8"::: + +:::moniker-end The grid, add, and view components use the "context-per-operation" pattern, where a context is created for each operation. The edit component uses the "context-per-component" pattern, where a context is created for each component. @@ -73,153 +95,97 @@ The following recommendations are designed to provide a consistent approach to u ``` Place operations after the `Loading = true;` line in the `try` block. + + Loading logic doesn't require locking database records because thread safety isn't a concern. The loading logic is used to disable UI controls so that users don't inadvertently select buttons or update fields while data is fetched. + +* If there's any chance that multiple threads may access the same code block, [inject a factory](#scope-to-the-component-lifetime) and make a new instance per operation. Otherwise, injecting and using the context is usually sufficient. * For longer-lived operations that take advantage of EF Core's [change tracking](/ef/core/querying/tracking) or [concurrency control](/ef/core/saving/concurrency), [scope the context to the lifetime of the component](#scope-to-the-component-lifetime). -### New DbContext instances - -The fastest way to create a new instance is by using `new` to create a new instance. However, there are several scenarios that may require resolving additional dependencies. For example, you may wish to use [`DbContextOptions`](/ef/core/miscellaneous/configuring-dbcontext#configuring-dbcontextoptions) to configure the context. - -The recommended solution to create a new with dependencies is to use a factory. EF Core 5.0 or later provides a built-in factory for creating new contexts. +## New `DbContext` instances -The following example configures [SQLite](https://www.sqlite.org/index.html) and enables data logging. The code uses an extension method (`AddDbContextFactory`) to configure the database factory for DI and provide default options: - -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Program.cs" id="snippet1"::: +The fastest way to create a new instance is by using `new` to create a new instance. However, there are scenarios that require resolving additional dependencies: -The factory is injected into components and used to create new instances. For example, in `Pages/Index.razor`: +* Using [`DbContextOptions`](/ef/core/miscellaneous/configuring-dbcontext#configuring-dbcontextoptions) to configure the context. +* Using a connection string per , such as when you use [ASP.NET Core's Identity model](xref:security/authentication/customize_identity_model). For more information, see [Multi-tenancy (EF Core documentation)](/ef/core/miscellaneous/multitenancy). -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/Index.razor" id="snippet1"::: - -> [!NOTE] -> `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Index` component (`Pages/Index.razor`) in the [sample app](https://github.com/dotnet/blazor-samples/blob/main/6.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/Index.razor). +The recommended approach to create a new with dependencies is to use a factory. EF Core 5.0 or later provides a built-in factory for creating new contexts. -New instances can be created with a factory that allows you to configure the connection string per `DbContext`, such as when you use [ASP.NET Core's Identity model](xref:security/authentication/customize_identity_model). For more information, see [Multi-tenancy (EF Core documentation)](/ef/core/miscellaneous/multitenancy). - -### Scope to the component lifetime - -You may wish to create a that exists for the lifetime of a component. This allows you to use it as a [unit of work](https://martinfowler.com/eaaCatalog/unitOfWork.html) and take advantage of built-in features, such as change tracking and concurrency resolution. -You can use the factory to create a context and track it for the lifetime of the component. First, implement and inject the factory as shown in `Pages/EditContact.razor`: +:::moniker range="< aspnetcore-5.0" -```razor -@implements IDisposable -@inject IDbContextFactory DbFactory -``` +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorServerEFCoreSample/Data/DbContextFactory.cs"::: -The sample app ensures the context is disposed when the component is disposed: +In the preceding factory: -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/EditContact.razor" id="snippet1"::: +* satisfies any dependencies via the service provider. +* `IDbContextFactory` is available in EF Core ASP.NET Core 5.0 or later, so the interface is [implemented in the sample app for ASP.NET Core 3.x](https://github.com/dotnet/blazor-samples/blob/main/3.1/BlazorServerEFCoreSample/Data/IDbContextFactory.cs). -Finally, [`OnInitializedAsync`](xref:blazor/components/lifecycle) is overridden to create a new context. In the sample app, [`OnInitializedAsync`](xref:blazor/components/lifecycle) loads the contact in the same method: +:::moniker-end -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/EditContact.razor" id="snippet2"::: +The following example configures [SQLite](https://www.sqlite.org/index.html) and enables data logging. The code uses an extension method (`AddDbContextFactory`) to configure the database factory for DI and provide default options: -### Enable sensitive data logging +:::moniker range=">= aspnetcore-7.0" - includes application data in exception messages and framework logging. The logged data can include the values assigned to properties of entity instances and parameter values for commands sent to the database. Logging data with is a **security risk**, as it may expose passwords and other personally identifiable information (PII) when it logs SQL statements executed against the database. +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Program.cs" id="snippet1"::: -We recommend only enabling for development and testing: - -```csharp -#if DEBUG - services.AddDbContextFactory(opt => - opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db") - .EnableSensitiveDataLogging()); -#else - services.AddDbContextFactory(opt => - opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")); -#endif -``` +:::moniker-end -## Additional resources +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -* [EF Core documentation](/ef/) +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/Program.cs" id="snippet1"::: :::moniker-end :::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a *circuit*. One example of user state is data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core. - -> [!NOTE] -> This article addresses EF Core in Blazor Server apps. Blazor WebAssembly apps run in a WebAssembly sandbox that prevents most direct database connections. Running EF Core in Blazor WebAssembly is beyond the scope of this article. - -## Sample app - -The sample app was built as a reference for Blazor Server apps that use EF Core. The sample app includes a grid with sorting and filtering, delete, add, and update operations. The sample demonstrates use of EF Core to handle optimistic concurrency. - -[View or download sample code](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/blazor/samples/) ([how to download](xref:index#how-to-download-a-sample)) - -The sample uses a local [SQLite](https://www.sqlite.org/index.html) database so that it can be used on any platform. The sample also configures database logging to show the SQL queries that are generated. This is configured in `appsettings.Development.json`: - -[!code-json[](~/blazor/samples/5.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/appsettings.Development.json?highlight=8)] - -The grid, add, and view components use the "context-per-operation" pattern, where a context is created for each operation. The edit component uses the "context-per-component" pattern, where a context is created for each component. +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorServerEFCoreSample/Startup.cs" id="snippet1"::: -> [!NOTE] -> Some of the code examples in this topic require namespaces and services that aren't shown. To inspect the fully working code, including the required [`@using`](xref:mvc/views/razor#using) and [`@inject`](xref:mvc/views/razor#inject) directives for Razor examples, see the [sample app](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/blazor/samples/). +:::moniker-end -## Database access +:::moniker range="< aspnetcore-5.0" -EF Core relies on a as the means to [configure database access](/ef/core/miscellaneous/configuring-dbcontext) and act as a [*unit of work*](https://martinfowler.com/eaaCatalog/unitOfWork.html). EF Core provides the extension for ASP.NET Core apps that registers the context as a *scoped* service by default. In Blazor Server apps, scoped service registrations can be problematic because the instance is shared across components within the user's circuit. isn't thread safe and isn't designed for concurrent use. The existing lifetimes are inappropriate for these reasons: +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorServerEFCoreSample/Startup.cs" id="snippet1"::: -* **Singleton** shares state across all users of the app and leads to inappropriate concurrent use. -* **Scoped** (the default) poses a similar issue between components for the same user. -* **Transient** results in a new instance per request; but as components can be long-lived, this results in a longer-lived context than may be intended. +:::moniker-end -The following recommendations are designed to provide a consistent approach to using EF Core in Blazor Server apps. +The factory is injected into components and used to create new `DbContext` instances. -* By default, consider using one context per operation. The context is designed for fast, low overhead instantiation: +In `Pages/Index.razor` of the [sample app](https://github.com/dotnet/blazor-samples/blob/main/7.0/BlazorServerEFCoreSample/Pages/Index.razor), `IDbContextFactory` is injected into the component: - ```csharp - using var context = new MyContext(); +```razor +@inject IDbContextFactory DbFactory +``` - return await context.MyEntities.ToListAsync(); - ``` +A `DbContext` is created using the factory (`DbFactory`) to delete a contact in the `DeleteContactAsync` method: -* Use a flag to prevent multiple concurrent operations: +:::moniker range=">= aspnetcore-7.0" - ```csharp - if (Loading) - { - return; - } +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Pages/Index.razor" id="snippet1"::: - try - { - Loading = true; - - ... - } - finally - { - Loading = false; - } - ``` +:::moniker-end - Place operations after the `Loading = true;` line in the `try` block. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -* For longer-lived operations that take advantage of EF Core's [change tracking](/ef/core/querying/tracking) or [concurrency control](/ef/core/saving/concurrency), [scope the context to the lifetime of the component](#scope-to-the-component-lifetime). +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/Pages/Index.razor" id="snippet1"::: -### New DbContext instances +:::moniker-end -The fastest way to create a new instance is by using `new` to create a new instance. However, there are several scenarios that may require resolving additional dependencies. For example, you may wish to use [`DbContextOptions`](/ef/core/miscellaneous/configuring-dbcontext#configuring-dbcontextoptions) to configure the context. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -The recommended solution to create a new with dependencies is to use a factory. EF Core 5.0 or later provides a built-in factory for creating new contexts. +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorServerEFCoreSample/Pages/Index.razor" id="snippet1"::: -The following example configures [SQLite](https://www.sqlite.org/index.html) and enables data logging. The code uses an extension method (`AddDbContextFactory`) to configure the database factory for DI and provide default options: +:::moniker-end -[!code-csharp[](~/blazor/samples/5.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Startup.cs?name=snippet1)] +:::moniker range="< aspnetcore-5.0" -The factory is injected into components and used to create new instances. For example, in `Pages/Index.razor`: +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorServerEFCoreSample/Pages/Index.razor" id="snippet1"::: -[!code-csharp[](~/blazor/samples/5.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/Index.razor?name=snippet1)] +:::moniker-end > [!NOTE] -> `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Index` component (`Pages/Index.razor`) in the [sample app](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/samples/5.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/Index.razor). +> `Filters` is an injected `IContactFilters`, and `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Index` component (`Pages/Index.razor`) in the [sample app](https://github.com/dotnet/blazor-samples/blob/main/6.0/BlazorServerEFCoreSample/Pages/Index.razor). -New instances can be created with a factory that allows you to configure the connection string per `DbContext`, such as when you use [ASP.NET Core's Identity model](xref:security/authentication/customize_identity_model). For more information, see [Multi-tenancy (EF Core documentation)](/ef/core/miscellaneous/multitenancy). - -### Scope to the component lifetime +## Scope to the component lifetime You may wish to create a that exists for the lifetime of a component. This allows you to use it as a [unit of work](https://martinfowler.com/eaaCatalog/unitOfWork.html) and take advantage of built-in features, such as change tracking and concurrency resolution. You can use the factory to create a context and track it for the lifetime of the component. First, implement and inject the factory as shown in `Pages/EditContact.razor`: @@ -231,149 +197,62 @@ You can use the factory to create a context and track it for the lifetime of the The sample app ensures the context is disposed when the component is disposed: -[!code-csharp[](~/blazor/samples/5.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/EditContact.razor?name=snippet1)] - -Finally, [`OnInitializedAsync`](xref:blazor/components/lifecycle) is overridden to create a new context. In the sample app, [`OnInitializedAsync`](xref:blazor/components/lifecycle) loads the contact in the same method: +:::moniker range=">= aspnetcore-7.0" -[!code-csharp[](~/blazor/samples/5.0/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/EditContact.razor?name=snippet2)] +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet1"::: -### Enable sensitive data logging +:::moniker-end - includes application data in exception messages and framework logging. The logged data can include the values assigned to properties of entity instances and parameter values for commands sent to the database. Logging data with is a **security risk**, as it may expose passwords and other personally identifiable information (PII) when it logs SQL statements executed against the database. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -We recommend only enabling for development and testing: +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet1"::: -```csharp -#if DEBUG - services.AddDbContextFactory(opt => - opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db") - .EnableSensitiveDataLogging()); -#else - services.AddDbContextFactory(opt => - opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")); -#endif -``` +:::moniker-end -## Additional resources +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* [EF Core documentation](/ef/) +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet1"::: :::moniker-end :::moniker range="< aspnetcore-5.0" -Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a *circuit*. One example of user state is data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core. - -> [!NOTE] -> This article addresses EF Core in Blazor Server apps. Blazor WebAssembly apps run in a WebAssembly sandbox that prevents most direct database connections. Running EF Core in Blazor WebAssembly is beyond the scope of this article. +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet1"::: -## Sample app - -The sample app was built as a reference for Blazor Server apps that use EF Core. The sample app includes a grid with sorting and filtering, delete, add, and update operations. The sample demonstrates use of EF Core to handle optimistic concurrency. - -[View or download sample code](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/blazor/samples/) ([how to download](xref:index#how-to-download-a-sample)) - -The sample uses a local [SQLite](https://www.sqlite.org/index.html) database so that it can be used on any platform. The sample also configures database logging to show the SQL queries that are generated. This is configured in `appsettings.Development.json`: - -[!code-json[](~/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/appsettings.Development.json?highlight=8)] - -The grid, add, and view components use the "context-per-operation" pattern, where a context is created for each operation. The edit component uses the "context-per-component" pattern, where a context is created for each component. - -> [!NOTE] -> Some of the code examples in this topic require namespaces and services that aren't shown. To inspect the fully working code, including the required [`@using`](xref:mvc/views/razor#using) and [`@inject`](xref:mvc/views/razor#inject) directives for Razor examples, see the [sample app](https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/blazor/samples/). - -## Database access - -EF Core relies on a as the means to [configure database access](/ef/core/miscellaneous/configuring-dbcontext) and act as a [*unit of work*](https://martinfowler.com/eaaCatalog/unitOfWork.html). EF Core provides the extension for ASP.NET Core apps that registers the context as a *scoped* service by default. In Blazor Server apps, this can be problematic because the instance is shared across components within the user's circuit. isn't thread safe and isn't designed for concurrent use. The existing lifetimes are inappropriate for these reasons: - -* **Singleton** shares state across all users of the app and leads to inappropriate concurrent use. -* **Scoped** (the default) poses a similar issue between components for the same user. -* **Transient** results in a new instance per request; but as components can be long-lived, this results in a longer-lived context than may be intended. - -The following recommendations are designed to provide a consistent approach to using EF Core in Blazor Server apps. - -* By default, consider using one context per operation. The context is designed for fast, low overhead instantiation: - - ```csharp - using var context = new MyContext(); - - return await context.MyEntities.ToListAsync(); - ``` - -* Use a flag to prevent multiple concurrent operations: - - ```csharp - if (Loading) - { - return; - } - - try - { - Loading = true; - - ... - } - finally - { - Loading = false; - } - ``` - - Place operations after the `Loading = true;` line in the `try` block. - -* For longer-lived operations that take advantage of EF Core's [change tracking](/ef/core/querying/tracking) or [concurrency control](/ef/core/saving/concurrency), [scope the context to the lifetime of the component](#scope-to-the-component-lifetime). - -### New DbContext instances - -The fastest way to create a new instance is by using `new` to create a new instance. However, there are several scenarios that may require resolving additional dependencies. For example, you may wish to use [`DbContextOptions`](/ef/core/miscellaneous/configuring-dbcontext#configuring-dbcontextoptions) to configure the context. - -The recommended solution to create a new with dependencies is to use a factory. The sample app implements its own factory in `Data/DbContextFactory.cs`. - -[!code-csharp[](~/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Data/DbContextFactory.cs)] - -In the preceding factory: - -* satisfies any dependencies via the service provider. -* `IDbContextFactory` is available in EF Core ASP.NET Core 5.0 or later, so the interface is [implemented in the sample app for ASP.NET Core 3.x](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Data/IDbContextFactory.cs). - -The following example configures [SQLite](https://www.sqlite.org/index.html) and enables data logging. The code uses an extension method to configure the database factory for DI and provide default options: +:::moniker-end -[!code-csharp[](~/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Startup.cs?name=snippet1)] +Finally, [`OnInitializedAsync`](xref:blazor/components/lifecycle) is overridden to create a new context. In the sample app, [`OnInitializedAsync`](xref:blazor/components/lifecycle) loads the contact in the same method: -The factory is injected into components and used to create new instances. For example, in `Pages/Index.razor`: +:::moniker range=">= aspnetcore-7.0" -[!code-csharp[](~/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/Index.razor?name=snippet1)] +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet2"::: -> [!NOTE] -> `Wrapper` is a [component reference](xref:blazor/components/index#capture-references-to-components) to the `GridWrapper` component. See the `Index` component (`Pages/Index.razor`) in the [sample app](https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/Index.razor). +:::moniker-end -New instances can be created with a factory that allows you to configure the connection string per `DbContext`, such as when you use [ASP.NET Core's Identity model])(xref:security/authentication/customize_identity_model). For more information, see [Multi-tenancy (EF Core documentation)](/ef/core/miscellaneous/multitenancy). +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -### Scope to the component lifetime +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet2"::: -You may wish to create a that exists for the lifetime of a component. This allows you to use it as a [unit of work](https://martinfowler.com/eaaCatalog/unitOfWork.html) and take advantage of built-in features, such as change tracking and concurrency resolution. -You can use the factory to create a context and track it for the lifetime of the component. First, implement and inject the factory as shown in `Pages/EditContact.razor`: +:::moniker-end -```razor -@implements IDisposable -@inject IDbContextFactory DbFactory -``` +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -The sample app ensures the context is disposed when the component is disposed: +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet2"::: -[!code-csharp[](~/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/EditContact.razor?name=snippet1)] +:::moniker-end -Finally, [`OnInitializedAsync`](xref:blazor/components/lifecycle) is overridden to create a new context. In the sample app, [`OnInitializedAsync`](xref:blazor/components/lifecycle) loads the contact in the same method: +:::moniker range="< aspnetcore-5.0" -[!code-csharp[](~/blazor/samples/3.1/BlazorServerEFCoreSample/BlazorServerDbContextExample/Pages/EditContact.razor?name=snippet2)] +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorServerEFCoreSample/Pages/EditContact.razor" id="snippet2"::: In the preceding example: * When `Busy` is set to `true`, asynchronous operations may begin. When `Busy` is set back to `false`, asynchronous operations should be finished. * Place additional error handling logic in a `catch` block. -### Enable sensitive data logging +:::moniker-end + +## Enable sensitive data logging includes application data in exception messages and framework logging. The logged data can include the values assigned to properties of entity instances and parameter values for commands sent to the database. Logging data with is a **security risk**, as it may expose passwords and other personally identifiable information (PII) when it logs SQL statements executed against the database. @@ -393,5 +272,4 @@ We recommend only enabling = aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 04/06/2023 uid: blazor/call-web-api zone_pivot_groups: blazor-hosting-models --- # Call a web API from ASP.NET Core Blazor +[!INCLUDE[](~/includes/not-latest-version.md)] + This article describes how to call a web API from a Blazor app. -:::moniker range=">= aspnetcore-6.0" +> [!NOTE] +> The code examples in this article adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core 6.0 or later. When targeting ASP.NET Core 5.0 or earlier, remove the null type designation (`?`) from the `string?`, `TodoItem[]?`, `WeatherForecast[]?`, and `IEnumerable?` types in the article's examples. :::zone pivot="webassembly" @@ -46,13 +49,15 @@ public class TodoItem } ``` -For guidance on how to create a server-side web API, see . For information on Cross-origin resource sharing (CORS), see the [CORS guidance](#cross-origin-resource-sharing-cors) later in this article. +For guidance on how to create a server-side web API, see . For information on Cross-origin resource sharing (CORS), see the *Cross-origin resource sharing (CORS)* section later in this article. -## Packages +The Blazor WebAssembly examples that demonstrate obtaining weather data from a server API are based on a hosted Blazor WebAssembly solution created from the [Blazor WebAssembly project template](xref:blazor/project-structure#blazor-webassembly). -Add a package reference for [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json). +## Package -[!INCLUDE[](~/includes/package-reference.md)] +The [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json) package provides extension methods for and that perform automatic serialization and deserialization using [`System.Text.Json`](https://www.nuget.org/packages/System.Text.Json). + +The [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json) package is provided by the .NET shared framework and doesn't require adding a package reference to the app. ## Add the `HttpClient` service @@ -70,7 +75,7 @@ builder.Services.AddScoped(sp => is available as a preconfigured service for making requests back to the origin server. - and JSON helpers () are also used to call third-party web API endpoints. is implemented using the browser's [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) and is subject to its limitations, including enforcement of the [same-origin policy (discussed later in this article)](#cross-origin-resource-sharing-cors). + and JSON helpers () are also used to call third-party web API endpoints. is implemented using the browser's [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) and is subject to its limitations, including enforcement of the same-origin policy, which is discussed later in this article in the *Cross-origin resource sharing (CORS)* section. The client's base address is set to the originating server's address. Inject an instance into a component using the [`@inject`](xref:mvc/views/razor#inject) directive: @@ -91,10 +96,10 @@ Use the namespace for acces In the following component code, the `todoItems` are displayed by the component. is called when the component is finished initializing ([`OnInitializedAsync`](xref:blazor/components/lifecycle#component-initialization-oninitializedasync)). +> [!NOTE] +> When targeting ASP.NET Core 5.0 or earlier, add `@using` directives to the following component for , , and . + ```razor -@using System.Net.Http -@using System.Net.Http.Json -@using System.Threading.Tasks @inject HttpClient Http @if (todoItems == null) @@ -125,10 +130,10 @@ else In the following component code, `newItemName` is provided by a bound element of the component. The `AddItem` method is triggered by selecting a ` + + } + + +@code { + private async Task UpdateItem(long id) => + await Http.PatchAsJsonAsync( + $"api/TodoItems/{id}", + new TodoItem() { IsComplete = true }, + new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + }); +} +``` + + returns an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data as an array: ```csharp -var content = await response.Content.ReadFromJsonAsync(); +var content = await response.Content.ReadFromJsonAsync() ?? + Array.Empty(); ``` +In the preceding example, an empty array is created if no weather data is returned by the method, so `content` isn't null after the statement executes. + +:::moniker-end + ### Additional extension methods includes additional extension methods for sending HTTP requests and receiving HTTP responses. is used to send an HTTP DELETE request to a web API. In the following component code, the ` - -@code { - private string newItemName; - - private async Task AddItem() - { - var addItem = new TodoItem { Name = newItemName, IsComplete = false }; - await Http.PostAsJsonAsync("api/TodoItems", addItem); - } -} -``` - -Calls to return an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: - -```csharp -var content = await response.Content.ReadFromJsonAsync(); -``` - -### PUT as JSON (`PutAsJsonAsync`) - - sends an HTTP PUT request with JSON-encoded content. - -In the following component code, `editItem` values for `Name` and `IsCompleted` are provided by bound elements of the component. The item's `Id` is set when the item is selected in another part of the UI (not shown) and `EditItem` is called. The `SaveItem` method is triggered by selecting the ` - -@code { - private string id; - private TodoItem editItem = new TodoItem(); - - private void EditItem(long id) - { - editItem = todoItems.Single(i => i.Id == id); - } - - private async Task SaveItem() => - await Http.PutAsJsonAsync($"api/TodoItems/{editItem.Id}", editItem); -} -``` - -Calls to return an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: - -```csharp -var content = await response.Content.ReadFromJsonAsync(); -``` - -### Additional extension methods - - includes additional extension methods for sending HTTP requests and receiving HTTP responses. is used to send an HTTP DELETE request to a web API. - -In the following component code, the ` - -@code { - private long id; - - private async Task DeleteItem() => - await Http.DeleteAsync($"api/TodoItems/{id}"); -} -``` - -## Named `HttpClient` with `IHttpClientFactory` - - services and the configuration of a named are supported. - -Add the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package to the app. - -[!INCLUDE[](~/includes/package-reference.md)] - -In `Program.cs`: - -```csharp -builder.Services.AddHttpClient("WebAPI", client => - client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); -``` - -In the following component code: - -* An instance of creates a named . -* The named is used to issue a GET request for JSON weather forecast data from the web API. - -`Pages/FetchDataViaFactory.razor`: - -```razor -@page "/fetch-data-via-factory" -@using System.Net.Http -@using System.Net.Http.Json -@using System.Threading.Tasks -@inject IHttpClientFactory ClientFactory - -

Fetch data via IHttpClientFactory

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ -

Temperatures by Date

- -
    - @foreach (var forecast in forecasts) - { -
  • - @forecast.Date.ToShortDateString(): - @forecast.TemperatureC ℃ - @forecast.TemperatureF ℉ -
  • - } -
-} - -@code { - private WeatherForecast[] forecasts; - - protected override async Task OnInitializedAsync() - { - var client = ClientFactory.CreateClient("WebAPI"); - - forecasts = await client.GetFromJsonAsync( - "WeatherForecast"); - } -} -``` - -## Typed `HttpClient` - -Typed uses one or more of the app's instances, default or named, to return data from one or more web API endpoints. - -`WeatherForecastClient.cs`: - -```csharp -using System.Net.Http; -using System.Net.Http.Json; -using System.Threading.Tasks; - -public class WeatherForecastHttpClient -{ - private readonly HttpClient http; - - public WeatherForecastHttpClient(HttpClient http) - { - this.http = http; - } - - public async Task GetForecastAsync() - { - try - { - return await http.GetFromJsonAsync( - "WeatherForecast"); - } - catch - { - ... - - return new WeatherForecast[0]; - } - } -} -``` - -In `Program.cs`: - -```csharp -builder.Services.AddHttpClient(client => - client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); -``` - -Components inject the typed to call the web API. - -In the following component code: - -* An instance of the preceding `WeatherForecastHttpClient` is injected, which creates a typed . -* The typed is used to issue a GET request for JSON weather forecast data from the web API. - -`Pages/FetchDataViaTypedHttpClient.razor`: - -```razor -@page "/fetch-data-via-typed-httpclient" -@using System.Threading.Tasks -@inject WeatherForecastHttpClient Http - -

Fetch data via typed HttpClient

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ -

Temperatures by Date

- -
    - @foreach (var forecast in forecasts) - { -
  • - @forecast.Date.ToShortDateString(): - @forecast.TemperatureC ℃ - @forecast.TemperatureF ℉ -
  • - } -
-} - -@code { - private WeatherForecast[] forecasts; - - protected override async Task OnInitializedAsync() - { - forecasts = await Http.GetForecastAsync(); - } -} -``` - -## `HttpClient` and `HttpRequestMessage` with Fetch API request options - -[`HttpClient`](xref:fundamentals/http-requests) ([API documentation](xref:System.Net.Http.HttpClient)) and can be used to customize requests. For example, you can specify the HTTP method and request headers. The following component makes a `POST` request to a web API endpoint and shows the response body. - -`Pages/TodoRequest.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/call-web-api/TodoRequest.razor)] - -Blazor WebAssembly's implementation of uses [Fetch API](https://developer.mozilla.org/docs/Web/API/fetch). Fetch API allows the configuration of several [request-specific options](https://developer.mozilla.org/docs/Web/API/fetch#Parameters). Options can be configured with extension methods shown in the following table. - -| Extension method | Fetch API request property | -| --- | --- | -| | [`cache`](https://developer.mozilla.org/docs/Web/API/Request/cache) | -| | [`credentials`](https://developer.mozilla.org/docs/Web/API/Request/credentials) | -| | [`integrity`](https://developer.mozilla.org/docs/Web/API/Request/integrity) | -| | [`mode`](https://developer.mozilla.org/docs/Web/API/Request/mode) | - -Set additional options using the generic extension method. - -The HTTP response is typically buffered to enable support for synchronous reads on the response content. To enable support for response streaming, use the extension method on the request. - -To include credentials in a cross-origin request, use the extension method: - -```csharp -requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include); -``` - -For more information on Fetch API options, see [MDN web docs: WindowOrWorkerGlobalScope.fetch(): Parameters](https://developer.mozilla.org/docs/Web/API/fetch#Parameters). - -## Call web API example - -The following example calls a web API. The example requires a running web API based on the sample app described by the article. This example makes requests to the web API at `https://localhost:10000/api/TodoItems`. If a different web API address is used, update the `ServiceEndpoint` constant value in the component's `@code` block. - -The following example makes a [cross-origin resource sharing (CORS)](xref:security/cors) request from `http://localhost:5000` or `https://localhost:5001` to the web API. Add the following CORS middleware configuration to the web API's service's `Startup.Configure` method: - -```csharp -app.UseCors(policy => - policy.WithOrigins("http://localhost:5000", "https://localhost:5001") - .AllowAnyMethod() - .WithHeaders(HeaderNames.ContentType)); -``` - -Adjust the domains and ports of `WithOrigins` as needed for the Blazor app. For more information, see . - -By default, ASP.NET Core apps use ports 5000 (HTTP) and 5001 (HTTPS). To run both apps on the same machine at the same time for testing, use a different port for the web API app (for example, port 10000). For more information on setting the port, see . - -`Pages/CallWebAPI.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/call-web-api/CallWebAPI.razor)] - -## Handle errors - -Handle web API response errors in developer code when they occur. For example, expects a JSON response from the web API with a `Content-Type` of `application/json`. If the response isn't in JSON format, content validation throws a . - -In the following example, the URI endpoint for the weather forecast data request is misspelled. The URI should be to `WeatherForecast` but appears in the call as `WeatherForcast`, which is missing the letter `e` in `Forecast`. - -The call expects JSON to be returned, but the web API returns HTML for an unhandled exception with a `Content-Type` of `text/html`. The unhandled exception occurs because the path to `/WeatherForcast` isn't found and middleware can't serve a page or view for the request. - -In on the client, is thrown when the response content is validated as non-JSON. The exception is caught in the `catch` block, where custom logic could log the error or present a friendly error message to the user. - -`Pages/FetchDataReturnsHTMLOnException.razor`: - -```razor -@page "/fetch-data-returns-html-on-exception" -@using System.Net.Http -@using System.Net.Http.Json -@using System.Threading.Tasks -@inject HttpClient Http - -

Fetch data but receive HTML on unhandled exception

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ -

Temperatures by Date

- -
    - @foreach (var forecast in forecasts) - { -
  • - @forecast.Date.ToShortDateString(): - @forecast.TemperatureC ℃ - @forecast.TemperatureF ℉ -
  • - } -
-} - -

- @exceptionMessage -

- -@code { - private WeatherForecast[] forecasts; - private string exceptionMessage; - - protected override async Task OnInitializedAsync() - { - try - { - // The URI endpoint "WeatherForecast" is misspelled on purpose on the - // next line. See the preceding text for more information. - forecasts = await Http.GetFromJsonAsync("WeatherForcast"); - } - catch (NotSupportedException exception) - { - exceptionMessage = exception.Message; - } - } -} -``` - -> [!NOTE] -> The preceding example is for demonstration purposes. A web API can be configured to return JSON even when an endpoint doesn't exist or an unhandled exception occurs on the server. - -For more information, see . - -:::zone-end - -:::zone pivot="server" - -> [!NOTE] -> This article has loaded **Blazor Server** coverage for calling web APIs. The [Blazor WebAssembly coverage](?pivots=webassembly) addresses the following subjects: -> -> * Blazor WebAssembly examples based on an client-side WebAssembly app that calls a web API to create, read, update, and delete todo list items. -> * `System.Net.Http.Json` package. -> * `HttpClient` service configuration. -> * `HttpClient` and JSON helpers (`GetFromJsonAsync`, `PostAsJsonAsync`, `PutAsJsonAsync`, `DeleteAsync`). -> * `IHttpClientFactory` services and the configuration of a named `HttpClient`. -> * Typed `HttpClient`. -> * `HttpClient` and `HttpRequestMessage` to customize requests. -> * Call web API example with cross-origin resource sharing (CORS) and how CORS pertains to Blazor WebAssembly apps. -> * How to handle web API response errors in developer code. -> * Blazor framework component examples for testing web API access. -> * Additional resources for developing Blazor WebAssembly apps that call a web API. - -[Blazor Server](xref:blazor/hosting-models#blazor-server) apps call web APIs using instances, typically created using . For guidance that applies to Blazor Server, see . - -A Blazor Server app doesn't include an service by default. Provide an to the app using the [`HttpClient` factory infrastructure](xref:fundamentals/http-requests). - -In `Startup.ConfigureServices` of `Startup.cs`: - -```csharp -services.AddHttpClient(); -``` - -The following Blazor Server Razor component makes a request to a web API for GitHub branches similar to the *Basic Usage* example in the article. - -`Pages/CallWebAPI.razor`: - -```razor -@page "/call-web-api" -@using System.Text.Json -@using System.Text.Json.Serialization; -@inject IHttpClientFactory ClientFactory - -

Call web API from a Blazor Server Razor component

- -@if (getBranchesError) -{ -

Unable to get branches from GitHub. Please try again later.

-} -else -{ -
    - @foreach (var branch in branches) - { -
  • @branch.Name
  • - } -
-} - -@code { - private IEnumerable branches = Array.Empty(); - private bool getBranchesError; - private bool shouldRender; - - protected override bool ShouldRender() => shouldRender; - - protected override async Task OnInitializedAsync() - { - var request = new HttpRequestMessage(HttpMethod.Get, - "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches"); - request.Headers.Add("Accept", "application/vnd.github.v3+json"); - request.Headers.Add("User-Agent", "HttpClientFactory-Sample"); - - var client = ClientFactory.CreateClient(); - - var response = await client.SendAsync(request); - - if (response.IsSuccessStatusCode) - { - using var responseStream = await response.Content.ReadAsStreamAsync(); - branches = await JsonSerializer.DeserializeAsync - >(responseStream); - } - else - { - getBranchesError = true; - } - - shouldRender = true; - } - - public class GitHubBranch - { - [JsonPropertyName("name")] - public string Name { get; set; } - } -} -``` - -For an additional working example, see the Blazor Server file upload example that uploads files to a web API controller in the article. - -:::zone-end - -## Cross-origin resource sharing (CORS) - -Browser security restricts a webpage from making requests to a different domain than the one that served the webpage. This restriction is called the *same-origin policy*. The same-origin policy restricts (but doesn't prevent) a malicious site from reading sensitive data from another site. To make requests from the browser to an endpoint with a different origin, the *endpoint* must enable [cross-origin resource sharing (CORS)](https://www.w3.org/TR/cors/). - -:::zone pivot="webassembly" - -For information on CORS requests in Blazor WebAssembly apps, see . - -For information on CORS, see . The article's examples don't pertain directly to Blazor WebAssembly apps, but the article is useful for learning general CORS concepts. - -:::zone-end - -:::zone pivot="server" - -For more information, see . - -:::zone-end - -## Blazor framework component examples for testing web API access - -Various network tools are publicly available for testing web API backend apps directly, such as [Firefox Browser Developer](https://www.mozilla.org/firefox/developer/) and [Postman](https://www.postman.com). Blazor framework's reference source includes test assets that are useful for testing: - -[`HttpClientTest` assets in the `dotnet/aspnetcore` GitHub repository](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/BasicTestApp/HttpClientTest) - -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -## Additional resources - -:::zone pivot="webassembly" - -* : Includes coverage on using to make secure web API requests. -* : Although the content applies to ASP.NET Core apps, not Blazor WebAssembly apps, the article covers general CORS concepts. -* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) -* [Fetch API](https://developer.mozilla.org/docs/Web/API/fetch) - -:::zone-end - -:::zone pivot="server" - -* : Includes coverage on using to make secure web API requests. -* -* -* -* [Kestrel HTTPS endpoint configuration](xref:fundamentals/servers/kestrel/endpoints) -* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) - -:::zone-end - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -:::zone pivot="webassembly" - -> [!NOTE] -> This article has loaded **Blazor WebAssembly** coverage for calling web APIs. The [Blazor Server coverage](?pivots=server) addresses the following subjects: -> -> * Use of the `HttpClient` factory infrastructure to provide an `HttpClient` to the app. -> * Cross-origin resource sharing (CORS) pertaining to Blazor Server apps. -> * Blazor framework component examples for testing web API access. -> * Additional resources for developing Blazor Server apps that call a web API. - -[Blazor WebAssembly](xref:blazor/hosting-models#blazor-webassembly) apps call web APIs using a preconfigured service, which is focused on making requests back to the server of origin. Additional service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with . Requests can include [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) option configuration. - -## Examples in this article - -In this article's component examples, a hypothetical todo list web API is used to create, read, update, and delete (CRUD) todo items on a server. The examples are based on a `TodoItem` class that stores the following todo item data: - -* ID (`Id`, `long`): Unique ID of the item. -* Name (`Name`, `string`): Name of the item. -* Status (`IsComplete`, `bool`): Indication if the todo item is finished. - -Use the following `TodoItem` class with this article's examples if you build the examples into a test app: - -```csharp -public class TodoItem -{ - public long Id { get; set; } - public string Name { get; set; } - public bool IsComplete { get; set; } -} -``` - -For guidance on how to create a server-side web API, see . For information on Cross-origin resource sharing (CORS), see the [CORS guidance](#cross-origin-resource-sharing-cors) later in this article. - -## Packages - -Add a package reference for [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json). - -[!INCLUDE[](~/includes/package-reference.md)] - -## Add the `HttpClient` service - -In `Program.cs`, add an service if it isn't already present from a Blazor project template used to create the app: - -```csharp -builder.Services.AddScoped(sp => - new HttpClient - { - BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) - }); -``` - -## `HttpClient` and JSON helpers - - is available as a preconfigured service for making requests back to the origin server. - - and JSON helpers () are also used to call third-party web API endpoints. is implemented using the browser's [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) and is subject to its limitations, including enforcement of the [same-origin policy (discussed later in this article)](#cross-origin-resource-sharing-cors). - -The client's base address is set to the originating server's address. Inject an instance into a component using the [`@inject`](xref:mvc/views/razor#inject) directive: - -```razor -@using System.Net.Http -@inject HttpClient Http -``` - -Use the namespace for access to , including , , and : - -```razor -@using System.Net.Http.Json -``` - -### GET from JSON (`GetFromJsonAsync`) - - sends an HTTP GET request and parses the JSON response body to create an object. - -In the following component code, the `todoItems` are displayed by the component. is called when the component is finished initializing ([`OnInitializedAsync`](xref:blazor/components/lifecycle#component-initialization-oninitializedasync)). - -```razor -@using System.Net.Http -@using System.Net.Http.Json -@using System.Threading.Tasks -@inject HttpClient Http - -@if (todoItems == null) -{ -

No Todo Items found.

-} -else -{ -
    - @foreach (var item in todoItems) - { -
  • @item.Name
  • - } -
-} - -@code { - private TodoItem[] todoItems; - - protected override async Task OnInitializedAsync() => - todoItems = await Http.GetFromJsonAsync("api/TodoItems"); -} -``` - -### POST as JSON (`PostAsJsonAsync`) - - sends a POST request to the specified URI containing the value serialized as JSON in the request body. - -In the following component code, `newItemName` is provided by a bound element of the component. The `AddItem` method is triggered by selecting a ` - -@code { - private string newItemName; - - private async Task AddItem() - { - var addItem = new TodoItem { Name = newItemName, IsComplete = false }; - await Http.PostAsJsonAsync("api/TodoItems", addItem); - } -} -``` - -Calls to return an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: - -```csharp -var content = await response.Content.ReadFromJsonAsync(); -``` - -### PUT as JSON (`PutAsJsonAsync`) - - sends an HTTP PUT request with JSON-encoded content. - -In the following component code, `editItem` values for `Name` and `IsCompleted` are provided by bound elements of the component. The item's `Id` is set when the item is selected in another part of the UI (not shown) and `EditItem` is called. The `SaveItem` method is triggered by selecting the ` - -@code { - private string id; - private TodoItem editItem = new TodoItem(); - - private void EditItem(long id) - { - editItem = todoItems.Single(i => i.Id == id); - } - - private async Task SaveItem() => - await Http.PutAsJsonAsync($"api/TodoItems/{editItem.Id}", editItem); -} -``` - -Calls to return an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: - -```csharp -var content = await response.Content.ReadFromJsonAsync(); -``` - -### Additional extension methods - - includes additional extension methods for sending HTTP requests and receiving HTTP responses. is used to send an HTTP DELETE request to a web API. - -In the following component code, the ` - -@code { - private long id; - - private async Task DeleteItem() => - await Http.DeleteAsync($"api/TodoItems/{id}"); -} -``` - -## Named `HttpClient` with `IHttpClientFactory` - - services and the configuration of a named are supported. - -Add the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package to the app. - -[!INCLUDE[](~/includes/package-reference.md)] - -In `Program.cs`: - -```csharp -builder.Services.AddHttpClient("WebAPI", client => - client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); -``` - -In the following component code: - -* An instance of creates a named . -* The named is used to issue a GET request for JSON weather forecast data from the web API. - -`Pages/FetchDataViaFactory.razor`: - -```razor -@page "/fetch-data-via-factory" -@using System.Net.Http -@using System.Net.Http.Json -@using System.Threading.Tasks -@inject IHttpClientFactory ClientFactory - -

Fetch data via IHttpClientFactory

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ -

Temperatures by Date

- -
    - @foreach (var forecast in forecasts) - { -
  • - @forecast.Date.ToShortDateString(): - @forecast.TemperatureC ℃ - @forecast.TemperatureF ℉ -
  • - } -
-} - -@code { - private WeatherForecast[] forecasts; - - protected override async Task OnInitializedAsync() - { - var client = ClientFactory.CreateClient("WebAPI"); - - forecasts = await client.GetFromJsonAsync( - "WeatherForecast"); - } -} -``` - -## Typed `HttpClient` - -Typed uses one or more of the app's instances, default or named, to return data from one or more web API endpoints. - -`WeatherForecastClient.cs`: - -```csharp -using System.Net.Http; -using System.Net.Http.Json; -using System.Threading.Tasks; - -public class WeatherForecastHttpClient -{ - private readonly HttpClient http; - - public WeatherForecastHttpClient(HttpClient http) - { - this.http = http; - } - - public async Task GetForecastAsync() - { - try - { - return await http.GetFromJsonAsync( - "WeatherForecast"); - } - catch - { - ... - - return new WeatherForecast[0]; - } - } -} -``` - -In `Program.cs`: - -```csharp -builder.Services.AddHttpClient(client => - client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); -``` - -Components inject the typed to call the web API. - -In the following component code: - -* An instance of the preceding `WeatherForecastHttpClient` is injected, which creates a typed . -* The typed is used to issue a GET request for JSON weather forecast data from the web API. - -`Pages/FetchDataViaTypedHttpClient.razor`: - -```razor -@page "/fetch-data-via-typed-httpclient" -@using System.Threading.Tasks -@inject WeatherForecastHttpClient Http - -

Fetch data via typed HttpClient

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ -

Temperatures by Date

- -
    - @foreach (var forecast in forecasts) - { -
  • - @forecast.Date.ToShortDateString(): - @forecast.TemperatureC ℃ - @forecast.TemperatureF ℉ -
  • - } -
-} - -@code { - private WeatherForecast[] forecasts; - - protected override async Task OnInitializedAsync() - { - forecasts = await Http.GetForecastAsync(); - } -} -``` - -## `HttpClient` and `HttpRequestMessage` with Fetch API request options - -[`HttpClient`](xref:fundamentals/http-requests) ([API documentation](xref:System.Net.Http.HttpClient)) and can be used to customize requests. For example, you can specify the HTTP method and request headers. The following component makes a `POST` request to a web API endpoint and shows the response body. - -`Pages/TodoRequest.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/call-web-api/TodoRequest.razor)] - -Blazor WebAssembly's implementation of uses [Fetch API](https://developer.mozilla.org/docs/Web/API/fetch). Fetch API allows the configuration of several [request-specific options](https://developer.mozilla.org/docs/Web/API/fetch#Parameters). Options can be configured with extension methods shown in the following table. - -| Extension method | Fetch API request property | -| --- | --- | -| | [`cache`](https://developer.mozilla.org/docs/Web/API/Request/cache) | -| | [`credentials`](https://developer.mozilla.org/docs/Web/API/Request/credentials) | -| | [`integrity`](https://developer.mozilla.org/docs/Web/API/Request/integrity) | -| | [`mode`](https://developer.mozilla.org/docs/Web/API/Request/mode) | - -Set additional options using the generic extension method. - -The HTTP response is typically buffered to enable support for synchronous reads on the response content. To enable support for response streaming, use the extension method on the request. - -To include credentials in a cross-origin request, use the extension method: - -```csharp -requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include); -``` - -For more information on Fetch API options, see [MDN web docs: WindowOrWorkerGlobalScope.fetch(): Parameters](https://developer.mozilla.org/docs/Web/API/fetch#Parameters). - -## Call web API example - -The following example calls a web API. The example requires a running web API based on the sample app described by the article. This example makes requests to the web API at `https://localhost:10000/api/TodoItems`. If a different web API address is used, update the `ServiceEndpoint` constant value in the component's `@code` block. - -The following example makes a [cross-origin resource sharing (CORS)](xref:security/cors) request from `http://localhost:5000` or `https://localhost:5001` to the web API. Add the following CORS middleware configuration to the web API's service's `Startup.Configure` method: - -```csharp -app.UseCors(policy => - policy.WithOrigins("http://localhost:5000", "https://localhost:5001") - .AllowAnyMethod() - .WithHeaders(HeaderNames.ContentType)); -``` - -Adjust the domains and ports of `WithOrigins` as needed for the Blazor app. For more information, see . - -By default, ASP.NET Core apps use ports 5000 (HTTP) and 5001 (HTTPS). To run both apps on the same machine at the same time for testing, use a different port for the web API app (for example, port 10000). For more information on setting the port, see . - -`Pages/CallWebAPI.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/call-web-api/CallWebAPI.razor)] - -## Handle errors - -Handle web API response errors in developer code when they occur. For example, expects a JSON response from the web API with a `Content-Type` of `application/json`. If the response isn't in JSON format, content validation throws a . - -In the following example, the URI endpoint for the weather forecast data request is misspelled. The URI should be to `WeatherForecast` but appears in the call as `WeatherForcast`, which is missing the letter `e` in `Forecast`. - -The call expects JSON to be returned, but the web API returns HTML for an unhandled exception with a `Content-Type` of `text/html`. The unhandled exception occurs because the path to `/WeatherForcast` isn't found and middleware can't serve a page or view for the request. - -In on the client, is thrown when the response content is validated as non-JSON. The exception is caught in the `catch` block, where custom logic could log the error or present a friendly error message to the user. - -`Pages/FetchDataReturnsHTMLOnException.razor`: - -```razor -@page "/fetch-data-returns-html-on-exception" -@using System.Net.Http -@using System.Net.Http.Json -@using System.Threading.Tasks -@inject HttpClient Http - -

Fetch data but receive HTML on unhandled exception

- -@if (forecasts == null) -{ -

Loading...

-} -else -{ -

Temperatures by Date

- -
    - @foreach (var forecast in forecasts) - { -
  • - @forecast.Date.ToShortDateString(): - @forecast.TemperatureC ℃ - @forecast.TemperatureF ℉ -
  • - } -
-} - -

- @exceptionMessage -

- -@code { - private WeatherForecast[] forecasts; - private string exceptionMessage; - - protected override async Task OnInitializedAsync() - { - try - { - // The URI endpoint "WeatherForecast" is misspelled on purpose on the - // next line. See the preceding text for more information. - forecasts = await Http.GetFromJsonAsync("WeatherForcast"); - } - catch (NotSupportedException exception) - { - exceptionMessage = exception.Message; - } - } -} -``` - -> [!NOTE] -> The preceding example is for demonstration purposes. A web API can be configured to return JSON even when an endpoint doesn't exist or an unhandled exception occurs on the server. - -For more information, see . - -:::zone-end - -:::zone pivot="server" - -> [!NOTE] -> This article has loaded **Blazor Server** coverage for calling web APIs. The [Blazor WebAssembly coverage](?pivots=webassembly) addresses the following subjects: -> -> * Blazor WebAssembly examples based on an client-side WebAssembly app that calls a web API to create, read, update, and delete todo list items. -> * `System.Net.Http.Json` package. -> * `HttpClient` service configuration. -> * `HttpClient` and JSON helpers (`GetFromJsonAsync`, `PostAsJsonAsync`, `PutAsJsonAsync`, `DeleteAsync`). -> * `IHttpClientFactory` services and the configuration of a named `HttpClient`. -> * Typed `HttpClient`. -> * `HttpClient` and `HttpRequestMessage` to customize requests. -> * Call web API example with cross-origin resource sharing (CORS) and how CORS pertains to Blazor WebAssembly apps. -> * How to handle web API response errors in developer code. -> * Blazor framework component examples for testing web API access. -> * Additional resources for developing Blazor WebAssembly apps that call a web API. - -[Blazor Server](xref:blazor/hosting-models#blazor-server) apps call web APIs using instances, typically created using . For guidance that applies to Blazor Server, see . - -A Blazor Server app doesn't include an service by default. Provide an to the app using the [`HttpClient` factory infrastructure](xref:fundamentals/http-requests). - -In `Startup.ConfigureServices` of `Startup.cs`: - -```csharp -services.AddHttpClient(); -``` - -The following Blazor Server Razor component makes a request to a web API for GitHub branches similar to the *Basic Usage* example in the article. - -`Pages/CallWebAPI.razor`: - -```razor -@page "/call-web-api" -@using System.Text.Json -@using System.Text.Json.Serialization; -@inject IHttpClientFactory ClientFactory - -

Call web API from a Blazor Server Razor component

- -@if (getBranchesError) -{ -

Unable to get branches from GitHub. Please try again later.

-} -else -{ -
    - @foreach (var branch in branches) - { -
  • @branch.Name
  • - } -
-} - -@code { - private IEnumerable branches = Array.Empty(); - private bool getBranchesError; - private bool shouldRender; - - protected override bool ShouldRender() => shouldRender; - - protected override async Task OnInitializedAsync() - { - var request = new HttpRequestMessage(HttpMethod.Get, - "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches"); - request.Headers.Add("Accept", "application/vnd.github.v3+json"); - request.Headers.Add("User-Agent", "HttpClientFactory-Sample"); - - var client = ClientFactory.CreateClient(); - - var response = await client.SendAsync(request); - - if (response.IsSuccessStatusCode) - { - using var responseStream = await response.Content.ReadAsStreamAsync(); - branches = await JsonSerializer.DeserializeAsync - >(responseStream); - } - else - { - getBranchesError = true; - } - - shouldRender = true; - } - - public class GitHubBranch - { - [JsonPropertyName("name")] - public string Name { get; set; } - } -} -``` - -For an additional working example, see the Blazor Server file upload example that uploads files to a web API controller in the article. - -:::zone-end - -## Cross-origin resource sharing (CORS) - -Browser security restricts a webpage from making requests to a different domain than the one that served the webpage. This restriction is called the *same-origin policy*. The same-origin policy restricts (but doesn't prevent) a malicious site from reading sensitive data from another site. To make requests from the browser to an endpoint with a different origin, the *endpoint* must enable [cross-origin resource sharing (CORS)](https://www.w3.org/TR/cors/). - -:::zone pivot="webassembly" - -For information on CORS requests in Blazor WebAssembly apps, see . - -For information on CORS, see . The article's examples don't pertain directly to Blazor WebAssembly apps, but the article is useful for learning general CORS concepts. - -:::zone-end - -:::zone pivot="server" - -For more information, see . - -:::zone-end - -## Blazor framework component examples for testing web API access - -Various network tools are publicly available for testing web API backend apps directly, such as [Firefox Browser Developer](https://www.mozilla.org/firefox/developer/) and [Postman](https://www.postman.com). Blazor framework's reference source includes test assets that are useful for testing: - -[`HttpClientTest` assets in the `dotnet/aspnetcore` GitHub repository](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/BasicTestApp/HttpClientTest) - -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -## Additional resources - -:::zone pivot="webassembly" - -* : Includes coverage on using to make secure web API requests. -* : Although the content applies to ASP.NET Core apps, not Blazor WebAssembly apps, the article covers general CORS concepts. -* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) -* [Fetch API](https://developer.mozilla.org/docs/Web/API/fetch) - -:::zone-end - -:::zone pivot="server" - -* : Includes coverage on using to make secure web API requests. -* -* -* -* [Kestrel HTTPS endpoint configuration](xref:fundamentals/servers/kestrel#endpoint-configuration) -* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) +* [Cross-Origin resource sharing (CORS) at W3C](https://www.w3.org/TR/cors/) :::zone-end diff --git a/aspnetcore/blazor/components/built-in-components.md b/aspnetcore/blazor/components/built-in-components.md index 90de442ad1c7..519699c96e6d 100644 --- a/aspnetcore/blazor/components/built-in-components.md +++ b/aspnetcore/blazor/components/built-in-components.md @@ -5,17 +5,17 @@ description: Find information on Razor components provided by the Blazor framewo monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 06/09/2022 +ms.date: 03/30/2023 uid: blazor/components/built-in-components --- # ASP.NET Core built-in Razor components -This article lists the Razor components that are provided by the Blazor framework. - -:::moniker range=">= aspnetcore-6.0" +[!INCLUDE[](~/includes/not-latest-version.md)] The following built-in Razor components are provided by the Blazor framework: +:::moniker range=">= aspnetcore-8.0" + * [`App`](xref:blazor/project-structure) * [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) * [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) @@ -25,69 +25,131 @@ The following built-in Razor components are provided by the Blazor framework: * [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation) * [`HeadContent`](xref:blazor/components/control-head-content) * [`HeadOutlet`](xref:blazor/components/control-head-content) -* [`InputCheckbox`](xref:blazor/forms-validation#built-in-form-components) -* [`InputDate`](xref:blazor/forms-validation#built-in-form-components) +* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) * [`InputFile`](xref:blazor/file-uploads) -* [`InputNumber`](xref:blazor/forms-validation#built-in-form-components) -* [`InputRadio`](xref:blazor/forms-validation#built-in-form-components) -* [`InputRadioGroup`](xref:blazor/forms-validation#built-in-form-components) -* [`InputSelect`](xref:blazor/forms-validation#built-in-form-components) -* [`InputText`](xref:blazor/forms-validation#built-in-form-components) -* [`InputTextArea`](xref:blazor/forms-validation#built-in-form-components) +* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) * [`NavMenu`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) * [`PageTitle`](xref:blazor/components/control-head-content) +* [`QuickGrid`](xref:blazor/components/quickgrid) * [`Router`](xref:blazor/fundamentals/routing#route-templates) * [`RouteView`](xref:blazor/fundamentals/routing#route-templates) +* [`SectionContent`](xref:blazor/components/sections) +* [`SectionOutlet`](xref:blazor/components/sections) * [`Virtualize`](xref:blazor/components/virtualization) :::moniker-end -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" +:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" -The following built-in Razor components are provided by ASP.NET Core: +* [`App`](xref:blazor/project-structure) +* [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) +* [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) +* [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) +* [`DynamicComponent`](xref:blazor/components/dynamiccomponent) +* [`ErrorBoundary`](xref:blazor/fundamentals/handle-errors#error-boundaries) +* [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation) +* [`HeadContent`](xref:blazor/components/control-head-content) +* [`HeadOutlet`](xref:blazor/components/control-head-content) +* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputFile`](xref:blazor/file-uploads) +* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) +* [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) +* [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) +* [`NavMenu`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) +* [`PageTitle`](xref:blazor/components/control-head-content) +* [`QuickGrid`](xref:blazor/components/quickgrid) +* [`Router`](xref:blazor/fundamentals/routing#route-templates) +* [`RouteView`](xref:blazor/fundamentals/routing#route-templates) +* [`Virtualize`](xref:blazor/components/virtualization) + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" * [`App`](xref:blazor/project-structure) * [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) * [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) * [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) -* [`InputCheckbox`](xref:blazor/forms-validation#built-in-form-components) -* [`InputDate`](xref:blazor/forms-validation#built-in-form-components) +* [`DynamicComponent`](xref:blazor/components/dynamiccomponent) +* [`ErrorBoundary`](xref:blazor/fundamentals/handle-errors#error-boundaries) +* [`FocusOnNavigate`](xref:blazor/fundamentals/routing#focus-an-element-on-navigation) +* [`HeadContent`](xref:blazor/components/control-head-content) +* [`HeadOutlet`](xref:blazor/components/control-head-content) +* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) * [`InputFile`](xref:blazor/file-uploads) -* [`InputNumber`](xref:blazor/forms-validation#built-in-form-components) -* [`InputRadio`](xref:blazor/forms-validation#built-in-form-components) -* [`InputRadioGroup`](xref:blazor/forms-validation#built-in-form-components) -* [`InputSelect`](xref:blazor/forms-validation#built-in-form-components) -* [`InputText`](xref:blazor/forms-validation#built-in-form-components) -* [`InputTextArea`](xref:blazor/forms-validation#built-in-form-components) +* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) * [`NavMenu`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) +* [`PageTitle`](xref:blazor/components/control-head-content) * [`Router`](xref:blazor/fundamentals/routing#route-templates) * [`RouteView`](xref:blazor/fundamentals/routing#route-templates) * [`Virtualize`](xref:blazor/components/virtualization) :::moniker-end -:::moniker range="< aspnetcore-5.0" +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -The following built-in Razor components are provided by the Blazor framework: +* [`App`](xref:blazor/project-structure) +* [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) +* [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) +* [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) +* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputFile`](xref:blazor/file-uploads) +* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) +* [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) +* [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) +* [`NavMenu`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) +* [`Router`](xref:blazor/fundamentals/routing#route-templates) +* [`RouteView`](xref:blazor/fundamentals/routing#route-templates) +* [`Virtualize`](xref:blazor/components/virtualization) + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" * [`App`](xref:blazor/project-structure) * [`Authentication`](xref:blazor/security/webassembly/index#authentication-component) * [`AuthorizeView`](xref:blazor/security/index#authorizeview-component) * [`CascadingValue`](xref:blazor/components/cascading-values-and-parameters#cascadingvalue-component) -* [`InputCheckbox`](xref:blazor/forms-validation#built-in-form-components) -* [`InputDate`](xref:blazor/forms-validation#built-in-form-components) -* [`InputNumber`](xref:blazor/forms-validation#built-in-form-components) -* [`InputRadio`](xref:blazor/forms-validation#built-in-form-components) -* [`InputRadioGroup`](xref:blazor/forms-validation#built-in-form-components) -* [`InputSelect`](xref:blazor/forms-validation#built-in-form-components) -* [`InputText`](xref:blazor/forms-validation#built-in-form-components) -* [`InputTextArea`](xref:blazor/forms-validation#built-in-form-components) +* [`InputCheckbox`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputDate`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputNumber`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadio`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputRadioGroup`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputSelect`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputText`](xref:blazor/forms-and-input-components#built-in-input-components) +* [`InputTextArea`](xref:blazor/forms-and-input-components#built-in-input-components) * [`LayoutView`](xref:blazor/components/layouts#apply-a-layout-to-arbitrary-content-layoutview-component) * [`MainLayout`](xref:blazor/components/layouts#mainlayout-component) * [`NavLink`](xref:blazor/fundamentals/routing#navlink-and-navmenu-components) diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 6a7421f13eaa..e38faeba6256 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -5,17 +5,20 @@ description: Learn how to flow data from an ancestor Razor component to descende monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/cascading-values-and-parameters --- # ASP.NET Core Blazor cascading values and parameters -This article explains how to flow data from an ancestor Razor component to descendent components. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains how to flow data from an ancestor Razor component to descendent components. *Cascading values and parameters* provide a convenient way to flow data down a component hierarchy from an ancestor component to any number of descendent components. Unlike [Component parameters](xref:blazor/components/index#component-parameters), cascading values and parameters don't require an attribute assignment for each descendent component where the data is consumed. Cascading values and parameters also allow components to coordinate with each other across a component hierarchy. +> [!NOTE] +> The code examples in this article adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core 6.0 or later. When targeting ASP.NET Core 5.0 or earlier, remove the null type designation (`?`) from the `CascadingType?`, `@ActiveTab?`, `RenderFragment?`, `ITab?`, `TabSet?`, and `string?` types in the article's examples. + ## `CascadingValue` component An ancestor component provides a cascading value using the Blazor framework's [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component, which wraps a subtree of a component hierarchy and supplies a single value to all of the components within its subtree. @@ -29,257 +32,57 @@ The following `ThemeInfo` C# class is placed in a folder named `UIThemeClasses` `UIThemeClasses/ThemeInfo.cs`: -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs"::: +:::moniker range=">= aspnetcore-7.0" -The following [layout component](xref:blazor/components/layouts) specifies theme information (`ThemeInfo`) as a cascading value for all components that make up the layout body of the property. `ButtonClass` is assigned a value of [`btn-success`](https://getbootstrap.com/docs/5.0/components/buttons/), which is a Bootstrap button style. Any descendent component in the component hierarchy can use the `ButtonClass` property through the `ThemeInfo` cascading value. +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs"::: -`Shared/MainLayout.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/MainLayout.razor" highlight="2,10-14,19"::: - -## `[CascadingParameter]` attribute - -To make use of cascading values, descendent components declare cascading parameters using the [`[CascadingParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). Cascading values are bound to cascading parameters **by type**. Cascading multiple values of the same type is covered in the [Cascade multiple values](#cascade-multiple-values) section later in this article. - -The following component binds the `ThemeInfo` cascading value to a cascading parameter, optionally using the same name of `ThemeInfo`. The parameter is used to set the CSS class for the **`Increment Counter (Themed)`** button. - -`Pages/ThemedCounter.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/ThemedCounter.razor"::: - -Similar to a regular component parameter, components accepting a cascading parameter are rerendered when the cascading value is changed. For instance, configuring a different theme instance causes the `ThemedCounter` component from the [`CascadingValue` component](#cascadingvalue-component) section to rerender: - -`Shared/MainLayout.razor`: - -```razor -
- -
- @Body -
-
- -
- -@code { - private ThemeInfo theme = new() { ButtonClass = "btn-success" }; - - private void ChangeToDarkTheme() - { - theme = new() { ButtonClass = "btn-darkmode-success" }; - } -} -``` - - can be used to indicate that a cascading parameter doesn't change after initialization. - -## Cascade multiple values - -To cascade multiple values of the same type within the same subtree, provide a unique string to each [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component and their corresponding [`[CascadingParameter]` attributes](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). - -In the following example, two [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) components cascade different instances of `CascadingType`: - -```razor - - - ... - - - -@code { - private CascadingType? parentCascadeParameter1; - - [Parameter] - public CascadingType? ParentCascadeParameter2 { get; set; } -} -``` - -In a descendant component, the cascaded parameters receive their cascaded values from the ancestor component by : - -```razor -@code { - [CascadingParameter(Name = "CascadeParam1")] - protected CascadingType? ChildCascadeParameter1 { get; set; } - - [CascadingParameter(Name = "CascadeParam2")] - protected CascadingType? ChildCascadeParameter2 { get; set; } -} -``` - -## Pass data across a component hierarchy - -Cascading parameters also enable components to pass data across a component hierarchy. Consider the following UI tab set example, where a tab set component maintains a series of individual tabs. - -> [!NOTE] -> For the examples in this section, the app's namespace is `BlazorSample`. When experimenting with the code in your own sample app, change the namespace to your sample app's namespace. - -Create an `ITab` interface that tabs implement in a folder named `UIInterfaces`. - -`UIInterfaces/ITab.cs`: - -```csharp -using Microsoft.AspNetCore.Components; - -namespace BlazorSample.UIInterfaces -{ - public interface ITab - { - RenderFragment ChildContent { get; } - } -} -``` - -> [!NOTE] -> For more information on , see . - -The following `TabSet` component maintains a set of tabs. The tab set's `Tab` components, which are created later in this section, supply the list items (`
  • ...
  • `) for the list (`
      ...
    `). - -Child `Tab` components aren't explicitly passed as parameters to the `TabSet`. Instead, the child `Tab` components are part of the child content of the `TabSet`. However, the `TabSet` still needs a reference each `Tab` component so that it can render the headers and the active tab. To enable this coordination without requiring additional code, the `TabSet` component *can provide itself as a cascading value* that is then picked up by the descendent `Tab` components. - -`Shared/TabSet.razor`: - -```razor -@using BlazorSample.UIInterfaces - - - - - - - - - - - -@code { - [Parameter] - public RenderFragment? ChildContent { get; set; } - - public ITab? ActiveTab { get; private set; } - - public void AddTab(ITab tab) - { - if (ActiveTab is null) - { - SetActiveTab(tab); - } - } - - public void SetActiveTab(ITab tab) - { - if (ActiveTab != tab) - { - ActiveTab = tab; - StateHasChanged(); - } - } -} -``` - -Descendent `Tab` components capture the containing `TabSet` as a cascading parameter. The `Tab` components add themselves to the `TabSet` and coordinate to set the active tab. +:::moniker-end -`Shared/Tab.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```razor -@using BlazorSample.UIInterfaces -@implements ITab - -
  • - - @Title - -
  • +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs"::: -@code { - [CascadingParameter] - public TabSet? ContainerTabSet { get; set; } +:::moniker-end - [Parameter] - public string? Title { get; set; } +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - [Parameter] - public RenderFragment? ChildContent { get; set; } +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs"::: - private string? TitleCssClass => - ContainerTabSet?.ActiveTab == this ? "active" : null; +:::moniker-end - protected override void OnInitialized() - { - ContainerTabSet?.AddTab(this); - } +:::moniker range="< aspnetcore-5.0" - private void ActivateTab() - { - ContainerTabSet?.SetActiveTab(this); - } -} -``` +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs"::: -The following `ExampleTabSet` component uses the `TabSet` component, which contains three `Tab` components. +:::moniker-end -`Pages/ExampleTabSet.razor`: +The following [layout component](xref:blazor/components/layouts) specifies theme information (`ThemeInfo`) as a cascading value for all components that make up the layout body of the property. `ButtonClass` is assigned a value of [`btn-success`](https://getbootstrap.com/docs/5.0/components/buttons/), which is a Bootstrap button style. Any descendent component in the component hierarchy can use the `ButtonClass` property through the `ThemeInfo` cascading value. -```razor -@page "/example-tab-set" +`Shared/MainLayout.razor`: - - -

    Greetings from the first tab!

    +:::moniker range=">= aspnetcore-7.0" - -
    +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/MainLayout.razor"::: - -

    Hello from the second tab!

    -
    +:::moniker-end - @if (showThirdTab) - { - -

    Welcome to the disappearing third tab!

    -

    Toggle this tab from the first tab.

    -
    - } -
    +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -@code { - private bool showThirdTab; -} -``` +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/MainLayout.razor"::: :::moniker-end :::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -*Cascading values and parameters* provide a convenient way to flow data down a component hierarchy from an ancestor component to any number of descendent components. Unlike [Component parameters](xref:blazor/components/index#component-parameters), cascading values and parameters don't require an attribute assignment for each descendent component where the data is consumed. Cascading values and parameters also allow components to coordinate with each other across a component hierarchy. - -## `CascadingValue` component - -An ancestor component provides a cascading value using the Blazor framework's [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component, which wraps a subtree of a component hierarchy and supplies a single value to all of the components within its subtree. - -The following example demonstrates the flow of theme information down the component hierarchy of a layout component to provide a CSS style class to buttons in child components. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/MainLayout.razor"::: -The following `ThemeInfo` C# class is placed in a folder named `UIThemeClasses` and specifies the theme information. - -> [!NOTE] -> For the examples in this section, the app's namespace is `BlazorSample`. When experimenting with the code in your own sample app, change the app's namespace to your sample app's namespace. - -`UIThemeClasses/ThemeInfo.cs`: - -[!code-csharp[](~/blazor/samples/5.0/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs)] +:::moniker-end -The following [layout component](xref:blazor/components/layouts) specifies theme information (`ThemeInfo`) as a cascading value for all components that make up the layout body of the property. `ButtonClass` is assigned a value of [`btn-success`](https://getbootstrap.com/docs/5.0/components/buttons/), which is a Bootstrap button style. Any descendent component in the component hierarchy can use the `ButtonClass` property through the `ThemeInfo` cascading value. +:::moniker range="< aspnetcore-5.0" -`Shared/MainLayout.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/MainLayout.razor"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/MainLayout.razor?highlight=2,10-14,19)] +:::moniker-end ## `[CascadingParameter]` attribute @@ -289,230 +92,64 @@ The following component binds the `ThemeInfo` cascading value to a cascading par `Pages/ThemedCounter.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/ThemedCounter.razor?highlight=2,15-17,23-24)] - -## Cascade multiple values - -To cascade multiple values of the same type within the same subtree, provide a unique string to each [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component and their corresponding [`[CascadingParameter]` attributes](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). - -In the following example, two [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) components cascade different instances of `CascadingType`: - -```razor - - - ... - - - -@code { - private CascadingType parentCascadeParameter1; - - [Parameter] - public CascadingType ParentCascadeParameter2 { get; set; } - - ... -} -``` - -In a descendant component, the cascaded parameters receive their cascaded values from the ancestor component by : - -```razor -... - -@code { - [CascadingParameter(Name = "CascadeParam1")] - protected CascadingType ChildCascadeParameter1 { get; set; } - - [CascadingParameter(Name = "CascadeParam2")] - protected CascadingType ChildCascadeParameter2 { get; set; } -} -``` - -## Pass data across a component hierarchy - -Cascading parameters also enable components to pass data across a component hierarchy. Consider the following UI tab set example, where a tab set component maintains a series of individual tabs. - -> [!NOTE] -> For the examples in this section, the app's namespace is `BlazorSample`. When experimenting with the code in your own sample app, change the namespace to your sample app's namespace. - -Create an `ITab` interface that tabs implement in a folder named `UIInterfaces`. - -`UIInterfaces/ITab.cs`: - -```csharp -using Microsoft.AspNetCore.Components; - -namespace BlazorSample.UIInterfaces -{ - public interface ITab - { - RenderFragment ChildContent { get; } - } -} -``` - -> [!NOTE] -> For more information on , see . +:::moniker range=">= aspnetcore-7.0" -The following `TabSet` component maintains a set of tabs. The tab set's `Tab` components, which are created later in this section, supply the list items (`
  • ...
  • `) for the list (`
      ...
    `). +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/ThemedCounter.razor"::: -Child `Tab` components aren't explicitly passed as parameters to the `TabSet`. Instead, the child `Tab` components are part of the child content of the `TabSet`. However, the `TabSet` still needs a reference each `Tab` component so that it can render the headers and the active tab. To enable this coordination without requiring additional code, the `TabSet` component *can provide itself as a cascading value* that is then picked up by the descendent `Tab` components. +:::moniker-end -`Shared/TabSet.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```razor -@using BlazorSample.UIInterfaces +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/ThemedCounter.razor"::: - +:::moniker-end - - - +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/ThemedCounter.razor"::: - +:::moniker-end -@code { - [Parameter] - public RenderFragment ChildContent { get; set; } +:::moniker range="< aspnetcore-5.0" - public ITab ActiveTab { get; private set; } +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/ThemedCounter.razor"::: - public void AddTab(ITab tab) - { - if (ActiveTab == null) - { - SetActiveTab(tab); - } - } +:::moniker-end - public void SetActiveTab(ITab tab) - { - if (ActiveTab != tab) - { - ActiveTab = tab; - StateHasChanged(); - } - } -} -``` +:::moniker range=">= aspnetcore-6.0" -Descendent `Tab` components capture the containing `TabSet` as a cascading parameter. The `Tab` components add themselves to the `TabSet` and coordinate to set the active tab. +Similar to a regular component parameter, components accepting a cascading parameter are rerendered when the cascading value is changed. For instance, configuring a different theme instance causes the `ThemedCounter` component from the [`CascadingValue` component](#cascadingvalue-component) section to rerender: -`Shared/Tab.razor`: +`Shared/MainLayout.razor`: ```razor -@using BlazorSample.UIInterfaces -@implements ITab +
    +
    + About +
    -
  • - - @Title - -
  • + +
    + @Body +
    +
    + +
    @code { - [CascadingParameter] - public TabSet ContainerTabSet { get; set; } - - [Parameter] - public string Title { get; set; } - - [Parameter] - public RenderFragment ChildContent { get; set; } - - private string TitleCssClass => - ContainerTabSet.ActiveTab == this ? "active" : null; - - protected override void OnInitialized() - { - ContainerTabSet.AddTab(this); - } - - private void ActivateTab() + private ThemeInfo theme = new() { ButtonClass = "btn-success" }; + + private void ChangeToDarkTheme() { - ContainerTabSet.SetActiveTab(this); + theme = new() { ButtonClass = "btn-darkmode-success" }; } } ``` -The following `ExampleTabSet` component uses the `TabSet` component, which contains three `Tab` components. - -`Pages/ExampleTabSet.razor`: - -```razor -@page "/example-tab-set" - - - -

    Greetings from the first tab!

    - - -
    - - -

    Hello from the second tab!

    -
    - - @if (showThirdTab) - { - -

    Welcome to the disappearing third tab!

    -

    Toggle this tab from the first tab.

    -
    - } -
    - -@code { - private bool showThirdTab; -} -``` + can be used to indicate that a cascading parameter doesn't change after initialization. :::moniker-end -:::moniker range="< aspnetcore-5.0" - -*Cascading values and parameters* provide a convenient way to flow data down a component hierarchy from an ancestor component to any number of descendent components. Unlike [Component parameters](xref:blazor/components/index#component-parameters), cascading values and parameters don't require an attribute assignment for each descendent component where the data is consumed. Cascading values and parameters also allow components to coordinate with each other across a component hierarchy. - -## `CascadingValue` component - -An ancestor component provides a cascading value using the Blazor framework's [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component, which wraps a subtree of a component hierarchy and supplies a single value to all of the components within its subtree. - -The following example demonstrates the flow of theme information down the component hierarchy of a layout component to provide a CSS style class to buttons in child components. - -The following `ThemeInfo` C# class is placed in a folder named `UIThemeClasses` and specifies the theme information. - -> [!NOTE] -> For the examples in this section, the app's namespace is `BlazorSample`. When experimenting with the code in your own sample app, change the app's namespace to your sample app's namespace. - -`UIThemeClasses/ThemeInfo.cs`: - -[!code-csharp[](~/blazor/samples/3.1/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs)] - -The following [layout component](xref:blazor/components/layouts) specifies theme information (`ThemeInfo`) as a cascading value for all components that make up the layout body of the property. `ButtonClass` is assigned a value of [`btn-success`](https://getbootstrap.com/docs/5.0/components/buttons/), which is a Bootstrap button style. Any descendent component in the component hierarchy can use the `ButtonClass` property through the `ThemeInfo` cascading value. - -`Shared/MainLayout.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/MainLayout.razor?highlight=2,9-13,17)] - -## `[CascadingParameter]` attribute - -To make use of cascading values, descendent components declare cascading parameters using the [`[CascadingParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). Cascading values are bound to cascading parameters **by type**. Cascading multiple values of the same type is covered in the [Cascade multiple values](#cascade-multiple-values) section later in this article. - -The following component binds the `ThemeInfo` cascading value to a cascading parameter, optionally using the same name of `ThemeInfo`. The parameter is used to set the CSS class for the **`Increment Counter (Themed)`** button. - -`Pages/ThemedCounter.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/ThemedCounter.razor?highlight=2,15-17,23-24)] - ## Cascade multiple values To cascade multiple values of the same type within the same subtree, provide a unique string to each [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component and their corresponding [`[CascadingParameter]` attributes](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). @@ -527,26 +164,22 @@ In the following example, two [`CascadingValue`](xref:Microsoft.AspNetCore.Compo @code { - private CascadingType parentCascadeParameter1; + private CascadingType? parentCascadeParameter1; [Parameter] - public CascadingType ParentCascadeParameter2 { get; set; } - - ... + public CascadingType? ParentCascadeParameter2 { get; set; } } ``` In a descendant component, the cascaded parameters receive their cascaded values from the ancestor component by : ```razor -... - @code { [CascadingParameter(Name = "CascadeParam1")] - protected CascadingType ChildCascadeParameter1 { get; set; } - + protected CascadingType? ChildCascadeParameter1 { get; set; } + [CascadingParameter(Name = "CascadeParam2")] - protected CascadingType ChildCascadeParameter2 { get; set; } + protected CascadingType? ChildCascadeParameter2 { get; set; } } ``` @@ -564,12 +197,11 @@ Create an `ITab` interface that tabs implement in a folder named `UIInterfaces`. ```csharp using Microsoft.AspNetCore.Components; -namespace BlazorSample.UIInterfaces +namespace BlazorSample.UIInterfaces; + +public interface ITab { - public interface ITab - { - RenderFragment ChildContent { get; } - } + RenderFragment ChildContent { get; } } ``` @@ -601,13 +233,13 @@ Child `Tab` components aren't explicitly passed as parameters to the `TabSet`. I @code { [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } - public ITab ActiveTab { get; private set; } + public ITab? ActiveTab { get; private set; } public void AddTab(ITab tab) { - if (ActiveTab == null) + if (ActiveTab is null) { SetActiveTab(tab); } @@ -640,25 +272,25 @@ Descendent `Tab` components capture the containing `TabSet` as a cascading param @code { [CascadingParameter] - public TabSet ContainerTabSet { get; set; } + public TabSet? ContainerTabSet { get; set; } [Parameter] - public string Title { get; set; } + public string? Title { get; set; } [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } - private string TitleCssClass => - ContainerTabSet.ActiveTab == this ? "active" : null; + private string? TitleCssClass => + ContainerTabSet?.ActiveTab == this ? "active" : null; protected override void OnInitialized() { - ContainerTabSet.AddTab(this); + ContainerTabSet?.AddTab(this); } private void ActivateTab() { - ContainerTabSet.SetActiveTab(this); + ContainerTabSet?.SetActiveTab(this); } } ``` @@ -697,5 +329,3 @@ The following `ExampleTabSet` component uses the `TabSet` component, which conta private bool showThirdTab; } ``` - -:::moniker-end diff --git a/aspnetcore/blazor/components/class-libraries.md b/aspnetcore/blazor/components/class-libraries.md index 7c5418e9ff88..b036ef135ff5 100644 --- a/aspnetcore/blazor/components/class-libraries.md +++ b/aspnetcore/blazor/components/class-libraries.md @@ -5,12 +5,12 @@ description: Discover how components can be included in Blazor apps from an exte monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/class-libraries --- # Consume ASP.NET Core Razor components from a Razor class library (RCL) -:::moniker range=">= aspnetcore-6.0" +[!INCLUDE[](~/includes/not-latest-version.md)] Components can be shared in a [Razor class library (RCL)](xref:razor-pages/ui-class) across projects. Include components and static assets in an app from: @@ -37,6 +37,8 @@ Just as components are regular .NET types, components provided by an RCL are nor 1. Right-click the app project. Select **Add** > **Project Reference**. 1. Select the RCL project. Select **OK**. +:::moniker range=">= aspnetcore-5.0" + If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template: * Add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: @@ -55,20 +57,34 @@ If the **Support pages and views** checkbox is selected to support pages and vie For more information on the `SupportedPlatform` item, see the [Browser compatibility analyzer for Blazor WebAssembly](#browser-compatibility-analyzer-for-blazor-webassembly) section. +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: + +```razor +@using Microsoft.AspNetCore.Components.Web +``` + +:::moniker-end + # [Visual Studio for Mac](#tab/visual-studio-mac) 1. Create a new project. -1. In the sidebar under **Web and Console**, select **App**. Under **ASP.NET Core**, select **Razor Class Library** from the project templates shown. Select **Next**. -1. Select the target framework for the library with the **Target Framework** dropdown list. Select **Next**. -1. In the **Configure your new Class Library** dialog, provide a project name in the **Project Name** field. Examples in this topic use the project name `ComponentLibrary`. Select **Create**. +1. In the sidebar under **Web and Console**, select **App**. Under **ASP.NET Core**, select **Razor Class Library** from the project templates. Select **Continue**. +1. Select the target framework for the library with the **Target framework** dropdown list. Select **Continue**. +1. Provide a project name in the **Project name** field. Examples in this topic use the project name `ComponentLibrary`. Select **Create**. 1. Add the RCL to a solution: 1. Open the solution. - 1. Right-click the solution in **Solution Explorer**. Select **Add** > **Existing Project**. + 1. Right-click the solution in the **Solution** sidebar and select **Add** > **Existing Project**. Alternatively, use the **Add** > **Existing Project** menu command from the **File** menu. 1. Navigate to the RCL's project file. - 1. Select the RCL's project file (`.csproj`). + 1. Select the RCL's project file (`.csproj`) and select **Open**. 1. Add a reference to the RCL from the app: - 1. Right-click the app project. Select **Add** > **Reference**. - 1. Select the RCL project. Select **OK**. + 1. Right-click the app project. Select **Add** > **Reference**. Alternatively, select the **Add Project Reference** menu command from the **Project** menu. + 1. Select the RCL project's checkbox and reference the project with the **Select** button. + +:::moniker range=">= aspnetcore-5.0" If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template: @@ -88,6 +104,18 @@ If the **Support pages and views** checkbox is selected to support pages and vie For more information on the `SupportedPlatform` item, see the [Browser compatibility analyzer for Blazor WebAssembly](#browser-compatibility-analyzer-for-blazor-webassembly) section. +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: + +```razor +@using Microsoft.AspNetCore.Components.Web +``` + +:::moniker-end + # [Visual Studio Code / .NET Core CLI](#tab/visual-studio-code+netcore-cli) 1. Use the **Razor Class Library** project template (`razorclasslib`) with the [`dotnet new`](/dotnet/core/tools/dotnet-new) command in a command shell. In the following example, an RCL is created and named `ComponentLibrary` using the `-o|--output` option. The folder that holds `ComponentLibrary` is created automatically when the command is executed: @@ -102,6 +130,8 @@ If the **Support pages and views** checkbox is selected to support pages and vie dotnet add reference {PATH TO LIBRARY} ``` +:::moniker range=">= aspnetcore-5.0" + If the `-s|--support-pages-and-views` option is used to support pages and views when generating the RCL from the template: * Add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: @@ -120,6 +150,18 @@ If the `-s|--support-pages-and-views` option is used to support pages and views For more information on the `SupportedPlatform` item, see the [Browser compatibility analyzer for Blazor WebAssembly](#browser-compatibility-analyzer-for-blazor-webassembly) section. +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +If the `-s|--support-pages-and-views` option is used to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: + +```razor +@using Microsoft.AspNetCore.Components.Web +``` + +:::moniker-end + --- ## Consume a Razor component from an RCL @@ -169,6 +211,8 @@ Alternatively, add a [`@using`](xref:mvc/views/razor#using) directive and use th ``` +:::moniker range=">= aspnetcore-6.0" + For library components that use [CSS isolation](xref:blazor/components/css-isolation), the component styles are automatically made available to the consuming app. There's no need to manually link or import the library's individual component stylesheets or its bundled CSS file in the app that consumes the library. The app uses CSS imports to reference the RCL's bundled styles. The bundled styles aren't published as a static web asset of the app that consumes the library. For a class library named `ClassLib` and a Blazor app with a `BlazorSample.styles.css` stylesheet, the RCL's stylesheet is imported at the top of the app's stylesheet automatically at build time: ```css @@ -243,19 +287,77 @@ Add a page to the app that uses the `ExtraStyles` component from the RCL. ``` -Link to the library's stylesheet in the app's `` markup. - -`wwwroot/index.html` file (Blazor WebAssembly) or `Pages/_Layout.cshtml` file (Blazor Server): +Link to the library's stylesheet in the app's `` markup ([location of `` content](xref:blazor/project-structure#location-of-head-content)). ```html ``` +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +For library components that use [CSS isolation](xref:blazor/components/css-isolation), the component styles are automatically made available to the consuming app. There's no need to manually link or import the library's individual component stylesheets or its bundled CSS file in the app that consumes the library. The app uses CSS imports to reference the RCL's bundled styles. The bundled styles aren't published as a static web asset of the app that consumes the library. For a class library named `ClassLib` and a Blazor app with a `BlazorSample.styles.css` stylesheet, the RCL's stylesheet is imported at the top of the app's stylesheet automatically at build time: + +```css +@import '_content/ClassLib/ClassLib.bundle.scp.css'; +``` + +For the preceding examples, `Component1`'s stylesheet (`Component1.razor.css`) is bundled automatically. + +`Component1.razor.css` in the `ComponentLibrary` RCL: + +```css +.my-component { + border: 2px dashed red; + padding: 1em; + margin: 1em 0; + background-image: url('background.png'); +} +``` + +The background image is also included from the RCL project template and resides in the `wwwroot` folder of the RCL. + +`wwwroot/background.png` in the `ComponentLibrary` RCL: + +![Diagonally-striped background image from the RCL project template](~/blazor/components/class-libraries/_static/background.png) + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +The following background image and stylesheet are used by the RCL's `Component1` example component. There's no need to add these static assets to a new RCL created from the RCL project template, as they're added automatically by the project template. + +`wwwroot/background.png` in the `ComponentLibrary` RCL: + +![Diagonally-striped background image added to the library by the RCL project template](~/blazor/components/class-libraries/_static/background.png) + +`wwwroot/styles.css` in the `ComponentLibrary` RCL: + +```css +.my-component { + border: 2px dashed red; + padding: 1em; + margin: 1em 0; + background-image: url('background.png'); +} +``` + +To provide `Component1`'s `my-component` CSS class, link to the library's stylesheet in the app's `` markup. + +`wwwroot/index.html` file (Blazor WebAssembly) or `Pages/_Host.cshtml` file (Blazor Server): + +```html + +``` + +:::moniker-end + ## Create an RCL with static assets in the `wwwroot` folder An RCL's static assets are available to any app that consumes the library. -Place static assets in the `wwwroot` folder of the RCL and reference the static assets with the following path in the app: `_content/{PACKAGE ID}/{PATH AND FILE NAME}`. The `{PACKAGE ID}` placeholder is the library's [package ID](/nuget/create-packages/creating-a-package-msbuild#set-properties). The package ID defaults to the project's assembly name if `` isn't specified in the project file. The `{PATH AND FILE NAME}` placeholder is path and file name under `wwwroot`. +Place static assets in the `wwwroot` folder of the RCL and reference the static assets with the following path in the app: `_content/{PACKAGE ID}/{PATH AND FILE NAME}`. The `{PACKAGE ID}` placeholder is the library's [package ID](/nuget/create-packages/creating-a-package-msbuild#set-properties). The package ID defaults to the project's assembly name if `` isn't specified in the project file. The `{PATH AND FILE NAME}` placeholder is path and file name under `wwwroot`. This path format is also used in the app for static assets supplied by NuGet packages added to the RCL. The following example demonstrates the use of RCL static assets with an RCL named `ComponentLibrary` and a Blazor app that consumes the RCL. The app has a project reference for the `ComponentLibrary` RCL. @@ -310,14 +412,20 @@ Rendered `Jeep` component: For more information, see . +:::moniker range=">= aspnetcore-6.0" + ## Create an RCL with JavaScript files collocated with components [!INCLUDE[](~/includes/js-collocation.md)] +:::moniker-end + ## Supply components and static assets to multiple hosted Blazor apps For more information, see . +:::moniker range=">= aspnetcore-5.0" + ## Browser compatibility analyzer for Blazor WebAssembly Blazor WebAssembly apps target the full .NET API surface area, but not all .NET APIs are supported on WebAssembly due to browser sandbox constraints. Unsupported APIs throw when running on WebAssembly. A platform compatibility analyzer warns the developer when the app uses APIs that aren't supported by the app's target platforms. For Blazor WebAssembly apps, this means checking that APIs are supported in browsers. Annotating .NET framework APIs for the compatibility analyzer is an on-going process, so not all .NET framework API is currently annotated. @@ -346,6 +454,10 @@ private static string GetLoggingDirectory() For more information, see [Annotating APIs as unsupported on specific platforms (dotnet/designs GitHub repository](https://github.com/dotnet/designs/blob/main/accepted/2020/platform-exclusion/platform-exclusion.md#build-configuration-for-platforms). +:::moniker-end + +:::moniker range=">= aspnetcore-5.0" + ## JavaScript isolation in JavaScript modules Blazor enables JavaScript isolation in standard [JavaScript modules](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules). JavaScript isolation provides the following benefits: @@ -355,6 +467,16 @@ Blazor enables JavaScript isolation in standard [JavaScript modules](https://dev For more information, see . +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + +## Avoid trimming JavaScript-invokable .NET methods + +[Runtime relinking](xref:blazor/host-and-deploy/webassembly#runtime-relinking) trims class instance JavaScript-invokable .NET methods unless they're explicitly preserved. For more information, see . + +:::moniker-end + ## Build, pack, and ship to NuGet Because Razor class libraries that contain Razor components are standard .NET libraries, packing and shipping them to NuGet is no different from packing and shipping any library to NuGet. Packing is performed using the [`dotnet pack`](/dotnet/core/tools/dotnet-pack) command in a command shell: @@ -371,296 +493,8 @@ Upload the package to NuGet using the [`dotnet nuget push`](/dotnet/core/tools/d ## Additional resources -* -* [Add an XML Intermediate Language (IL) Trimmer configuration file to a library](xref:blazor/host-and-deploy/configure-trimmer) -* [CSS isolation support with Razor class libraries](xref:blazor/components/css-isolation#razor-class-library-rcl-support) - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -Components can be shared in a [Razor class library (RCL)](xref:razor-pages/ui-class) across projects. Include components and static assets in an app from: - -* Another project in the [solution](xref:blazor/tooling#visual-studio-solution-file-sln). -* A referenced .NET library. -* A NuGet package. - -Just as components are regular .NET types, components provided by an RCL are normal .NET assemblies. - -## Create an RCL - -# [Visual Studio](#tab/visual-studio) - -1. Create a new project. -1. In the **Create a new project** dialog, select **Razor Class Library** from the list of ASP.NET Core project templates. Select **Next**. -1. In the **Configure your new project** dialog, provide a project name in the **Project name** field or accept the default project name. Examples in this topic use the project name `ComponentLibrary`. Select **Create**. -1. In the **Create a new Razor class library** dialog, select **Create**. -1. Add the RCL to a solution: - 1. Open the solution. - 1. Right-click the solution in **Solution Explorer**. Select **Add** > **Existing Project**. - 1. Navigate to the RCL's project file. - 1. Select the RCL's project file (`.csproj`). -1. Add a reference to the RCL from the app: - 1. Right-click the app project. Select **Add** > **Project Reference**. - 1. Select the RCL project. Select **OK**. - -If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template: - -* Add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: - - ```razor - @using Microsoft.AspNetCore.Components.Web - ``` - -* Add the following `SupportedPlatform` item to the project file (`.csproj`): - - ```xml - - - - ``` - - For more information on the `SupportedPlatform` item, see the [Browser compatibility analyzer for Blazor WebAssembly](#browser-compatibility-analyzer-for-blazor-webassembly) section. - -# [Visual Studio for Mac](#tab/visual-studio-mac) - -1. Create a new project. -1. In the sidebar under **Web and Console**, select **App**. Under **ASP.NET Core**, select **Razor Class Library** from the project templates shown. Select **Next**. -1. Select the target framework for the library with the **Target Framework** dropdown list. Select **Next**. -1. In the **Configure your new Class Library** dialog, provide a project name in the **Project Name** field. Examples in this topic use the project name `ComponentLibrary`. Select **Create**. -1. Add the RCL to a solution: - 1. Open the solution. - 1. Right-click the solution in **Solution Explorer**. Select **Add** > **Existing Project**. - 1. Navigate to the RCL's project file. - 1. Select the RCL's project file (`.csproj`). -1. Add a reference to the RCL from the app: - 1. Right-click the app project. Select **Add** > **Reference**. - 1. Select the RCL project. Select **OK**. - -If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template: - -* Add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: - - ```razor - @using Microsoft.AspNetCore.Components.Web - ``` - -* Add the following `SupportedPlatform` item to the project file (`.csproj`): - - ```xml - - - - ``` - - For more information on the `SupportedPlatform` item, see the [Browser compatibility analyzer for Blazor WebAssembly](#browser-compatibility-analyzer-for-blazor-webassembly) section. - -# [Visual Studio Code / .NET Core CLI](#tab/visual-studio-code+netcore-cli) - -1. Use the **Razor Class Library** project template (`razorclasslib`) with the [`dotnet new`](/dotnet/core/tools/dotnet-new) command in a command shell. In the following example, an RCL is created and named `ComponentLibrary` using the `-o|--output` option. The folder that holds `ComponentLibrary` is created automatically when the command is executed: - - ```dotnetcli - dotnet new razorclasslib -o ComponentLibrary - ``` - -1. To add the library to an existing project, use the [`dotnet add reference`](/dotnet/core/tools/dotnet-add-reference) command in a command shell. In the following command, the `{PATH TO LIBRARY}` placeholder is the path to the library's project folder: - - ```dotnetcli - dotnet add reference {PATH TO LIBRARY} - ``` - -If the `-s|--support-pages-and-views` option is used to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: - -```razor -@using Microsoft.AspNetCore.Components.Web -``` - ---- - -## Consume a Razor component from an RCL - -To consume components from an RCL in another project, use either of the following approaches: - -* Use the full component type name, which includes the RCL's namespace. -* Individual components can be added by name without the RCL's namespace if Razor's [`@using`](xref:mvc/views/razor#using) directive declares the RCL's namespace. Use the following approaches: - * Add the `@using` directive to individual components. - * include the `@using` directive in the top-level `_Imports.razor` file to make the library's components available to an entire project. Add the directive to an `_Imports.razor` file at any level to apply the namespace to a single component or set of components within a folder. When an `_Imports.razor` file is used, individual components don't require an `@using` directive for the RCL's namespace. - -In the following examples, `ComponentLibrary` is an RCL containing the `Component1` component. The `Component1` component is an example component automatically added to an RCL created from the RCL project template that isn't created to support pages and views. - -> [!NOTE] -> If the RCL is created to support pages and views, manually add the `Component1` component and its static assets to the RCL if you plan to follow the examples in this article. The component and static assets are shown in this section. - -`Component1.razor` in the `ComponentLibrary` RCL: - -```razor -
    - This component is defined in the ComponentLibrary package. -
    -``` - -In the app that consumes the RCL, reference the `Component1` component using its namespace, as the following example shows. - -`Pages/ConsumeComponent1.razor`: - -```razor -@page "/consume-component-1" - -

    Consume component (full namespace example)

    - - -``` - -Alternatively, add a [`@using`](xref:mvc/views/razor#using) directive and use the component without its namespace. The following `@using` directive can also appear in any `_Imports.razor` file in or above the current folder. - -`Pages/ConsumeComponent2.razor`: - -```razor -@page "/consume-component-2" -@using ComponentLibrary - -

    Consume component (@@using example)

    - - -``` - -For library components that use [CSS isolation](xref:blazor/components/css-isolation), the component styles are automatically made available to the consuming app. There's no need to manually link or import the library's individual component stylesheets or its bundled CSS file in the app that consumes the library. The app uses CSS imports to reference the RCL's bundled styles. The bundled styles aren't published as a static web asset of the app that consumes the library. For a class library named `ClassLib` and a Blazor app with a `BlazorSample.styles.css` stylesheet, the RCL's stylesheet is imported at the top of the app's stylesheet automatically at build time: - -```css -@import '_content/ClassLib/ClassLib.bundle.scp.css'; -``` - -For the preceding examples, `Component1`'s stylesheet (`Component1.razor.css`) is bundled automatically. - -`Component1.razor.css` in the `ComponentLibrary` RCL: - -```css -.my-component { - border: 2px dashed red; - padding: 1em; - margin: 1em 0; - background-image: url('background.png'); -} -``` - -The background image is also included from the RCL project template and resides in the `wwwroot` folder of the RCL. - -`wwwroot/background.png` in the `ComponentLibrary` RCL: - -![Diagonally-striped background image from the RCL project template](~/blazor/components/class-libraries/_static/background.png) - -## Create an RCL with static assets - -An RCL's static assets are available to any app that consumes the library. - -Place static assets in the `wwwroot` folder of the RCL and reference the static assets with the following path in the app: `_content/{PACKAGE ID}/{PATH AND FILE NAME}`. The `{PACKAGE ID}` placeholder is the library's [package ID](/nuget/create-packages/creating-a-package-msbuild#set-properties). The package ID defaults to the project's assembly name if `` isn't specified in the project file. The `{PATH AND FILE NAME}` placeholder is path and file name under `wwwroot`. - -The following example demonstrates the use of RCL static assets with an RCL named `ComponentLibrary` and a Blazor app that consumes the RCL. The app has a project reference for the `ComponentLibrary` RCL. - -The following Jeep® image is used in this section's example. If you implement the example shown in this section, right-click the image to save it locally. - -`wwwroot/jeep-yj.png` in the `ComponentLibrary` RCL: - -![Jeep YJ®](~/blazor/components/class-libraries/_static/jeep-yj.png) - -Add the following `JeepYJ` component to the RCL. - -`JeepYJ.razor` in the `ComponentLibrary` RCL: - -```razor -

    ComponentLibrary.JeepYJ

    - -

    - Jeep YJ® -

    -``` - -Add the following `Jeep` component to the app that consumes the `ComponentLibrary` RCL. The `Jeep` component uses: - -* The Jeep YJ® image from the `ComponentLibrary` RCL's `wwwroot` folder. -* The `JeepYJ` component from the RCL. - -`Pages/Jeep.razor`: - -```razor -@page "/jeep" -@using ComponentLibrary - -
    -

    Direct use

    - -

    - Jeep YJ® -

    -
    - - - -

    - Jeep and Jeep YJ are registered trademarks of - FCA US LLC (Stellantis NV). -

    -``` - -Rendered `Jeep` component: - -![Jeep component](~/blazor/components/class-libraries/_static/jeep-component.png) - -For more information, see . - -## Supply components and static assets to multiple hosted Blazor apps - -For more information, see . - -## Browser compatibility analyzer for Blazor WebAssembly - -Blazor WebAssembly apps target the full .NET API surface area, but not all .NET APIs are supported on WebAssembly due to browser sandbox constraints. Unsupported APIs throw when running on WebAssembly. A platform compatibility analyzer warns the developer when the app uses APIs that aren't supported by the app's target platforms. For Blazor WebAssembly apps, this means checking that APIs are supported in browsers. Annotating .NET framework APIs for the compatibility analyzer is an on-going process, so not all .NET framework API is currently annotated. - -Blazor WebAssembly and RCL projects *automatically* enable browser compatibility checks by adding `browser` as a supported platform with the `SupportedPlatform` MSBuild item. Library developers can manually add the `SupportedPlatform` item to a library's project file to enable the feature: - -```xml - - - -``` - -When authoring a library, indicate that a particular API isn't supported in browsers by specifying `browser` to : - -```csharp -[UnsupportedOSPlatform("browser")] -private static string GetLoggingDirectory() -{ - ... -} -``` - -For more information, see [Annotating APIs as unsupported on specific platforms (dotnet/designs GitHub repository](https://github.com/dotnet/designs/blob/main/accepted/2020/platform-exclusion/platform-exclusion.md#build-configuration-for-platforms). - -## JavaScript isolation in JavaScript modules - -Blazor enables JavaScript isolation in standard [JavaScript modules](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules). JavaScript isolation provides the following benefits: - -* Imported JavaScript no longer pollutes the global namespace. -* Consumers of the library and components aren't required to manually import the related JavaScript. - -For more information, see . - -## Build, pack, and ship to NuGet - -Because Razor class libraries that contain Razor components are standard .NET libraries, packing and shipping them to NuGet is no different from packing and shipping any library to NuGet. Packing is performed using the [`dotnet pack`](/dotnet/core/tools/dotnet-pack) command in a command shell: - -```dotnetcli -dotnet pack -``` - -Upload the package to NuGet using the [`dotnet nuget push`](/dotnet/core/tools/dotnet-nuget-push) command in a command shell. - -## Trademarks - -*Jeep* and *Jeep YJ* are registered trademarks of [FCA US LLC (Stellantis NV)](https://www.stellantis.com). - -## Additional resources - +:::moniker range=">= aspnetcore-5.0" + * * [Add an XML Intermediate Language (IL) Trimmer configuration file to a library](xref:blazor/host-and-deploy/configure-trimmer) * [CSS isolation support with Razor class libraries](xref:blazor/components/css-isolation#razor-class-library-rcl-support) @@ -669,231 +503,6 @@ Upload the package to NuGet using the [`dotnet nuget push`](/dotnet/core/tools/d :::moniker range="< aspnetcore-5.0" -Components can be shared in a [Razor class library (RCL)](xref:razor-pages/ui-class) across projects. Include components and static assets in an app from: - -* Another project in the [solution](xref:blazor/tooling#visual-studio-solution-file-sln). -* A referenced .NET library. -* A NuGet package. - -Just as components are regular .NET types, components provided by an RCL are normal .NET assemblies. - -## Create an RCL - -# [Visual Studio](#tab/visual-studio) - -1. Create a new project. -1. In the **Create a new project** dialog, select **Razor Class Library** from the list of ASP.NET Core project templates. Select **Next**. -1. In the **Configure your new project** dialog, provide a project name in the **Project name** field or accept the default project name. Examples in this topic use the project name `ComponentLibrary`. Select **Create**. -1. In the **Create a new Razor class library** dialog, select **Create**. -1. Add the RCL to a solution: - 1. Open the solution. - 1. Right-click the solution in **Solution Explorer**. Select **Add** > **Existing Project**. - 1. Navigate to the RCL's project file. - 1. Select the RCL's project file (`.csproj`). -1. Add a reference to the RCL from the app: - 1. Right-click the app project. Select **Add** > **Project Reference**. - 1. Select the RCL project. Select **OK**. - -If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: - -```razor -@using Microsoft.AspNetCore.Components.Web -``` - -# [Visual Studio for Mac](#tab/visual-studio-mac) - -1. Create a new project. -1. In the sidebar under **Web and Console**, select **App**. Under **ASP.NET Core**, select **Razor Class Library** from the project templates shown. Select **Next**. -1. Select the target framework for the library with the **Target Framework** dropdown list. Select **Next**. -1. In the **Configure your new Class Library** dialog, provide a project name in the **Project Name** field. Examples in this topic use the project name `ComponentLibrary`. Select **Create**. -1. Add the RCL to a solution: - 1. Open the solution. - 1. Right-click the solution in **Solution Explorer**. Select **Add** > **Existing Project**. - 1. Navigate to the RCL's project file. - 1. Select the RCL's project file (`.csproj`). -1. Add a reference to the RCL from the app: - 1. Right-click the app project. Select **Add** > **Reference**. - 1. Select the RCL project. Select **OK**. - -If the **Support pages and views** checkbox is selected to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: - -```razor -@using Microsoft.AspNetCore.Components.Web -``` - -# [Visual Studio Code / .NET Core CLI](#tab/visual-studio-code+netcore-cli) - -1. Use the **Razor Class Library** project template (`razorclasslib`) with the [`dotnet new`](/dotnet/core/tools/dotnet-new) command in a command shell. In the following example, an RCL is created and named `ComponentLibrary` using the `-o|--output` option. The folder that holds `ComponentLibrary` is created automatically when the command is executed: - - ```dotnetcli - dotnet new razorclasslib -o ComponentLibrary - ``` - -1. To add the library to an existing project, use the [`dotnet add reference`](/dotnet/core/tools/dotnet-add-reference) command in a command shell. In the following command, the `{PATH TO LIBRARY}` placeholder is the path to the library's project folder: - - ```dotnetcli - dotnet add reference {PATH TO LIBRARY} - ``` - -If the `-s|--support-pages-and-views` option is used to support pages and views when generating the RCL from the template, add an `_Imports.razor` file to root of the generated RCL project with the following contents to enable Razor component authoring: - -```razor -@using Microsoft.AspNetCore.Components.Web -``` - ---- - -## Consume a Razor component from an RCL - -To consume components from an RCL in another project, use either of the following approaches: - -* Use the full component type name, which includes the RCL's namespace. -* Individual components can be added by name without the RCL's namespace if Razor's [`@using`](xref:mvc/views/razor#using) directive declares the RCL's namespace. Use the following approaches: - * Add the `@using` directive to individual components. - * include the `@using` directive in the top-level `_Imports.razor` file to make the library's components available to an entire project. Add the directive to an `_Imports.razor` file at any level to apply the namespace to a single component or set of components within a folder. When an `_Imports.razor` file is used, individual components don't require an `@using` directive for the RCL's namespace. - -In the following examples, `ComponentLibrary` is an RCL containing the `Component1` component. The `Component1` component is an example component automatically added to an RCL created from the RCL project template that isn't created to support pages and views. - -> [!NOTE] -> If the RCL is created to support pages and views, manually add the `Component1` component and its static assets to the RCL if you plan to follow the examples in this article. The component and static assets are shown in this section. - -`Component1.razor` in the `ComponentLibrary` RCL: - -```razor -
    - This component is defined in the ComponentLibrary package. -
    -``` - -In the app that consumes the RCL, reference the `Component1` component using its namespace, as the following example shows. - -`Pages/ConsumeComponent1.razor`: - -```razor -@page "/consume-component-1" - -

    Consume component (full namespace example)

    - - -``` - -Alternatively, add a [`@using`](xref:mvc/views/razor#using) directive and use the component without its namespace. The following `@using` directive can also appear in any `_Imports.razor` file in or above the current folder. - -`Pages/ConsumeComponent2.razor`: - -```razor -@page "/consume-component-2" -@using ComponentLibrary - -

    Consume component (@@using example)

    - - -``` - -The following background image and stylesheet are used by the RCL's `Component1` example component. There's no need to add these static assets to a new RCL created from the RCL project template, as they're added automatically by the project template. - -`wwwroot/background.png` in the `ComponentLibrary` RCL: - -![Diagonally-striped background image added to the library by the RCL project template](~/blazor/components/class-libraries/_static/background.png) - -`wwwroot/styles.css` in the `ComponentLibrary` RCL: - -```css -.my-component { - border: 2px dashed red; - padding: 1em; - margin: 1em 0; - background-image: url('background.png'); -} -``` - -To provide `Component1`'s `my-component` CSS class, link to the library's stylesheet in the app's `` markup. - -`wwwroot/index.html` file (Blazor WebAssembly) or `Pages/_Host.cshtml` file (Blazor Server): - -```html - -``` - -## Create an RCL with static assets - -An RCL's static assets are available to any app that consumes the library. - -Place static assets in the `wwwroot` folder of the RCL and reference the static assets with the following path in the app: `_content/{PACKAGE ID}/{PATH AND FILE NAME}`. The `{PACKAGE ID}` placeholder is the library's [package ID](/nuget/create-packages/creating-a-package-msbuild#set-properties). The package ID defaults to the project's assembly name if `` isn't specified in the project file. The `{PATH AND FILE NAME}` placeholder is path and file name under `wwwroot`. - -The following example demonstrates the use of RCL static assets with an RCL named `ComponentLibrary` and a Blazor app that consumes the RCL. The app has a project reference for the `ComponentLibrary` RCL. - -The following Jeep® image is used in this section's example. If you implement the example shown in this section, right-click the image to save it locally. - -`wwwroot/jeep-yj.png` in the `ComponentLibrary` RCL: - -![Jeep YJ®](~/blazor/components/class-libraries/_static/jeep-yj.png) - -Add the following `JeepYJ` component to the RCL. - -`JeepYJ.razor` in the `ComponentLibrary` RCL: - -```razor -

    ComponentLibrary.JeepYJ

    - -

    - Jeep YJ® -

    -``` - -Add the following `Jeep` component to the app that consumes the `ComponentLibrary` RCL. The `Jeep` component uses: - -* The Jeep YJ® image from the `ComponentLibrary` RCL's `wwwroot` folder. -* The `JeepYJ` component from the RCL. - -`Pages/Jeep.razor`: - -```razor -@page "/jeep" -@using ComponentLibrary - -
    -

    Direct use

    - -

    - Jeep YJ® -

    -
    - - - -

    - Jeep and Jeep YJ are registered trademarks of - FCA US LLC (Stellantis NV). -

    -``` - -Rendered `Jeep` component: - -![Jeep component](~/blazor/components/class-libraries/_static/jeep-component.png) - -For more information, see . - -## Supply components and static assets to multiple hosted Blazor apps - -For more information, see . - -## Build, pack, and ship to NuGet - -Because Razor class libraries that contain Razor components are standard .NET libraries, packing and shipping them to NuGet is no different from packing and shipping any library to NuGet. Packing is performed using the [`dotnet pack`](/dotnet/core/tools/dotnet-pack) command in a command shell: - -```dotnetcli -dotnet pack -``` - -Upload the package to NuGet using the [`dotnet nuget push`](/dotnet/core/tools/dotnet-nuget-push) command in a command shell. - -## Trademarks - -*Jeep* and *Jeep YJ* are registered trademarks of [FCA US LLC (Stellantis NV)](https://www.stellantis.com). - -## Additional resources - * * [Add an XML Intermediate Language (IL) Linker configuration file to a library](xref:blazor/host-and-deploy/configure-linker#add-an-xml-linker-configuration-file-to-a-library) diff --git a/aspnetcore/blazor/components/control-head-content.md b/aspnetcore/blazor/components/control-head-content.md index 9e942ce63812..3b1480e573f6 100644 --- a/aspnetcore/blazor/components/control-head-content.md +++ b/aspnetcore/blazor/components/control-head-content.md @@ -5,11 +5,13 @@ description: Learn how to control head content in Blazor apps, including how to monikerRange: '>= aspnetcore-6.0' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/control-head-content --- # Control `` content in ASP.NET Core Blazor apps +[!INCLUDE[](~/includes/not-latest-version.md)] + Razor components can modify the HTML `` element content of a page, including setting the page's title (`` element) and modifying metadata (`<meta>` elements). ## Control `<head>` content in a Razor component @@ -22,8 +24,20 @@ The following example sets the page's title and description using Razor. `Pages/ControlHeadContent.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/control-head-content/ControlHeadContent.razor" highlight="13,15-17"::: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/control-head-content/ControlHeadContent.razor" highlight="13,15-17"::: +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + ## Control `<head>` content during prerendering *This section applies to prerendered Blazor WebAssembly apps and Blazor Server apps.* @@ -44,6 +58,8 @@ In a **required**, shared `_Layout.cshtml` file of a prerendered hosted Blazor W <component type="typeof(HeadOutlet)" render-mode="WebAssemblyPrerendered" /> ``` +:::moniker-end + ## `HeadOutlet` component The <xref:Microsoft.AspNetCore.Components.Web.HeadOutlet> component renders content provided by <xref:Microsoft.AspNetCore.Components.Web.PageTitle> and <xref:Microsoft.AspNetCore.Components.Web.HeadContent> components. @@ -56,8 +72,18 @@ builder.RootComponents.Add<HeadOutlet>("head::after"); When the [`::after` pseudo-selector](https://developer.mozilla.org/docs/Web/CSS/::after) is specified, the contents of the root component are appended to the existing head contents instead of replacing the content. This allows the app to retain static head content in `wwwroot/index.html` without having to repeat the content in the app's Razor components. +:::moniker range=">= aspnetcore-7.0" + +In Blazor Server apps created from the Blazor Server project template, a [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) renders `<head>` content for the <xref:Microsoft.AspNetCore.Components.Web.HeadOutlet> component in `Pages/_Host.cshtml`: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + In Blazor Server apps created from the Blazor Server project template, a [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) renders `<head>` content for the <xref:Microsoft.AspNetCore.Components.Web.HeadOutlet> component in `Pages/_Layout.cshtml`: +:::moniker-end + ```cshtml <head> ... @@ -78,6 +104,7 @@ In Blazor apps created from Blazor project templates, the `NotFound` component t ## Additional resources * [Control headers in C# code at startup](xref:blazor/fundamentals/startup#control-headers-in-c-code) +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) Mozilla MDN Web Docs documentation: diff --git a/aspnetcore/blazor/components/css-isolation.md b/aspnetcore/blazor/components/css-isolation.md index 341e8139b455..72cb8bb04a9b 100644 --- a/aspnetcore/blazor/components/css-isolation.md +++ b/aspnetcore/blazor/components/css-isolation.md @@ -5,17 +5,17 @@ description: Learn how CSS isolation scopes CSS to Razor components, which can s monikerRange: '>= aspnetcore-5.0' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/css-isolation --- # ASP.NET Core Blazor CSS isolation +[!INCLUDE[](~/includes/not-latest-version.md)] + By [Dave Brock](https://twitter.com/daveabrock) This article explains how CSS isolation scopes CSS to Razor components, which can simplify CSS and avoid collisions with other components or libraries. -:::moniker range=">= aspnetcore-6.0" - Isolate CSS styles to individual pages, views, and components to reduce or avoid: * Dependencies on global styles that can be challenging to maintain. @@ -51,13 +51,13 @@ h1 { ## CSS isolation bundling -CSS isolation occurs at build time. Blazor rewrites CSS selectors to match markup rendered by the component. The rewritten CSS styles are bundled and produced as a static asset. The stylesheet is referenced inside the `<head>` tag of `wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Layout.cshtml` (Blazor Server). The following `<link>` element is added by default to an app created from the Blazor project templates, where the placeholder `{ASSEMBLY NAME}` is the project's assembly name: +CSS isolation occurs at build time. Blazor rewrites CSS selectors to match markup rendered by the component. The rewritten CSS styles are bundled and produced as a static asset. The stylesheet is referenced inside the `<head>` tag ([location of `<head>` content](xref:blazor/project-structure#location-of-head-content)). The following `<link>` element is added by default to an app created from the Blazor project templates, where the placeholder `{ASSEMBLY NAME}` is the project's assembly name: ```html <link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet"> ``` -The following example is from a hosted Blazor WebAssembly **`Client`** app. The app's assembly name is `BlazorSample.Client`, and the `<link>` is added by the Blazor WebAssembly project template when the project is created with the Hosted option (`-ho|--hosted` option using the .NET CLI or **ASP.NET Core hosted** checkbox using Visual Studio): +The following example is from a hosted Blazor WebAssembly **:::no-loc text="Client":::** app. The app's assembly name is `BlazorSample.Client`, and the `<link>` is added by the Blazor WebAssembly project template when the project is created with the Hosted option (`-ho|--hosted` option using the .NET CLI or **ASP.NET Core Hosted** checkbox using Visual Studio): ```html <link href="BlazorSample.Client.styles.css" rel="stylesheet"> @@ -153,7 +153,7 @@ The ability to attach the `::deep` pseudo-element to any HTML element allows you CSS preprocessors are useful for improving CSS development by utilizing features such as variables, nesting, modules, mixins, and inheritance. While CSS isolation doesn't natively support CSS preprocessors such as Sass or Less, integrating CSS preprocessors is seamless as long as preprocessor compilation occurs before Blazor rewrites the CSS selectors during the build process. Using Visual Studio for example, configure existing preprocessor compilation as a **Before Build** task in the Visual Studio Task Runner Explorer. -Many third-party NuGet packages, such as [`Delegate.SassBuilder`](https://www.nuget.org/packages/Delegate.SassBuilder), can compile SASS/SCSS files at the beginning of the build process before CSS isolation occurs, and no additional configuration is required. +Many third-party NuGet packages, such as [`AspNetCore.SassCompiler`](https://www.nuget.org/packages/AspNetCore.SassCompiler#readme-body-tab), can compile SASS/SCSS files at the beginning of the build process before CSS isolation occurs. ## CSS isolation configuration @@ -161,7 +161,7 @@ CSS isolation is designed to work out-of-the-box but provides configuration for ### Customize scope identifier format -By default, scope identifiers use the format `b-{STRING}`, where the `{STRING}` placeholder is a ten-character string generated by the framework.. To customize the scope identifier format, update the project file to a desired pattern: +By default, scope identifiers use the format `b-{STRING}`, where the `{STRING}` placeholder is a ten-character string generated by the framework. To customize the scope identifier format, update the project file to a desired pattern: ```xml <ItemGroup> @@ -233,227 +233,11 @@ For more information on RCLs, see the following articles: * <xref:blazor/components/class-libraries> * <xref:razor-pages/ui-class> +:::moniker range=">= aspnetcore-6.0" + ## Additional resources * [Razor Pages CSS isolation](xref:razor-pages/index#css-isolation) * [MVC CSS isolation](xref:mvc/views/overview#css-isolation) :::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -CSS isolation simplifies an app's CSS footprint by preventing dependencies on global styles and helps to avoid styling conflicts among components and libraries. - -## Enable CSS isolation - -To define component-specific styles, create a `.razor.css` file matching the name of the `.razor` file for the component in the same folder. The `.razor.css` file is a *scoped CSS file*. - -For an `Example` component in an `Example.razor` file, create a file alongside the component named `Example.razor.css`. The `Example.razor.css` file must reside in the same folder as the `Example` component (`Example.razor`). The "`Example`" base name of the file is **not** case-sensitive. - -`Pages/Example.razor`: - -```razor -@page "/example" - -<h1>Scoped CSS Example</h1> -``` - -`Pages/Example.razor.css`: - -```css -h1 { - color: brown; - font-family: Tahoma, Geneva, Verdana, sans-serif; -} -``` - -**The styles defined in `Example.razor.css` are only applied to the rendered output of the `Example` component.** CSS isolation is applied to HTML elements in the matching Razor file. Any `h1` CSS declarations defined elsewhere in the app don't conflict with the `Example` component's styles. - -> [!NOTE] -> In order to guarantee style isolation when bundling occurs, importing CSS in Razor code blocks isn't supported. - -## CSS isolation bundling - -CSS isolation occurs at build time. Blazor rewrites CSS selectors to match markup rendered by the component. The rewritten CSS styles are bundled and produced as a static asset. The stylesheet is referenced inside the `<head>` tag of `wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Host.cshtml` (Blazor Server). The following `<link>` element is added by default to an app created from the Blazor project templates, where the placeholder `{ASSEMBLY NAME}` is the project's assembly name: - -```html -<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet"> -``` - -The following example is from a hosted Blazor WebAssembly **`Client`** app. The app's assembly name is `BlazorSample.Client`, and the `<link>` is added by the Blazor WebAssembly project template when the project is created with the Hosted option (`-ho|--hosted` option using the .NET CLI or **ASP.NET Core hosted** checkbox using Visual Studio): - -```html -<link href="BlazorSample.Client.styles.css" rel="stylesheet"> -``` - -Within the bundled file, each component is associated with a scope identifier. For each styled component, an HTML attribute is appended with the format `b-<10-character-string>`. The identifier is unique and different for each app. In the rendered `Counter` component, Blazor appends a scope identifier to the `h1` element: - -```html -<h1 b-3xxtam6d07> -``` - -The `{ASSEMBLY NAME}.styles.css` file uses the scope identifier to group a style declaration with its component. The following example provides the style for the preceding `<h1>` element: - -```css -/* /Pages/Counter.razor.rz.scp.css */ -h1[b-3xxtam6d07] { - color: brown; -} -``` - -At build time, a project bundle is created with the convention `obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY NAME}.bundle.scp.css`, where the placeholders are: - -* `{CONFIGURATION}`: The app's build configuration (for example, `Debug`, `Release`). -* `{TARGET FRAMEWORK}`: The target framework (for example, `net6.0`). -* `{ASSEMBLY NAME}`: The app's assembly name (for example, `BlazorSample`). - -## Child component support - -By default, CSS isolation only applies to the component you associate with the format `{COMPONENT NAME}.razor.css`, where the placeholder `{COMPONENT NAME}` is usually the component name. To apply changes to a child component, use the `::deep` [pseudo-element](https://developer.mozilla.org/docs/Web/CSS/Pseudo-elements) to any descendant elements in the parent component's `.razor.css` file. The `::deep` pseudo-element selects elements that are *descendants* of an element's generated scope identifier. - -The following example shows a parent component called `Parent` with a child component called `Child`. - -`Pages/Parent.razor`: - -```razor -@page "/parent" - -<div> - <h1>Parent component</h1> - - <Child /> -</div> -``` - -`Shared/Child.razor`: - -```razor -<h1>Child Component</h1> -``` - -Update the `h1` declaration in `Parent.razor.css` with the `::deep` pseudo-element to signify the `h1` style declaration must apply to the parent component and its children. - -`Pages/Parent.razor.css`: - -```css -::deep h1 { - color: red; -} -``` - -The `h1` style now applies to the `Parent` and `Child` components without the need to create a separate scoped CSS file for the child component. - -The `::deep` pseudo-element only works with descendant elements. The following markup applies the `h1` styles to components as expected. The parent component's scope identifier is applied to the `div` element, so the browser knows to inherit styles from the parent component. - -`Pages/Parent.razor`: - -```razor -<div> - <h1>Parent</h1> - - <Child /> -</div> -``` - -However, excluding the `div` element removes the descendant relationship. In the following example, the style is **not** applied to the child component. - -`Pages/Parent.razor`: - -```razor -<h1>Parent</h1> - -<Child /> -``` - -The `::deep` pseudo-element affects where the scope attribute is applied to the rule. When you define a CSS rule in a scoped CSS file, the scope is applied to the right most element by default. For example: `div > a` is transformed to `div > a[b-{STRING}]`, where the `{STRING}` placeholder is a ten-character string generated by the framework (for example, `b-3xxtam6d07`). If you instead want the rule to apply to a different selector, the `::deep` pseudo-element allows you do so. For example, `div ::deep > a` is transformed to `div[b-{STRING}] > a` (for example, `div[b-3xxtam6d07] > a`). - -The ability to attach the `::deep` pseudo-element to any HTML element allows you to create scoped CSS styles that affect elements rendered by other components when you can determine the structure of the rendered HTML tags. For a component that renders an hyperlink tag (`<a>`) inside another component, ensure the component is wrapped in a `div` (or any other element) and use the rule `::deep > a` to create a style that's only applied to that component when the parent component renders. - -> [!IMPORTANT] -> Scoped CSS only applies to ***HTML elements*** and not to Razor components or Tag Helpers, including elements with a Tag Helper applied, such as `<input asp-for="..." />`. - -## CSS preprocessor support - -CSS preprocessors are useful for improving CSS development by utilizing features such as variables, nesting, modules, mixins, and inheritance. While CSS isolation doesn't natively support CSS preprocessors such as Sass or Less, integrating CSS preprocessors is seamless as long as preprocessor compilation occurs before Blazor rewrites the CSS selectors during the build process. Using Visual Studio for example, configure existing preprocessor compilation as a **Before Build** task in the Visual Studio Task Runner Explorer. - -Many third-party NuGet packages, such as [Delegate.SassBuilder](https://www.nuget.org/packages/Delegate.SassBuilder), can compile SASS/SCSS files at the beginning of the build process before CSS isolation occurs, and no additional configuration is required. - -## CSS isolation configuration - -CSS isolation is designed to work out-of-the-box but provides configuration for some advanced scenarios, such as when there are dependencies on existing tools or workflows. - -### Customize scope identifier format - -By default, scope identifiers use the format `b-<10-character-string>`. To customize the scope identifier format, update the project file to a desired pattern: - -```xml -<ItemGroup> - <None Update="Pages/Example.razor.css" CssScope="my-custom-scope-identifier" /> -</ItemGroup> -``` - -In the preceding example, the CSS generated for `Example.razor.css` changes its scope identifier from `b-<10-character-string>` to `my-custom-scope-identifier`. - -Use scope identifiers to achieve inheritance with scoped CSS files. In the following project file example, a `BaseComponent.razor.css` file contains common styles across components. A `DerivedComponent.razor.css` file inherits these styles. - -```xml -<ItemGroup> - <None Update="Pages/BaseComponent.razor.css" CssScope="my-custom-scope-identifier" /> - <None Update="Pages/DerivedComponent.razor.css" CssScope="my-custom-scope-identifier" /> -</ItemGroup> -``` - -Use the wildcard (`*`) operator to share scope identifiers across multiple files: - -```xml -<ItemGroup> - <None Update="Pages/*.razor.css" CssScope="my-custom-scope-identifier" /> -</ItemGroup> -``` - -### Change base path for static web assets - -The `scoped.styles.css` file is generated at the root of the app. In the project file, use the [`<StaticWebAssetBasePath>` property](xref:blazor/fundamentals/static-files#static-web-asset-base-path) to change the default path. The following example places the `scoped.styles.css` file, and the rest of the app's assets, at the `_content` path: - -```xml -<PropertyGroup> - <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath> -</PropertyGroup> -``` - -### Disable automatic bundling - -To opt out of how Blazor publishes and loads scoped files at runtime, use the `DisableScopedCssBundling` property. When using this property, it means other tools or processes are responsible for taking the isolated CSS files from the `obj` directory and publishing and loading them at runtime: - -```xml -<PropertyGroup> - <DisableScopedCssBundling>true</DisableScopedCssBundling> -</PropertyGroup> -``` - -### Disable CSS isolation - -Disable CSS isolation for a project by setting the `<ScopedCssEnabled>` property to `false` in the app's project file: - -```xml -<ScopedCssEnabled>false</ScopedCssEnabled> -``` - -## Razor class library (RCL) support - -Isolated styles for components in a NuGet package or [Razor class library (RCL)](xref:razor-pages/ui-class) are automatically bundled: - -* The app uses CSS imports to reference the RCL's bundled styles. For a class library named `ClassLib` and a Blazor app with a `BlazorSample.styles.css` stylesheet, the RCL's stylesheet is imported at the top of the app's stylesheet: - - ```css - @import '_content/ClassLib/ClassLib.bundle.scp.css'; - ``` - -* The RCL's bundled styles aren't published as a static web asset of the app that consumes the styles. - -For more information on RCLs, see the following articles: - -* <xref:blazor/components/class-libraries> -* <xref:razor-pages/ui-class> - -:::moniker-end diff --git a/aspnetcore/blazor/components/data-binding.md b/aspnetcore/blazor/components/data-binding.md index 5ba53a4d4d27..93b923771428 100644 --- a/aspnetcore/blazor/components/data-binding.md +++ b/aspnetcore/blazor/components/data-binding.md @@ -5,14 +5,14 @@ description: Learn about data binding features for Razor components and Document monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/data-binding --- # ASP.NET Core Blazor data binding -This article explains data binding features for Razor components and Document Object Model (DOM) elements in Blazor apps. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains data binding features for Razor components and Document Object Model (DOM) elements in Blazor apps. Razor components provide data binding features with the [`@bind`](xref:mvc/views/razor#bind) Razor directive attribute with a field, property, or Razor expression value. @@ -25,285 +25,385 @@ When an `<input>` element loses focus, its bound field or property is updated. `Pages/Bind.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor" highlight="4,8"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor" highlight="4,8"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor" highlight="4,8"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor" highlight="4,8"::: + +:::moniker-end + The text box is updated in the UI only when the component is rendered, not in response to changing the field's or property's value. Since components render themselves after event handler code executes, field and property updates are usually reflected in the UI immediately after an event handler is triggered. -As a demonstration of how data binding composes in HTML, the following example binds the `InputValue` property to the second `<input>` element's `value` and [`onchange`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange) attributes. *The second `<input>` element in the following example is a concept demonstration and isn't meant to suggest how you should bind data in Razor components.* +As a demonstration of how data binding composes in HTML, the following example binds the `InputValue` property to the second `<input>` element's `value` and `onchange` attributes ([`change`](https://developer.mozilla.org/docs/Web/API/HTMLElement/change_event)). *The second `<input>` element in the following example is a concept demonstration and isn't meant to suggest how you should bind data in Razor components.* `Pages/BindTheory.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor" highlight="12-14"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor" highlight="12-14"::: -When the `BindTheory` component is rendered, the `value` of the HTML demonstration `<input>` element comes from the `InputValue` property. When the user enters a value in the text box and changes element focus, the `onchange` event is fired and the `InputValue` property is set to the changed value. In reality, code execution is more complex because [`@bind`](xref:mvc/views/razor#bind) handles cases where type conversions are performed. In general, [`@bind`](xref:mvc/views/razor#bind) associates the current value of an expression with a `value` attribute and handles changes using the registered handler. +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor" highlight="12-14"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor" highlight="12-14"::: + +:::moniker-end + +When the `BindTheory` component is rendered, the `value` of the HTML demonstration `<input>` element comes from the `InputValue` property. When the user enters a value in the text box and changes element focus, the `onchange` event is fired and the `InputValue` property is set to the changed value. In reality, code execution is more complex because [`@bind`](xref:mvc/views/razor#bind) handles cases where type conversions are performed. In general, [`@bind`](xref:mvc/views/razor#bind) associates the current value of an expression with the `value` attribute of the `<input>` and handles changes using the registered handler. -Bind a property or field on other [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events by including an `@bind:event="{EVENT}"` attribute with a DOM event for the `{EVENT}` placeholder. The following example binds the `InputValue` property to the `<input>` element's value when the element's [`oninput` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) is triggered. Unlike the [`onchange` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange), which fires when the element loses focus, [`oninput`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) fires when the value of the text box changes. +Bind a property or field on other [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events by including an `@bind:event="{EVENT}"` attribute with a DOM event for the `{EVENT}` placeholder. The following example binds the `InputValue` property to the `<input>` element's value when the element's `oninput` event ([`input`](https://developer.mozilla.org/docs/Web/API/HTMLElement/input_event)) is triggered. Unlike the `onchange` event ([`change`](https://developer.mozilla.org/docs/Web/API/HTMLElement/change_event)), which fires when the element loses focus, `oninput` ([`input`](https://developer.mozilla.org/docs/Web/API/HTMLElement/input_event)) fires when the value of the text box changes. `Page/BindEvent.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor" highlight="4"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor" highlight="4"::: -Razor attribute binding is case sensitive: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor" highlight="4"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor" highlight="4"::: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + +Razor attribute binding is case-sensitive: * `@bind` and `@bind:event` are valid. * `@Bind`/`@Bind:Event` (capital letters `B` and `E`) or `@BIND`/`@BIND:EVENT` (all capital letters) **are invalid**. -## Multiple option selection with `<input>` elements +:::moniker-end -Binding supports [`multiple`](https://developer.mozilla.org/docs/Web/HTML/Attributes/multiple) option selection with `<input>` elements. The [`@onchange`](xref:mvc/views/razor#onevent) event provides an array of the selected elements via [event arguments (`ChangeEventArgs`)](xref:blazor/components/event-handling#event-arguments). The value must be bound to an array type. +:::moniker range=">= aspnetcore-7.0" -`Pages/BindMultipleInput.razor`: +To execute asynchronous logic after binding, use `@bind:after="{EVENT}"` with a DOM event for the `{EVENT}` placeholder. An assigned C# method isn't executed until the bound value is assigned synchronously. -```razor -@page "/bind-multiple-input" - -<h1>Bind Multiple <code>input</code>Example</h1> +Using an event callback parameter (`[Parameter] public EventCallback<string> ValueChanged { get; set; }`) with `@bind:after` isn't supported. Instead, pass a method that returns an <xref:System.Action> or <xref:System.Threading.Tasks.Task> to `@bind:after`. -<p> - <label> - Select one or more cars: - <select @onchange="SelectedCarsChanged" multiple> - <option value="audi">Audi</option> - <option value="jeep">Jeep</option> - <option value="opel">Opel</option> - <option value="saab">Saab</option> - <option value="volvo">Volvo</option> - </select> - </label> -</p> +In the following example: -<p> - Selected Cars: @string.Join(", ", SelectedCars) -</p> +* The `<input>` element's `value` is bound to the value of `searchText` synchronously. +* After each keystroke (`onchange` event) in the field, the `PerformSearch` method executes asynchronously. +* `PerformSearch` calls a service with an asynchronous method (`FetchAsync`) to return search results. -<p> - <label> - Select one or more cities: - <select @bind="SelectedCities" multiple> - <option value="bal">Baltimore</option> - <option value="la">Los Angeles</option> - <option value="pdx">Portland</option> - <option value="sf">San Francisco</option> - <option value="sea">Seattle</option> - </select> - </label> -</p> +```razor +@inject ISearchService SearchService -<span> - Selected Cities: @string.Join(", ", SelectedCities) -</span> +<input @bind="searchText" @bind:after="PerformSearch" /> @code { - public string[] SelectedCars { get; set; } = new string[] { }; - public string[] SelectedCities { get; set; } = new[] { "bal", "sea" }; + private string? searchText; + private string[]? searchResult; - void SelectedCarsChanged(ChangeEventArgs e) + private async Task PerformSearch() { - if (e.Value is not null) - { - SelectedCars = (string[])e.Value; - } + searchResult = await SearchService.FetchAsync(searchText); } } ``` -For information on how empty strings and `null` values are handled in data binding, see the [Binding `<select>` element options to C# object `null` values](#binding-select-element-options-to-c-object-null-values) section. - -## Binding `<select>` element options to C# object `null` values - -There's no sensible way to represent a `<select>` element option value as a C# object `null` value, because: +Additional examples -* HTML attributes can't have `null` values. The closest equivalent to `null` in HTML is absence of the HTML `value` attribute from the `<option>` element. -* When selecting an `<option>` with no `value` attribute, the browser treats the value as the *text content* of that `<option>`'s element. +`Pages/BindAfter.razor`: -The Blazor framework doesn't attempt to suppress the default behavior because it would involve: +```razor +@page "/bind-after" +@using Microsoft.AspNetCore.Components.Forms -* Creating a chain of special-case workarounds in the framework. -* Breaking changes to current framework behavior. +<h1>Bind After Examples</h1> -The most plausible `null` equivalent in HTML is an *empty string* `value`. The Blazor framework handles `null` to empty string conversions for two-way binding to a `<select>`'s value. +<h2>Elements</h2> -## Unparsable values +<input type="text" @bind="text" @bind:after="() => { }" /> -When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered. +<input type="text" @bind="text" @bind:after="After" /> -Consider the following component, where an `<input>` element is bound to an `int` type with an initial value of `123`. +<input type="text" @bind="text" @bind:after="AfterAsync" /> -`Pages/UnparsableValues.razor`: +<h2>Components</h2> -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor" highlight="4,12"::: +<InputText @bind-Value="text" @bind-Value:after="() => { }" /> -By default, binding applies to the element's `onchange` event. If the user updates the value of the text box's entry to `123.45` and changes the focus, the element's value is reverted to `123` when `onchange` fires. When the value `123.45` is rejected in favor of the original value of `123`, the user understands that their value wasn't accepted. +<InputText @bind-Value="text" @bind-Value:after="After" /> -For the `oninput` event (`@bind:event="oninput"`), a value reversion occurs after any keystroke that introduces an unparsable value. When targeting the `oninput` event with an `int`-bound type, a user is prevented from typing a dot (`.`) character. A dot (`.`) character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. There are scenarios where reverting the value on the `oninput` event isn't ideal, such as when the user should be allowed to clear an unparsable `<input>` value. Alternatives include: +<InputText @bind-Value="text" @bind-Value:after="AfterAsync" /> -* Don't use the `oninput` event. Use the default `onchange` event, where an invalid value isn't reverted until the element loses focus. -* Bind to a nullable type, such as `int?` or `string` and provide [custom `get` and `set` accessor logic](#custom-binding-formats) to handle invalid entries. -* Use a [form validation component](xref:blazor/forms-validation), such as <xref:Microsoft.AspNetCore.Components.Forms.InputNumber%601> or <xref:Microsoft.AspNetCore.Components.Forms.InputDate%601>. Form validation components provide built-in support to manage invalid inputs. Form validation components: - * Permit the user to provide invalid input and receive validation errors on the associated <xref:Microsoft.AspNetCore.Components.Forms.EditContext>. - * Display validation errors in the UI without interfering with the user entering additional webform data. +@code { + private string text = ""; -## Format strings + private void After() {} + private Task AfterAsync() { return Task.CompletedTask; } +} +``` -Data binding works with a single <xref:System.DateTime> format string using `@bind:format="{FORMAT STRING}"`, where the `{FORMAT STRING}` placeholder is the format string. Other format expressions, such as currency or number formats, aren't available at this time but might be added in a future release. +For more information on the `InputText` component, see <xref:blazor/forms-and-input-components>. -`Pages/DateBinding.razor`: +Components support two-way data binding by defining a pair of parameters: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor" highlight="6"::: +* `@bind:get`: Specifies the value to bind. +* `@bind:set`: Specifies a callback for when the value changes. -In the preceding code, the `<input>` element's field type (`type` attribute) defaults to `text`. +The `@bind:get` and `@bind:set` modifiers are always used together. -Nullable <xref:System.DateTime?displayProperty=fullName> and <xref:System.DateTimeOffset?displayProperty=fullName> are supported: +Using an event callback parameter with `@bind:set` (`[Parameter] public EventCallback<string> ValueChanged { get; set; }`) isn't supported. Instead, pass a method that returns an <xref:System.Action> or <xref:System.Threading.Tasks.Task> to `@bind:set`. -```csharp -private DateTime? date; -private DateTimeOffset? dateOffset; -``` +Examples -Specifying a format for the `date` field type isn't recommended because Blazor has built-in support to format dates. In spite of the recommendation, only use the `yyyy-MM-dd` date format for binding to function correctly if a format is supplied with the `date` field type: +`Pages/BindGetSet.razor`: ```razor -<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd"> -``` +@page "/bind-get-set" +@using Microsoft.AspNetCore.Components.Forms -## Custom binding formats +<h1>Bind Get Set Examples</h1> -[C# `get` and `set` accessors](/dotnet/csharp/programming-guide/classes-and-structs/using-properties) can be used to create custom binding format behavior, as the following `DecimalBinding` component demonstrates. The component binds a positive or negative decimal with up to three decimal places to an `<input>` element by way of a `string` property (`DecimalValue`). +<h2>Elements</h2> -`Pages/DecimalBinding.razor`: +<input type="text" @bind:get="text" @bind:set="(value) => { text = value; }" /> +<input type="text" @bind:get="text" @bind:set="Set" /> +<input type="text" @bind:get="text" @bind:set="SetAsync" /> -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor" highlight="7,21-31"::: +<h2>Components</h2> -## Binding with component parameters +<InputText @bind-Value:get="text" @bind-Value:set="(value) => { text = value; }" /> +<InputText @bind-Value:get="text" @bind-Value:set="Set" /> +<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" /> -A common scenario is binding a property of a child component to a property in its parent component. This scenario is called a *chained bind* because multiple levels of binding occur simultaneously. +@code { + private string text = ""; -[Component parameters](xref:blazor/components/index#component-parameters) permit binding properties of a parent component with `@bind-{PROPERTY}` syntax, where the `{PROPERTY}` placeholder is the property to bind. + private void Set(string value) + { + text = value; + } -You can't implement chained binds with [`@bind`](xref:mvc/views/razor#bind) syntax in the child component. An event handler and value must be specified separately to support updating the property in the parent from the child component. + private Task SetAsync(string value) + { + text = value; + return Task.CompletedTask; + } +} +``` -The parent component still leverages the [`@bind`](xref:mvc/views/razor#bind) syntax to set up the databinding with the child component. +For more information on the `InputText` component, see <xref:blazor/forms-and-input-components>. -The following `ChildBind` component has a `Year` component parameter and an <xref:Microsoft.AspNetCore.Components.EventCallback%601>. By convention, the <xref:Microsoft.AspNetCore.Components.EventCallback%601> for the parameter must be named as the component parameter name with a "`Changed`" suffix. The naming syntax is `{PARAMETER NAME}Changed`, where the `{PARAMETER NAME}` placeholder is the parameter name. In the following example, the <xref:Microsoft.AspNetCore.Components.EventCallback%601> is named `YearChanged`. +For another example use of `@bind:get` and `@bind:set`, see the [Bind across more than two components](#bind-across-more-than-two-components) section later in this article. -<xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A?displayProperty=nameWithType> invokes the delegate associated with the binding with the provided argument and dispatches an event notification for the changed property. +Razor attribute binding is case-sensitive: -`Shared/ChildBind.razor`: +* `@bind`, `@bind:event`, and `@bind:after` are valid. +* `@Bind`/`@bind:Event`/`@bind:aftEr` (capital letters) or `@BIND`/`@BIND:EVENT`/`@BIND:AFTER` (all capital letters) **are invalid**. -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor" highlight="14-15,17-18,22"::: +## Use `@bind:get`/`@bind:set` modifiers and avoid event handlers for two-way data binding -For more information on events and <xref:Microsoft.AspNetCore.Components.EventCallback%601>, see the *EventCallback* section of the <xref:blazor/components/event-handling#eventcallback> article. +Two-way data binding isn't possible to implement with an event handler. Use `@bind:get`/`@bind:set` modifiers for two-way data binding. -In the following `Parent` component, the `year` field is bound to the `Year` parameter of the child component. The `Year` parameter is bindable because it has a companion `YearChanged` event that matches the type of the `Year` parameter. +<span aria-hidden="true">❌</span> Consider the following ***dysfunctional approach*** for two-way data binding using an event handler: -`Pages/Parent.razor`: +```razor +<p> + <input value="@inputValue" @oninput="OnInput" /> +</p> -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor" highlight="9"::: +<p> + <code>inputValue</code>: @inputValue +</p> -By convention, a property can be bound to a corresponding event handler by including an `@bind-{PROPERTY}:event` attribute assigned to the handler, where the `{PROPERTY}` placeholder is the property. `<ChildBind @bind-Year="year" />` is equivalent to writing: +@code { + private string? inputValue; -```razor -<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" /> + private void OnInput(ChangeEventArgs args) + { + var newValue = args.Value?.ToString() ?? string.Empty; + + inputValue = newValue.Length > 4 ? "Long!" : newValue; + } +} ``` -In a more sophisticated and real-world example, the following `PasswordEntry` component: +The `OnInput` event handler updates the value of `inputValue` to `Long!` after a fourth character is provided. However, the user can continue adding characters to the element value in the UI. The value of `inputValue` isn't bound back to the element's value with each keystroke. The preceding example is only capable of one-way data binding. -* Sets an `<input>` element's value to a `password` field. -* Exposes changes of a `Password` property to a parent component with an [`EventCallback`](xref:blazor/components/event-handling#eventcallback) that passes in the current value of the child's `password` field as its argument. -* Uses the `onclick` event to trigger the `ToggleShowPassword` method. For more information, see <xref:blazor/components/event-handling>. +The reason for this behavior is that Blazor isn't aware that your code intends to modify the value of `inputValue` in the event handler. Blazor doesn't try to force [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) element values and .NET variable values to match unless they're bound with `@bind` syntax. In earlier versions of Blazor, two-way data binding is implemented by [binding the element to a property and controlling the property's value with its setter](#binding-to-a-property-with-c-get-and-set-accessors). In ASP.NET Core 7.0 or later, `@bind:get`/`@bind:set` modifier syntax is used to implement two-way data binding, as the next example demonstrates. -`Shared/PasswordEntry.razor`: +<span aria-hidden="true">✔️</span> Consider the following ***correct approach*** using `@bind:get`/`@bind:set` for two-way data binding: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor" highlight="7-10,13,23-24,26-27,36-39"::: +```razor +<p> + <input @bind:event="oninput" @bind:get="inputValue" @bind:set="OnInput" /> +</p> -The `PasswordEntry` component is used in another component, such as the following `PasswordBinding` component example. +<p> + <code>inputValue</code>: @inputValue +</p> -`Pages/PasswordBinding.razor`: +@code { + private string? inputValue; -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor" highlight="5"::: + private void OnInput(string value) + { + var newValue = value ?? string.Empty; -When the `PasswordBinding` component is initially rendered, the `password` value of `Not set` is displayed in the UI. After initial rendering, the value of `password` reflects changes made to the `Password` component parameter value in the `PasswordEntry` component. + inputValue = newValue.Length > 4 ? "Long!" : newValue; + } +} +``` -> [!NOTE] -> The preceding example binds the password one-way from the child `PasswordEntry` component to the parent `PasswordBinding` component. Two-way binding isn't a requirement in this scenario if the goal is for the app to have a shared password entry component for reuse around the app that merely passes the password to the parent. For an approach that permits two-way binding without [writing directly to the child component's parameter](xref:blazor/components/index#overwritten-parameters), see the `NestedChild` component example in the [Bind across more than two components](#bind-across-more-than-two-components) section of this article. +Using `@bind:get`/`@bind:set` modifiers both controls the underlying value of `inputValue` via `@bind:set` and binds the value of `inputValue` to the element's value via `@bind:get`. The preceding example demonstrates the correct approach for implementing two-way data binding. -Perform checks or trap errors in the handler. The following revised `PasswordEntry` component provides immediate feedback to the user if a space is used in the password's value. +:::moniker-end -`Shared/PasswordEntry.razor`: +## Binding to a property with C# `get` and `set` accessors -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor" highlight="35-46"::: +[C# `get` and `set` accessors](/dotnet/csharp/programming-guide/classes-and-structs/using-properties) can be used to create custom binding format behavior, as the following `DecimalBinding` component demonstrates. The component binds a positive or negative decimal with up to three decimal places to an `<input>` element by way of a `string` property (`DecimalValue`). -## Bind across more than two components +`Pages/DecimalBinding.razor`: -You can bind parameters through any number of nested components, but you must respect the one-way flow of data: +:::moniker range=">= aspnetcore-7.0" -* Change notifications *flow up the hierarchy*. -* New parameter values *flow down the hierarchy*. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor" highlight="7,21-31"::: -A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated, as shown in the following example. +:::moniker-end -`Pages/Parent.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor"::: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor" highlight="7,21-31"::: -`Shared/NestedChild.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor"::: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -> [!WARNING] -> Generally, avoid creating components that write directly to their own component parameters. The preceding `NestedChild` component makes use of a `BoundValue` property instead of writing directly to its `ChildMessage` parameter. For more information, see <xref:blazor/components/index#overwritten-parameters>. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor" highlight="7,21-31"::: -`Shared/NestedGrandchild.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor"::: +:::moniker range="< aspnetcore-5.0" -For an alternative approach suited to sharing data in memory and across components that aren't necessarily nested, see <xref:blazor/state-management>. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor" highlight="7,21-31"::: -## Additional resources +:::moniker-end -* [Parameter change detection and additional guidance on Razor component rendering](xref:blazor/components/rendering) -* <xref:blazor/forms-validation> -* [Binding to radio buttons in a form](xref:blazor/forms-validation#radio-buttons) -* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms-validation#binding-inputselect-options-to-c-object-null-values) -* [ASP.NET Core Blazor event handling: `EventCallback` section](xref:blazor/components/event-handling#eventcallback) +:::moniker range=">= aspnetcore-7.0" + +> [!NOTE] +> Two-way binding to a property with `get`/`set` accessors requires discarding the <xref:System.Threading.Tasks.Task> returned by <xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A?displayProperty=nameWithType>. For two-way data binding, we recommend using `@bind:get`/`@bind:set` modifiers. For more information, see the `@bind:get`/`@bind:set` guidance in the earlier in this article. :::moniker-end -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" +:::moniker range="< aspnetcore-7.0" -Razor components provide data binding features with the [`@bind`](xref:mvc/views/razor#bind) Razor directive attribute with a field, property, or Razor expression value. +> [!NOTE] +> Two-way binding to a property with `get`/`set` accessors requires discarding the <xref:System.Threading.Tasks.Task> returned by <xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A?displayProperty=nameWithType>. For two-way data binding in ASP.NET Core 7.0 or later, we recommend using `@bind:get`/`@bind:set` modifiers, which are described in 7.0 or later versions of this article. -The following example binds: +:::moniker-end -* An `<input>` element value to the C# `inputValue` field. -* A second `<input>` element value to the C# `InputValue` property. +:::moniker range=">= aspnetcore-6.0" -When an `<input>` element loses focus, its bound field or property is updated. +## Multiple option selection with `<select>` elements -`Pages/Bind.razor`: +Binding supports [`multiple`](https://developer.mozilla.org/docs/Web/HTML/Attributes/multiple) option selection with `<select>` elements. The [`@onchange`](xref:mvc/views/razor#onevent) event provides an array of the selected elements via [event arguments (`ChangeEventArgs`)](xref:blazor/components/event-handling#event-arguments). The value must be bound to an array type. -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor?highlight=4,8)] +`Pages/BindMultipleInput.razor`: -The text box is updated in the UI only when the component is rendered, not in response to changing the field's or property's value. Since components render themselves after event handler code executes, field and property updates are usually reflected in the UI immediately after an event handler is triggered. +```razor +@page "/bind-multiple-input" -As a demonstration of how data binding composes in HTML, the following example binds the `InputValue` property to the second `<input>` element's `value` and [`onchange`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange) attributes. *The second `<input>` element in the following example is a concept demonstration and isn't meant to suggest how you should bind data in Razor components.* +<h1>Bind Multiple <code>input</code>Example</h1> -`Pages/BindTheory.razor`: +<p> + <label> + Select one or more cars: + <select @onchange="SelectedCarsChanged" multiple> + <option value="audi">Audi</option> + <option value="jeep">Jeep</option> + <option value="opel">Opel</option> + <option value="saab">Saab</option> + <option value="volvo">Volvo</option> + </select> + </label> +</p> -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor?highlight=12-14)] +<p> + Selected Cars: @string.Join(", ", SelectedCars) +</p> -When the `BindTheory` component is rendered, the `value` of the HTML demonstration `<input>` element comes from the `InputValue` property. When the user enters a value in the text box and changes element focus, the `onchange` event is fired and the `InputValue` property is set to the changed value. In reality, code execution is more complex because [`@bind`](xref:mvc/views/razor#bind) handles cases where type conversions are performed. In general, [`@bind`](xref:mvc/views/razor#bind) associates the current value of an expression with a `value` attribute and handles changes using the registered handler. +<p> + <label> + Select one or more cities: + <select @bind="SelectedCities" multiple> + <option value="bal">Baltimore</option> + <option value="la">Los Angeles</option> + <option value="pdx">Portland</option> + <option value="sf">San Francisco</option> + <option value="sea">Seattle</option> + </select> + </label> +</p> -Bind a property or field on other [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events by including an `@bind:event="{EVENT}"` attribute with a DOM event for the `{EVENT}` placeholder. The following example binds the `InputValue` property to the `<input>` element's value when the element's [`oninput` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) is triggered. Unlike the [`onchange` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange), which fires when the element loses focus, [`oninput`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) fires when the value of the text box changes. +<span> + Selected Cities: @string.Join(", ", SelectedCities) +</span> -`Page/BindEvent.razor`: +@code { + public string[] SelectedCars { get; set; } = new string[] { }; + public string[] SelectedCities { get; set; } = new[] { "bal", "sea" }; -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor?highlight=4)] + private void SelectedCarsChanged(ChangeEventArgs e) + { + if (e.Value is not null) + { + SelectedCars = (string[])e.Value; + } + } +} +``` -Razor attribute binding is case sensitive: +For information on how empty strings and `null` values are handled in data binding, see the [Binding `<select>` element options to C# object `null` values](#binding-select-element-options-to-c-object-null-values) section. -* `@bind` and `@bind:event` are valid. -* `@Bind`/`@Bind:Event` (capital letters `B` and `E`) or `@BIND`/`@BIND:EVENT` (all capital letters) **are invalid**. +:::moniker-end ## Binding `<select>` element options to C# object `null` values @@ -327,15 +427,37 @@ Consider the following component, where an `<input>` element is bound to an `int `Pages/UnparsableValues.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor?highlight=4,12)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor" highlight="4,12"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor" highlight="4,12"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor" highlight="4,12"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor" highlight="4,12"::: + +:::moniker-end By default, binding applies to the element's `onchange` event. If the user updates the value of the text box's entry to `123.45` and changes the focus, the element's value is reverted to `123` when `onchange` fires. When the value `123.45` is rejected in favor of the original value of `123`, the user understands that their value wasn't accepted. For the `oninput` event (`@bind:event="oninput"`), a value reversion occurs after any keystroke that introduces an unparsable value. When targeting the `oninput` event with an `int`-bound type, a user is prevented from typing a dot (`.`) character. A dot (`.`) character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. There are scenarios where reverting the value on the `oninput` event isn't ideal, such as when the user should be allowed to clear an unparsable `<input>` value. Alternatives include: * Don't use the `oninput` event. Use the default `onchange` event, where an invalid value isn't reverted until the element loses focus. -* Bind to a nullable type, such as `int?` or `string` and provide [custom `get` and `set` accessor logic](#custom-binding-formats) to handle invalid entries. -* Use a [form validation component](xref:blazor/forms-validation), such as <xref:Microsoft.AspNetCore.Components.Forms.InputNumber%601> or <xref:Microsoft.AspNetCore.Components.Forms.InputDate%601>. Form validation components provide built-in support to manage invalid inputs. Form validation components: +* Bind to a nullable type, such as `int?` or `string` and either use `@bind:get`/`@bind:set` modifiers (described earlier in this article) or [bind to a property with custom `get` and `set` accessor logic](#binding-to-a-property-with-c-get-and-set-accessors) to handle invalid entries. +* Use a [form validation component](xref:blazor/forms-and-input-components), such as <xref:Microsoft.AspNetCore.Components.Forms.InputNumber%601> or <xref:Microsoft.AspNetCore.Components.Forms.InputDate%601>. Form validation components provide built-in support to manage invalid inputs. Form validation components: * Permit the user to provide invalid input and receive validation errors on the associated <xref:Microsoft.AspNetCore.Components.Forms.EditContext>. * Display validation errors in the UI without interfering with the user entering additional webform data. @@ -345,10 +467,34 @@ Data binding works with a single <xref:System.DateTime> format string using `@bi `Pages/DateBinding.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor?highlight=6)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor" highlight="6"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor" highlight="6"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor" highlight="6"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor" highlight="6"::: + +:::moniker-end In the preceding code, the `<input>` element's field type (`type` attribute) defaults to `text`. +:::moniker range=">= aspnetcore-6.0" + Nullable <xref:System.DateTime?displayProperty=fullName> and <xref:System.DateTimeOffset?displayProperty=fullName> are supported: ```csharp @@ -356,20 +502,14 @@ private DateTime? date; private DateTimeOffset? dateOffset; ``` +:::moniker-end + Specifying a format for the `date` field type isn't recommended because Blazor has built-in support to format dates. In spite of the recommendation, only use the `yyyy-MM-dd` date format for binding to function correctly if a format is supplied with the `date` field type: ```razor <input type="date" @bind="startDate" @bind:format="yyyy-MM-dd"> ``` -## Custom binding formats - -[C# `get` and `set` accessors](/dotnet/csharp/programming-guide/classes-and-structs/using-properties) can be used to create custom binding format behavior, as the following `DecimalBinding` component demonstrates. The component binds a positive or negative decimal with up to three decimal places to an `<input>` element by way of a `string` property (`DecimalValue`). - -`Pages/DecimalBinding.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor?highlight=7,21-31)] - ## Binding with component parameters A common scenario is binding a property of a child component to a property in its parent component. This scenario is called a *chained bind* because multiple levels of binding occur simultaneously. @@ -386,277 +526,294 @@ The following `ChildBind` component has a `Year` component parameter and an <xre `Shared/ChildBind.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor?highlight=14-15,17-18,22)] - -For more information on events and <xref:Microsoft.AspNetCore.Components.EventCallback%601>, see the *EventCallback* section of the <xref:blazor/components/event-handling#eventcallback> article. +:::moniker range=">= aspnetcore-7.0" -In the following `Parent` component, the `year` field is bound to the `Year` parameter of the child component. The `Year` parameter is bindable because it has a companion `YearChanged` event that matches the type of the `Year` parameter. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor" highlight="14-15,17-18,22"::: -`Pages/Parent.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor?highlight=9)] +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -By convention, a property can be bound to a corresponding event handler by including an `@bind-{PROPERTY}:event` attribute assigned to the handler, where the `{PROPERTY}` placeholder is the property. `<ChildBind @bind-Year="year" />` is equivalent to writing: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor" highlight="14-15,17-18,22"::: -```razor -<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" /> -``` +:::moniker-end -In a more sophisticated and real-world example, the following `PasswordEntry` component: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* Sets an `<input>` element's value to a `password` field. -* Exposes changes of a `Password` property to a parent component with an [`EventCallback`](xref:blazor/components/event-handling#eventcallback) that passes in the current value of the child's `password` field as its argument. -* Uses the `onclick` event to trigger the `ToggleShowPassword` method. For more information, see <xref:blazor/components/event-handling>. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor" highlight="14-15,17-18,22"::: -`Shared/PasswordEntry.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor?highlight=7-10,13,23-24,26-27,36-39)] +:::moniker range="< aspnetcore-5.0" -The `PasswordEntry` component is used in another component, such as the following `PasswordBinding` component example. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor" highlight="14-15,17-18,22"::: -`Pages/PasswordBinding.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor?highlight=5)] +For more information on events and <xref:Microsoft.AspNetCore.Components.EventCallback%601>, see the *EventCallback* section of the <xref:blazor/components/event-handling#eventcallback> article. -When the `PasswordBinding` component is initially rendered, the `password` value of `Not set` is displayed in the UI. After initial rendering, the value of `password` reflects changes made to the `Password` component parameter value in the `PasswordEntry` component. +In the following `Parent1` component, the `year` field is bound to the `Year` parameter of the child component. The `Year` parameter is bindable because it has a companion `YearChanged` event that matches the type of the `Year` parameter. -> [!NOTE] -> The preceding example binds the password one-way from the child `PasswordEntry` component to the parent `PasswordBinding` component. Two-way binding isn't a requirement in this scenario if the goal is for the app to have a shared password entry component for reuse around the app that merely passes the password to the parent. For an approach that permits two-way binding without [writing directly to the child component's parameter](xref:blazor/components/index#overwritten-parameters), see the `NestedChild` component example in the [Bind across more than two components](#bind-across-more-than-two-components) section of this article. +`Pages/Parent1.razor`: -Perform checks or trap errors in the handler. The following revised `PasswordEntry` component provides immediate feedback to the user if a space is used in the password's value. +:::moniker range=">= aspnetcore-7.0" -`Shared/PasswordEntry.razor`: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor" highlight="9"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor?highlight=35-46)] +:::moniker-end -## Bind across more than two components +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -You can bind parameters through any number of nested components, but you must respect the one-way flow of data: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor" highlight="9"::: -* Change notifications *flow up the hierarchy*. -* New parameter values *flow down the hierarchy*. +:::moniker-end -A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated, as shown in the following example. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -`Pages/Parent.razor`: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor" highlight="9"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor)] +:::moniker-end -`Shared/NestedChild.razor`: +:::moniker range="< aspnetcore-5.0" -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor)] +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor" highlight="9"::: -> [!WARNING] -> Generally, avoid creating components that write directly to their own component parameters. The preceding `NestedChild` component makes use of a `BoundValue` property instead of writing directly to its `ChildMessage` parameter. For more information, see <xref:blazor/components/index#overwritten-parameters>. +:::moniker-end -`Shared/NestedGrandchild.razor`: +:::moniker range=">= aspnetcore-7.0" -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor)] +Component parameter binding can also trigger `@bind:after` events. In the following example, the `YearUpdated` method executes asynchronously after binding the `Year` component parameter. -For an alternative approach suited to sharing data in memory and across components that aren't necessarily nested, see <xref:blazor/state-management>. +```razor +<ChildBind @bind-Year="year" @bind-Year:after="YearUpdated" /> -## Additional resources +@code { + ... -* [Parameter change detection and additional guidance on Razor component rendering](xref:blazor/components/rendering) -* <xref:blazor/forms-validation> -* [Binding to radio buttons in a form](xref:blazor/forms-validation#radio-buttons) -* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms-validation#binding-inputselect-options-to-c-object-null-values) -* [ASP.NET Core Blazor event handling: `EventCallback` section](xref:blazor/components/event-handling#eventcallback) + private async Task YearUpdated() + { + ... = await ...; + } +} +``` :::moniker-end -:::moniker range="< aspnetcore-5.0" - -Razor components provide data binding features with the [`@bind`](xref:mvc/views/razor#bind) Razor directive attribute with a field, property, or Razor expression value. +By convention, a property can be bound to a corresponding event handler by including an `@bind-{PROPERTY}:event` attribute assigned to the handler, where the `{PROPERTY}` placeholder is the property. `<ChildBind @bind-Year="year" />` is equivalent to writing: -The following example binds: +```razor +<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" /> +``` -* An `<input>` element value to the C# `inputValue` field. -* A second `<input>` element value to the C# `InputValue` property. +In a more sophisticated and real-world example, the following `PasswordEntry` component: -When an `<input>` element loses focus, its bound field or property is updated. +* Sets an `<input>` element's value to a `password` field. +* Exposes changes of a `Password` property to a parent component with an [`EventCallback`](xref:blazor/components/event-handling#eventcallback) that passes in the current value of the child's `password` field as its argument. +* Uses the `onclick` event to trigger the `ToggleShowPassword` method. For more information, see <xref:blazor/components/event-handling>. -`Pages/Bind.razor`: +`Shared/PasswordEntry.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor?highlight=4,8)] +:::moniker range=">= aspnetcore-7.0" -The text box is updated in the UI only when the component is rendered, not in response to changing the field's or property's value. Since components render themselves after event handler code executes, field and property updates are usually reflected in the UI immediately after an event handler is triggered. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor" highlight="7-10,13,23-24,26-27,36-39"::: -As a demonstration of how data binding composes in HTML, the following example binds the `InputValue` property to the second `<input>` element's `value` and [`onchange`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange) attributes. *The second `<input>` element in the following example is a concept demonstration and isn't meant to suggest how you should bind data in Razor components.* +:::moniker-end -`Pages/BindTheory.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor?highlight=12-14)] +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor" highlight="7-10,13,23-24,26-27,36-39"::: -When the `BindTheory` component is rendered, the `value` of the HTML demonstration `<input>` element comes from the `InputValue` property. When the user enters a value in the text box and changes element focus, the `onchange` event is fired and the `InputValue` property is set to the changed value. In reality, code execution is more complex because [`@bind`](xref:mvc/views/razor#bind) handles cases where type conversions are performed. In general, [`@bind`](xref:mvc/views/razor#bind) associates the current value of an expression with a `value` attribute and handles changes using the registered handler. +:::moniker-end -Bind a property or field on other [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events by including an `@bind:event="{EVENT}"` attribute with a DOM event for the `{EVENT}` placeholder. The following example binds the `InputValue` property to the `<input>` element's value when the element's [`oninput` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) is triggered. Unlike the [`onchange` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange), which fires when the element loses focus, [`oninput`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) fires when the value of the text box changes. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -`Page/BindEvent.razor`: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor" highlight="7-10,13,23-24,26-27,36-39"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor?highlight=4)] +:::moniker-end -Razor attribute binding is case sensitive: +:::moniker range="< aspnetcore-5.0" -* `@bind` and `@bind:event` are valid. -* `@Bind`/`@Bind:Event` (capital letters `B` and `E`) or `@BIND`/`@BIND:EVENT` (all capital letters) **are invalid**. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor" highlight="7-10,13,23-24,26-27,36-39"::: -## Binding `<select>` element options to C# object `null` values +:::moniker-end -There's no sensible way to represent a `<select>` element option value as a C# object `null` value, because: +The `PasswordEntry` component is used in another component, such as the following `PasswordBinding` component example. -* HTML attributes can't have `null` values. The closest equivalent to `null` in HTML is absence of the HTML `value` attribute from the `<option>` element. -* When selecting an `<option>` with no `value` attribute, the browser treats the value as the *text content* of that `<option>`'s element. +`Pages/PasswordBinding.razor`: -The Blazor framework doesn't attempt to suppress the default behavior because it would involve: +:::moniker range=">= aspnetcore-7.0" -* Creating a chain of special-case workarounds in the framework. -* Breaking changes to current framework behavior. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor" highlight="5"::: -The Blazor framework doesn't automatically handle `null` to empty string conversions when attempting two-way binding to a `<select>`'s value. For more information, see [Fix binding `<select>` to a null value (dotnet/aspnetcore #23221)](https://github.com/dotnet/aspnetcore/pull/23221). +:::moniker-end -## Unparsable values +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor" highlight="5"::: -Consider the following component, where an `<input>` element is bound to an `int` type with an initial value of `123`. +:::moniker-end -`Pages/UnparsableValues.razor`: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor?highlight=4,12)] +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor" highlight="5"::: -By default, binding applies to the element's `onchange` event. If the user updates the value of the text box's entry to `123.45` and changes the focus, the element's value is reverted to `123` when `onchange` fires. When the value `123.45` is rejected in favor of the original value of `123`, the user understands that their value wasn't accepted. +:::moniker-end -For the `oninput` event (`@bind:event="oninput"`), a value reversion occurs after any keystroke that introduces an unparsable value. When targeting the `oninput` event with an `int`-bound type, a user is prevented from typing a dot (`.`) character. A dot (`.`) character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. There are scenarios where reverting the value on the `oninput` event isn't ideal, such as when the user should be allowed to clear an unparsable `<input>` value. Alternatives include: +:::moniker range="< aspnetcore-5.0" -* Don't use the `oninput` event. Use the default `onchange` event, where an invalid value isn't reverted until the element loses focus. -* Bind to a nullable type, such as `int?` or `string` and provide [custom `get` and `set` accessor logic](#custom-binding-formats) to handle invalid entries. -* Use a [form validation component](xref:blazor/forms-validation), such as <xref:Microsoft.AspNetCore.Components.Forms.InputNumber%601> or <xref:Microsoft.AspNetCore.Components.Forms.InputDate%601>. Form validation components provide built-in support to manage invalid inputs. Form validation components: - * Permit the user to provide invalid input and receive validation errors on the associated <xref:Microsoft.AspNetCore.Components.Forms.EditContext>. - * Display validation errors in the UI without interfering with the user entering additional webform data. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor" highlight="5"::: -## Format strings +:::moniker-end -Data binding works with a single <xref:System.DateTime> format string using `@bind:format="{FORMAT STRING}"`, where the `{FORMAT STRING}` placeholder is the format string. Other format expressions, such as currency or number formats, aren't available at this time but might be added in a future release. +When the `PasswordBinding` component is initially rendered, the `password` value of `Not set` is displayed in the UI. After initial rendering, the value of `password` reflects changes made to the `Password` component parameter value in the `PasswordEntry` component. -`Pages/DateBinding.razor`: +> [!NOTE] +> The preceding example binds the password one-way from the child `PasswordEntry` component to the parent `PasswordBinding` component. Two-way binding isn't a requirement in this scenario if the goal is for the app to have a shared password entry component for reuse around the app that merely passes the password to the parent. For an approach that permits two-way binding without [writing directly to the child component's parameter](xref:blazor/components/overwriting-parameters), see the `NestedChild` component example in the [Bind across more than two components](#bind-across-more-than-two-components) section of this article. -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor?highlight=6)] +Perform checks or trap errors in the handler. The following revised `PasswordEntry` component provides immediate feedback to the user if a space is used in the password's value. -In the preceding code, the `<input>` element's field type (`type` attribute) defaults to `text`. +`Shared/PasswordEntry.razor`: -Nullable <xref:System.DateTime?displayProperty=fullName> and <xref:System.DateTimeOffset?displayProperty=fullName> are supported: +:::moniker range=">= aspnetcore-7.0" -```csharp -private DateTime? date; -private DateTimeOffset? dateOffset; -``` +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor" highlight="35-46"::: -Specifying a format for the `date` field type isn't recommended because Blazor has built-in support to format dates. In spite of the recommendation, only use the `yyyy-MM-dd` date format for binding to function correctly if a format is supplied with the `date` field type: +In the following example, the `PasswordUpdated` method executes asynchronously after binding the `Password` component parameter: ```razor -<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd"> +<PasswordEntry @bind-Password="password" @bind-Password:after="PasswordUpdated" /> ``` -## Custom binding formats +:::moniker-end -[C# `get` and `set` accessors](/dotnet/csharp/programming-guide/classes-and-structs/using-properties) can be used to create custom binding format behavior, as the following `DecimalBinding` component demonstrates. The component binds a positive or negative decimal with up to three decimal places to an `<input>` element by way of a `string` property (`DecimalValue`). +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`Pages/DecimalBinding.razor`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor" highlight="35-46"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor?highlight=7,21-31)] +:::moniker-end -## Binding with component parameters +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -A common scenario is binding a property of a child component to a property in its parent component. This scenario is called a *chained bind* because multiple levels of binding occur simultaneously. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor" highlight="35-46"::: -[Component parameters](xref:blazor/components/index#component-parameters) permit binding properties of a parent component with `@bind-{PROPERTY}` syntax, where the `{PROPERTY}` placeholder is the property to bind. +:::moniker-end -You can't implement chained binds with [`@bind`](xref:mvc/views/razor#bind) syntax in the child component. An event handler and value must be specified separately to support updating the property in the parent from the child component. +:::moniker range="< aspnetcore-5.0" -The parent component still leverages the [`@bind`](xref:mvc/views/razor#bind) syntax to set up the databinding with the child component. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor" highlight="35-46"::: -The following `ChildBind` component has a `Year` component parameter and an <xref:Microsoft.AspNetCore.Components.EventCallback%601>. By convention, the <xref:Microsoft.AspNetCore.Components.EventCallback%601> for the parameter must be named as the component parameter name with a "`Changed`" suffix. The naming syntax is `{PARAMETER NAME}Changed`, where the `{PARAMETER NAME}` placeholder is the parameter name. In the following example, the <xref:Microsoft.AspNetCore.Components.EventCallback%601> is named `YearChanged`. +:::moniker-end -<xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A?displayProperty=nameWithType> invokes the delegate associated with the binding with the provided argument and dispatches an event notification for the changed property. +## Bind across more than two components -`Shared/ChildBind.razor`: +You can bind parameters through any number of nested components, but you must respect the one-way flow of data: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor?highlight=14-15,17-18,22)] +* Change notifications *flow up the hierarchy*. +* New parameter values *flow down the hierarchy*. -For more information on events and <xref:Microsoft.AspNetCore.Components.EventCallback%601>, see the *EventCallback* section of the <xref:blazor/components/event-handling#eventcallback> article. +A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated, as shown in the following example. -In the following `Parent` component, the `year` field is bound to the `Year` parameter of the child component. The `Year` parameter is bindable because it has a companion `YearChanged` event that matches the type of the `Year` parameter. +`Pages/Parent2.razor`: -`Pages/Parent.razor`: +:::moniker range=">= aspnetcore-7.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor?highlight=9)] +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor"::: -By convention, a property can be bound to a corresponding event handler by including an `@bind-{PROPERTY}:event` attribute assigned to the handler, where the `{PROPERTY}` placeholder is the property. `<ChildBind @bind-Year="year" />` is equivalent to writing: +:::moniker-end -```razor -<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" /> -``` +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -In a more sophisticated and real-world example, the following `PasswordEntry` component: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor"::: -* Sets an `<input>` element's value to a `password` field. -* Exposes changes of a `Password` property to a parent component with an [`EventCallback`](xref:blazor/components/event-handling#eventcallback) that passes in the current value of the child's `password` field as its argument. -* Uses the `onclick` event to trigger the `ToggleShowPassword` method. For more information, see <xref:blazor/components/event-handling>. +:::moniker-end -`Shared/PasswordEntry.razor`: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor?highlight=7-10,13,23-24,26-27,36-39)] +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor"::: -The `PasswordEntry` component is used in another component, such as the following `PasswordBinding` component example. +:::moniker-end -`Pages/PasswordBinding.razor`: +:::moniker range="< aspnetcore-5.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor?highlight=5)] +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor"::: -When the `PasswordBinding` component is initially rendered, the `password` value of `Not set` is displayed in the UI. After initial rendering, the value of `password` reflects changes made to the `Password` component parameter value in the `PasswordEntry` component. +:::moniker-end -> [!NOTE] -> The preceding example binds the password one-way from the child `PasswordEntry` component to the parent `PasswordBinding` component. Two-way binding isn't a requirement in this scenario if the goal is for the app to have a shared password entry component for reuse around the app that merely passes the password to the parent. For an approach that permits two-way binding without [writing directly to the child component's parameter](xref:blazor/components/index#overwritten-parameters), see the `NestedChild` component example in the [Bind across more than two components](#bind-across-more-than-two-components) section of this article. +:::moniker range=">= aspnetcore-7.0" -Perform checks or trap errors in the handler. The following revised `PasswordEntry` component provides immediate feedback to the user if a space is used in the password's value. +In the following `NestedChild` component, the `NestedGrandchild` component: -`Shared/PasswordEntry.razor`: +* Assigns the value of `ChildMessage` to `GrandchildMessage` with `@bind:get` syntax. +* Updates `GrandchildMessage` when `ChildMessageChanged` executes with `@bind:set` syntax. -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor?highlight=35-46)] +:::moniker-end -## Bind across more than two components +`Shared/NestedChild.razor`: -You can bind parameters through any number of nested components, but you must respect the one-way flow of data: +:::moniker range=">= aspnetcore-7.0" -* Change notifications *flow up the hierarchy*. -* New parameter values *flow down the hierarchy*. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor"::: -A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated, as shown in the following example. +:::moniker-end -`Pages/Parent.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor)] +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor"::: -`Shared/NestedChild.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor)] +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" > [!WARNING] -> Generally, avoid creating components that write directly to their own component parameters. The preceding `NestedChild` component makes use of a `BoundValue` property instead of writing directly to its `ChildMessage` parameter. For more information, see <xref:blazor/components/index#overwritten-parameters>. +> Generally, avoid creating components that write directly to their own component parameters. The preceding `NestedChild` component makes use of a `BoundValue` property instead of writing directly to its `ChildMessage` parameter. For more information, see <xref:blazor/components/overwriting-parameters>. + +:::moniker-end `Shared/NestedGrandchild.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor"::: + +:::moniker-end For an alternative approach suited to sharing data in memory and across components that aren't necessarily nested, see <xref:blazor/state-management>. ## Additional resources * [Parameter change detection and additional guidance on Razor component rendering](xref:blazor/components/rendering) -* <xref:blazor/forms-validation> -* [Binding to radio buttons in a form](xref:blazor/forms-validation#radio-buttons) -* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms-validation#binding-inputselect-options-to-c-object-null-values) +* <xref:blazor/forms-and-input-components> +* [Binding to radio buttons in a form](xref:blazor/forms-and-input-components#radio-buttons) +* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms-and-input-components#binding-inputselect-options-to-c-object-null-values) * [ASP.NET Core Blazor event handling: `EventCallback` section](xref:blazor/components/event-handling#eventcallback) - -:::moniker-end +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) diff --git a/aspnetcore/blazor/components/dynamiccomponent.md b/aspnetcore/blazor/components/dynamiccomponent.md index 894ee71d893b..bd89288ab035 100644 --- a/aspnetcore/blazor/components/dynamiccomponent.md +++ b/aspnetcore/blazor/components/dynamiccomponent.md @@ -5,11 +5,13 @@ description: Learn how to use dynamically-rendered Razor components in Blazor ap monikerRange: '>= aspnetcore-6.0' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/dynamiccomponent --- # Dynamically-rendered ASP.NET Core Razor components +[!INCLUDE[](~/includes/not-latest-version.md)] + By [Dave Brock](https://twitter.com/daveabrock) Use the built-in <xref:Microsoft.AspNetCore.Components.DynamicComponent> component to render components by type. @@ -65,6 +67,32 @@ In the following example, a Razor component renders a component based on the use | ULA® | `Shared/UnitedLaunchAlliance.razor` | | Virgin Galactic® | `Shared/VirginGalactic.razor` | +:::moniker range=">= aspnetcore-7.0" + +`Shared/RocketLab.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/RocketLab.razor"::: + +`Shared/SpaceX.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/SpaceX.razor"::: + +`Shared/UnitedLaunchAlliance.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/UnitedLaunchAlliance.razor"::: + +`Shared/VirginGalactic.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/VirginGalactic.razor"::: + +`Pages/DynamicComponentExample1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/dynamiccomponent/DynamicComponentExample1.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + `Shared/RocketLab.razor`: :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/RocketLab.razor"::: @@ -85,10 +113,12 @@ In the following example, a Razor component renders a component based on the use :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/dynamiccomponent/DynamicComponentExample1.razor"::: +:::moniker-end + In the preceding example: * Component names are used as the option values using the [`nameof` operator](/dotnet/csharp/language-reference/operators/nameof), which returns component names as constant strings. -* The namespace of the app is `BlazorSample`. +* The namespace of the app is `BlazorSample`. ***Change the namespace to match your app's namespace.*** ## Pass parameters @@ -111,12 +141,22 @@ The following `RocketLabWithWindowSeat` component (`Shared/RocketLabWithWindowSe `Shared/RocketLabWithWindowSeat.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/RocketLabWithWindowSeat.razor" highlight="13-14"::: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/RocketLabWithWindowSeat.razor" highlight="13-14"::: +:::moniker-end + In the following example: * Only the `RocketLabWithWindowSeat` component's parameter for a window seat (`WindowSeat`) receives the value of the **`Window Seat`** checkbox. -* The namespace of the app is `BlazorSample`. +* The namespace of the app is `BlazorSample`. ***Change the namespace to match your app's namespace.*** * The dynamically-rendered components are shared components in the app's `Shared` folder: * Shown in this article section: `RocketLabWithWindowSeat` (`Shared/RocketLabWithWindowSeat.razor`) * Components shown in the [Example](#example) section earlier in this article: @@ -126,8 +166,251 @@ In the following example: `Pages/DynamicComponentExample2.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/dynamiccomponent/DynamicComponentExample2.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/dynamiccomponent/DynamicComponentExample2.razor"::: +:::moniker-end + +## Event callbacks (`EventCallback`) + +Event callbacks (<xref:Microsoft.AspNetCore.Components.EventCallback>) can be passed to a <xref:Microsoft.AspNetCore.Components.DynamicComponent> in its parameter dictionary. + +`ComponentMetadata.cs`: + +```csharp +public class ComponentMetadata +{ + public string? Name { get; set; } + public Dictionary<string, object> Parameters { get; set; } = + new Dictionary<string, object>(); +} +``` + +Implement an event callback parameter (<xref:Microsoft.AspNetCore.Components.EventCallback>) within each dynamically-rendered component. + +`Shared/RocketLab2.razor`: + +```razor +<h2>Rocket Lab®</h2> + +<p> + Rocket Lab is a registered trademark of + <a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a> +</p> + +<button @onclick="OnClickCallback"> + Trigger a Parent component method +</button> + +@code { + [Parameter] + public EventCallback<MouseEventArgs> OnClickCallback { get; set; } +} +``` + +`Shared/SpaceX2.razor`: + +```razor +<h2>SpaceX®</h2> + +<p> + SpaceX is a registered trademark of + <a href="https://www.spacex.com/">Space Exploration Technologies Corp.</a> +</p> + +<button @onclick="OnClickCallback"> + Trigger a Parent component method +</button> + +@code { + [Parameter] + public EventCallback<MouseEventArgs> OnClickCallback { get; set; } +} +``` + +`Shared/UnitedLaunchAlliance2.razor`: + +```razor +<h2>United Launch Alliance®</h2> + +<p> + United Launch Alliance and ULA are registered trademarks of + <a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>. +</p> + +<button @onclick="OnClickCallback"> + Trigger a Parent component method +</button> + +@code { + [Parameter] + public EventCallback<MouseEventArgs> OnClickCallback { get; set; } +} +``` + +`Shared/VirginGalactic2.razor`: + +```razor +<h2>Virgin Galactic®</h2> + +<p> + Virgin Galactic is a registered trademark of + <a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>. +</p> + +<button @onclick="OnClickCallback"> + Trigger a Parent component method +</button> + +@code { + [Parameter] + public EventCallback<MouseEventArgs> OnClickCallback { get; set; } +} +``` + +In the following parent component example, the `ShowDTMessage` method assigns a string with the current time to `message`, and the value of `message` is rendered. + +The parent component passes the callback method, `ShowDTMessage` in the parameter dictionary: + +* The `string` key is the callback method's name, `OnClickCallback`. +* The `object` value is created by <xref:Microsoft.AspNetCore.Components.EventCallbackFactory.Create%2A?displayProperty=nameWithType> for the parent callback method, `ShowDTMessage`. Note that the [`this` keyword](/dotnet/csharp/language-reference/keywords/this) isn't supported in C# fields, so a C# property is used for the parameter dictionary. + +> [!IMPORTANT] +> For the following `DynamicComponentExample3` component, modify the code in the `OnDropdownChange` method. Change the namespace name of "`BlazorSample`" in the `Type.GetType()` argument to match your app's namespace. + +`Pages/DynamicComponentExample3.razor`: + +```razor +@page "/dynamiccomponent-example-3" + +<h1><code>DynamicComponent</code> Component Example 3</h1> + +<p> + <label> + Select your transport: + <select @onchange="OnDropdownChange"> + <option value="">Select a value</option> + <option value="@nameof(RocketLab2)">Rocket Lab</option> + <option value="@nameof(SpaceX2)">SpaceX</option> + <option value="@nameof(UnitedLaunchAlliance2)">ULA</option> + <option value="@nameof(VirginGalactic2)">Virgin Galactic</option> + </select> + </label> +</p> + +@if (selectedType is not null) +{ + <div class="border border-primary my-1 p-1"> + <DynamicComponent Type="@selectedType" + Parameters="@Components[selectedType.Name].Parameters" /> + </div> +} + +<p> + @message +</p> + +@code { + private Type? selectedType; + private string? message; + + private Dictionary<string, ComponentMetadata> Components + { + get + { + return new Dictionary<string, ComponentMetadata>() + { + { + "RocketLab2", + new ComponentMetadata + { + Name = "Rocket Lab", + Parameters = + new() + { + { + "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) + } + } + } + }, + { + "VirginGalactic2", + new ComponentMetadata + { + Name = "Virgin Galactic", + Parameters = + new() + { + { + "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) + } + } + } + }, + { + "UnitedLaunchAlliance2", + new ComponentMetadata + { + Name = "ULA", + Parameters = + new() + { + { + "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) + } + } + } + }, + { + "SpaceX2", + new ComponentMetadata + { + Name = "SpaceX", + Parameters = + new() + { + { + "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) + } + } + } + } + }; + } + } + + private void OnDropdownChange(ChangeEventArgs e) + { + /* + IMPORTANT! + Change "BlazorSample" to match your app's namespace + in the Type.GetType() argument. + */ + selectedType = e.Value?.ToString()?.Length > 0 ? + Type.GetType($"BlazorSample.Shared.{e.Value}") : null; + } + + private void ShowDTMessage(MouseEventArgs e) => + message = $"The current DT is: {DateTime.Now}."; +} +``` + ## Avoid catch-all parameters Avoid the use of [catch-all parameters](xref:blazor/fundamentals/routing#catch-all-route-parameters). If catch-all parameters are used, every explicit parameter on <xref:Microsoft.AspNetCore.Components.DynamicComponent> effectively is a reserved word that you can't pass to a dynamic child. Any new parameters passed to <xref:Microsoft.AspNetCore.Components.DynamicComponent> are a breaking change, as they start shadowing child component parameters that happen to have the same name. It's unlikely that the caller always knows a fixed set of parameter names to pass to all possible dynamic children. @@ -135,3 +418,7 @@ Avoid the use of [catch-all parameters](xref:blazor/fundamentals/routing#catch-a ## Trademarks Rocket Lab is a registered trademark of [Rocket Lab USA Inc.](https://www.rocketlabusa.com/) SpaceX is a registered trademark of [Space Exploration Technologies Corp.](https://www.spacex.com/) United Launch Alliance and ULA are registered trademarks of [United Launch Alliance, LLC](https://www.ulalaunch.com/). Virgin Galactic is a registered trademark of [Galactic Enterprises, LLC](https://www.virgingalactic.com/). + +## Additional resources + +* <xref:blazor/components/event-handling#eventcallback> diff --git a/aspnetcore/blazor/components/element-component-model-relationships.md b/aspnetcore/blazor/components/element-component-model-relationships.md new file mode 100644 index 000000000000..efac30ab23c7 --- /dev/null +++ b/aspnetcore/blazor/components/element-component-model-relationships.md @@ -0,0 +1,220 @@ +--- +title: Retain element, component, and model relationships in ASP.NET Core Blazor +author: guardrex +description: Learn how to use the @key directive attribute to retain element, component, and model relationships when rendering and the elements or components subsequently change. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 05/15/2023 +uid: blazor/components/key +--- +# Retain element, component, and model relationships in ASP.NET Core Blazor + +When rendering a list of elements or components and the elements or components subsequently change, Blazor must decide which of the previous elements or components are retained and how model objects should map to them. Normally, this process is automatic and sufficient for general rendering, but there are often cases where controlling the process using the [`@key`](xref:mvc/views/razor#key) directive attribute is required. + +Consider the following example that demonstrates a collection mapping problem that's solved by using [`@key`](xref:mvc/views/razor#key). + +For the following `Details` and `PeopleExample` components: + +* The `Details` component receives data (`Data`) from the parent `PeopleExample` component, which is displayed in an `<input>` element. Any given displayed `<input>` element can receive the focus of the page from the user when they select one of the `<input>` elements. +* The `PeopleExample` component creates a list of person objects for display using the `Details` component. Every three seconds, a new person is added to the collection. + +This demonstration allows you to: + +* Select an `<input>` from among several rendered `Details` components. +* Study the behavior of the page's focus as the people collection automatically grows. + +`Shared/Details.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/element-component-model-relationships/Details.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/element-component-model-relationships/Details.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/element-component-model-relationships/Details.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/element-component-model-relationships/Details.razor"::: + +:::moniker-end + +In the following `PeopleExample` component, each iteration of adding a person in `OnTimerCallback` results in Blazor rebuilding the entire collection. The page's focus remains on the *same index* position of `<input>` elements, so the focus shifts each time a person is added. *Shifting the focus away from what the user selected isn't desirable behavior.* After demonstrating the poor behavior with the following component, the [`@key`](xref:mvc/views/razor#key) directive attribute is used to improve the user's experience. + +`Pages/PeopleExample.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/element-component-model-relationships/PeopleExample.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/element-component-model-relationships/PeopleExample.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/element-component-model-relationships/PeopleExample.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/element-component-model-relationships/PeopleExample.razor"::: + +:::moniker-end + +The contents of the `people` collection changes with inserted, deleted, or re-ordered entries. Rerendering can lead to visible behavior differences. For example, each time a person is inserted into the `people` collection, the user's focus is lost. + +The mapping process of elements or components to a collection can be controlled with the [`@key`](xref:mvc/views/razor#key) directive attribute. Use of [`@key`](xref:mvc/views/razor#key) guarantees the preservation of elements or components based on the key's value. If the `Details` component in the preceding example is keyed on the `person` item, Blazor ignores rerendering `Details` components that haven't changed. + +To modify the `PeopleExample` component to use the [`@key`](xref:mvc/views/razor#key) directive attribute with the `people` collection, update the `<Details>` element to the following: + +```razor +<Details @key="person" Data="@person.Data" /> +``` + +When the `people` collection changes, the association between `Details` instances and `person` instances is retained. When a `Person` is inserted at the beginning of the collection, one new `Details` instance is inserted at that corresponding position. Other instances are left unchanged. Therefore, the user's focus isn't lost as people are added to the collection. + +Other collection updates exhibit the same behavior when the [`@key`](xref:mvc/views/razor#key) directive attribute is used: + +* If an instance is deleted from the collection, only the corresponding component instance is removed from the UI. Other instances are left unchanged. +* If collection entries are re-ordered, the corresponding component instances are preserved and re-ordered in the UI. + +> [!IMPORTANT] +> Keys are local to each container element or component. Keys aren't compared globally across the document. + +## When to use `@key` + +Typically, it makes sense to use [`@key`](xref:mvc/views/razor#key) whenever a list is rendered (for example, in a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) block) and a suitable value exists to define the [`@key`](xref:mvc/views/razor#key). + +You can also use [`@key`](xref:mvc/views/razor#key) to preserve an element or component subtree when an object doesn't change, as the following examples show. + +Example 1: + +```razor +<li @key="person"> + <input value="@person.Data" /> +</li> +``` + +Example 2: + +```razor +<div @key="person"> + @* other HTML elements *@ +</div> +``` + +If an `person` instance changes, the [`@key`](xref:mvc/views/razor#key) attribute directive forces Blazor to: + +* Discard the entire `<li>` or `<div>` and their descendants. +* Rebuild the subtree within the UI with new elements and components. + +This is useful to guarantee that no UI state is preserved when the collection changes within a subtree. + +## Scope of `@key` + +The [`@key`](xref:mvc/views/razor#key) attribute directive is scoped to its own siblings within its parent. + +Consider the following example. The `first` and `second` keys are compared against each other within the same scope of the outer `<div>` element: + +```razor +<div> + <div @key="first">...</div> + <div @key="second">...</div> +</div> +``` + +The following example demonstrates `first` and `second` keys in their own scopes, unrelated to each other and without influence on each other. Each [`@key`](xref:mvc/views/razor#key) scope only applies to its parent `<div>` element, not across the parent `<div>` elements: + +```razor +<div> + <div @key="first">...</div> +</div> +<div> + <div @key="second">...</div> +</div> +``` + +For the `Details` component shown earlier, the following examples render `person` data within the same [`@key`](xref:mvc/views/razor#key) scope and demonstrate typical use cases for [`@key`](xref:mvc/views/razor#key): + +```razor +<div> + @foreach (var person in people) + { + <Details @key="person" Data="@person.Data" /> + } +</div> +``` + +```razor +@foreach (var person in people) +{ + <div @key="person"> + <Details Data="@person.Data" /> + </div> +} +``` + +```razor +<ol> + @foreach (var person in people) + { + <li @key="person"> + <Details Data="@person.Data" /> + </li> + } +</ol> +``` + +The following examples only scope [`@key`](xref:mvc/views/razor#key) to the `<div>` or `<li>` element that surrounds each `Details` component instance. Therefore, `person` data for each member of the `people` collection is **not** keyed on each `person` instance across the rendered `Details` components. Avoid the following patterns when using [`@key`](xref:mvc/views/razor#key): + +```razor +@foreach (var person in people) +{ + <div> + <Details @key="person" Data="@person.Data" /> + </div> +} +``` + +```razor +<ol> + @foreach (var person in people) + { + <li> + <Details @key="person" Data="@person.Data" /> + </li> + } +</ol> +``` + +## When not to use `@key` + +There's a performance cost when rendering with [`@key`](xref:mvc/views/razor#key). The performance cost isn't large, but only specify [`@key`](xref:mvc/views/razor#key) if preserving the element or component benefits the app. + +Even if [`@key`](xref:mvc/views/razor#key) isn't used, Blazor preserves child element and component instances as much as possible. The only advantage to using [`@key`](xref:mvc/views/razor#key) is control over *how* model instances are mapped to the preserved component instances, instead of Blazor selecting the mapping. + +## Values to use for `@key` + +Generally, it makes sense to supply one of the following values for [`@key`](xref:mvc/views/razor#key): + +* Model object instances. For example, the `Person` instance (`person`) was used in the earlier example. This ensures preservation based on object reference equality. +* Unique identifiers. For example, unique identifiers can be based on primary key values of type `int`, `string`, or `Guid`. + +Ensure that values used for [`@key`](xref:mvc/views/razor#key) don't clash. If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Only use distinct values, such as object instances or primary key values. diff --git a/aspnetcore/blazor/components/event-handling.md b/aspnetcore/blazor/components/event-handling.md index 5cc4ff847f63..5a90728e1a7d 100644 --- a/aspnetcore/blazor/components/event-handling.md +++ b/aspnetcore/blazor/components/event-handling.md @@ -5,14 +5,14 @@ description: Learn about Blazor's event handling features, including event argum monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/event-handling --- # ASP.NET Core Blazor event handling -This article explains Blazor's event handling features, including event argument types, event callbacks, and managing default browser events. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains Blazor's event handling features, including event argument types, event callbacks, and managing default browser events. Specify delegate event handlers in Razor component markup with [`@on{DOM EVENT}="{DELEGATE}"`](xref:mvc/views/razor#onevent) Razor syntax: @@ -32,8 +32,30 @@ The following code: `Pages/EventHandlerExample1.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor" highlight="10,17,27-30,32-35"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor" highlight="10,17,27-30,32-35"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor" highlight="10,17,27-30,32-35"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor" highlight="10,17,27-30,32-35"::: + +:::moniker-end + In the following example, `UpdateHeading`: * Is called asynchronously when the button is selected. @@ -41,34 +63,76 @@ In the following example, `UpdateHeading`: `Pages/EventHandlerExample2.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor" highlight="10,19-24"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor" highlight="10,19-24"::: -## Event arguments +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor" highlight="10,19-24"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor" highlight="10,19-24"::: + +:::moniker-end -### Built-in event arguments +## Built-in event arguments For events that support an event argument type, specifying an event parameter in the event method definition is only necessary if the event type is used in the method. In the following example, <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> is used in the `ReportPointerLocation` method to set message text that reports the mouse coordinates when the user selects a button in the UI. `Pages/EventHandlerExample3.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor" highlight="17-20"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor" highlight="17-20"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor" highlight="17-20"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor" highlight="17-20"::: + +:::moniker-end + Supported <xref:System.EventArgs> are shown in the following table. -| Event | Class | [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events and notes | +| Event | Class | [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) notes | | ---------------- | ------ | --- | -| Clipboard | <xref:Microsoft.AspNetCore.Components.Web.ClipboardEventArgs> | `oncut`, `oncopy`, `onpaste` | -| Drag | <xref:Microsoft.AspNetCore.Components.Web.DragEventArgs> | `ondrag`, `ondragstart`, `ondragenter`, `ondragleave`, `ondragover`, `ondrop`, `ondragend`<br><br><xref:Microsoft.AspNetCore.Components.Web.DataTransfer> and <xref:Microsoft.AspNetCore.Components.Web.DataTransferItem> hold dragged item data.<br><br>Implement drag and drop in Blazor apps using [JS interop](xref:blazor/js-interop/call-javascript-from-dotnet) with [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API). | -| Error | <xref:Microsoft.AspNetCore.Components.Web.ErrorEventArgs> | `onerror` | -| Event | <xref:System.EventArgs> | *General*<br>`onactivate`, `onbeforeactivate`, `onbeforedeactivate`, `ondeactivate`, `onfullscreenchange`, `onfullscreenerror`, `onloadeddata`, `onloadedmetadata`, `onpointerlockchange`, `onpointerlockerror`, `onreadystatechange`, `onscroll`<br><br>*Clipboard*<br>`onbeforecut`, `onbeforecopy`, `onbeforepaste`<br><br>*Input*<br>`oninvalid`, `onreset`, `onselect`, `onselectionchange`, `onselectstart`, `onsubmit`<br><br>*Media*<br>`oncanplay`, `oncanplaythrough`, `oncuechange`, `ondurationchange`, `onemptied`, `onended`, `onpause`, `onplay`, `onplaying`, `onratechange`, `onseeked`, `onseeking`, `onstalled`, `onstop`, `onsuspend`, `ontimeupdate`, `ontoggle`, `onvolumechange`, `onwaiting`<br><br><xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. | -| Focus | <xref:Microsoft.AspNetCore.Components.Web.FocusEventArgs> | `onfocus`, `onblur`, `onfocusin`, `onfocusout`<br><br>Doesn't include support for `relatedTarget`. | -| Input | <xref:Microsoft.AspNetCore.Components.ChangeEventArgs> | `onchange`, `oninput` | -| Keyboard | <xref:Microsoft.AspNetCore.Components.Web.KeyboardEventArgs> | `onkeydown`, `onkeypress`, `onkeyup` | -| Mouse | <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> | `onclick`, `oncontextmenu`, `ondblclick`, `onmousedown`, `onmouseup`, `onmouseover`, `onmousemove`, `onmouseout` | -| Mouse pointer | <xref:Microsoft.AspNetCore.Components.Web.PointerEventArgs> | `onpointerdown`, `onpointerup`, `onpointercancel`, `onpointermove`, `onpointerover`, `onpointerout`, `onpointerenter`, `onpointerleave`, `ongotpointercapture`, `onlostpointercapture` | -| Mouse wheel | <xref:Microsoft.AspNetCore.Components.Web.WheelEventArgs> | `onwheel`, `onmousewheel` | -| Progress | <xref:Microsoft.AspNetCore.Components.Web.ProgressEventArgs> | `onabort`, `onload`, `onloadend`, `onloadstart`, `onprogress`, `ontimeout` | -| Touch | <xref:Microsoft.AspNetCore.Components.Web.TouchEventArgs> | `ontouchstart`, `ontouchend`, `ontouchmove`, `ontouchenter`, `ontouchleave`, `ontouchcancel`<br><br><xref:Microsoft.AspNetCore.Components.Web.TouchPoint> represents a single contact point on a touch-sensitive device. | +| Clipboard | <xref:Microsoft.AspNetCore.Components.Web.ClipboardEventArgs> | | +| Drag | <xref:Microsoft.AspNetCore.Components.Web.DragEventArgs> | <xref:Microsoft.AspNetCore.Components.Web.DataTransfer> and <xref:Microsoft.AspNetCore.Components.Web.DataTransferItem> hold dragged item data.<br><br>Implement drag and drop in Blazor apps using [JS interop](xref:blazor/js-interop/call-javascript-from-dotnet) with [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API). | +| Error | <xref:Microsoft.AspNetCore.Components.Web.ErrorEventArgs> | | +| Event | <xref:System.EventArgs> | <xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. | +| Focus | <xref:Microsoft.AspNetCore.Components.Web.FocusEventArgs> | Doesn't include support for `relatedTarget`. | +| Input | <xref:Microsoft.AspNetCore.Components.ChangeEventArgs> | | +| Keyboard | <xref:Microsoft.AspNetCore.Components.Web.KeyboardEventArgs> | | +| Mouse | <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> | | +| Mouse pointer | <xref:Microsoft.AspNetCore.Components.Web.PointerEventArgs> | | +| Mouse wheel | <xref:Microsoft.AspNetCore.Components.Web.WheelEventArgs> | | +| Progress | <xref:Microsoft.AspNetCore.Components.Web.ProgressEventArgs> | | +| Touch | <xref:Microsoft.AspNetCore.Components.Web.TouchEventArgs> | <xref:Microsoft.AspNetCore.Components.Web.TouchPoint> represents a single contact point on a touch-sensitive device. | For more information, see the following resources: @@ -76,13 +140,15 @@ For more information, see the following resources: [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] -* [MDN web docs: GlobalEventHandlers](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers): Includes information on which HTML elements support each DOM event. +* <xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. + +:::moniker range=">= aspnetcore-6.0" -### Custom event arguments +## Custom event arguments Blazor supports custom event arguments, which enable you to pass arbitrary data to .NET event handlers with custom events. -#### General configuration +### General configuration Custom events with custom event arguments are generally enabled with the following steps. @@ -97,22 +163,30 @@ Custom events with custom event arguments are generally enabled with the followi } ``` -1. Register the custom event with the preceding handler in `wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Layout.cshtml` (Blazor Server) immediately after the Blazor `<script>`: +1. Register the custom event with the preceding handler in a [JavaScript initializer](xref:blazor/fundamentals/startup#javascript-initializers). + + `wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js`: - ```html - <script> - Blazor.registerCustomEventType('customevent', { - createEventArgs: eventArgsCreator; + ```javascript + export function afterStarted(blazor) { + blazor.registerCustomEventType('customevent', { + createEventArgs: eventArgsCreator }); - </script> + } ``` + In the preceding example, the `{PACKAGE ID/ASSEMBLY NAME}` placeholder of the file name represents the package ID or assembly name of the app. + > [!NOTE] > The call to `registerCustomEventType` is performed in a script only once per event. + > + > For the call to `registerCustomEventType`, use the `blazor` parameter (lowercase `b`) provided by `afterStarted`. Although the registration is valid when using the `Blazor` object (uppercase `B`), the preferred approach is to use the parameter. 1. Define a class for the event arguments: ```csharp + namespace BlazorSample.CustomEvents; + public class CustomEventArgs : EventArgs { public string? CustomProperty1 {get; set;} @@ -120,11 +194,22 @@ Custom events with custom event arguments are generally enabled with the followi } ``` -1. Wire up the custom event with the event arguments by adding an <xref:Microsoft.AspNetCore.Components.EventHandlerAttribute> attribute annotation for the custom event. The class doesn't require members. Note that the class *must* be called `EventHandlers` in order to be found by the Razor compiler, but you should put it in a namespace specific to your app: +1. Wire up the custom event with the event arguments by adding an <xref:Microsoft.AspNetCore.Components.EventHandlerAttribute> attribute annotation for the custom event: + + * In order for the compiler to find the `[EventHandler]` class, it must be placed into a C# class file (`.cs`), making it a normal top-level class. + * Mark the class `public`. + * The class doesn't require members. + * The class *must* be called "`EventHandlers`" in order to be found by the Razor compiler. + * Place the class under a namespace specific to your app. + * Import the namespace into the Razor component (`.razor`) where the event is used. ```csharp + using Microsoft.AspNetCore.Components; + + namespace BlazorSample.CustomEvents; + [EventHandler("oncustomevent", typeof(CustomEventArgs), enableStopPropagation: true, enablePreventDefault: true)] - static class EventHandlers + public static class EventHandlers { } ``` @@ -132,11 +217,13 @@ Custom events with custom event arguments are generally enabled with the followi 1. Register the event handler on one or more HTML elements. Access the data that was passed in from JavaScript in the delegate handler method: ```razor + @using namespace BlazorSample.CustomEvents + <button @oncustomevent="HandleCustomEvent">Handle</button> @code { - void HandleCustomEvent(CustomEventArgs eventArgs) + private void HandleCustomEvent(CustomEventArgs eventArgs) { // eventArgs.CustomProperty1 // eventArgs.CustomProperty2 @@ -150,7 +237,7 @@ Whenever the custom event is fired on the DOM, the event handler is called with If you're attempting to fire a custom event, [`bubbles`](https://developer.mozilla.org/docs/Web/API/Event/bubbles) must be enabled by setting its value to `true`. Otherwise, the event doesn't reach the Blazor handler for processing into the C# custom <xref:Microsoft.AspNetCore.Components.EventHandlerAttribute> method. For more information, see [MDN Web Docs: Event bubbling](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events#event_bubbling). -#### Custom clipboard paste event example +### Custom clipboard paste event example The following example receives a custom clipboard paste event that includes the time of the paste and the user's pasted text. @@ -159,6 +246,10 @@ Declare a custom name (`oncustompaste`) for the event and a .NET class (`CustomP `CustomEvents.cs`: ```csharp +using Microsoft.AspNetCore.Components; + +namespace BlazorSample.CustomEvents; + [EventHandler("oncustompaste", typeof(CustomPasteEventArgs), enableStopPropagation: true, enablePreventDefault: true)] public static class EventHandlers @@ -172,24 +263,29 @@ public class CustomPasteEventArgs : EventArgs } ``` -Add JavaScript code to supply data for the <xref:System.EventArgs> subclass. In the `wwwroot/index.html` or `Pages/_Layout.cshtml` file, add the following `<script>` tag and content immediately after the Blazor script. The following example only handles pasting text, but you could use arbitrary JavaScript APIs to deal with users pasting other types of data, such as images. +Add JavaScript code to supply data for the <xref:System.EventArgs> subclass with the preceding handler in a [JavaScript initializer](xref:blazor/fundamentals/startup#javascript-initializers). The following example only handles pasting text, but you could use arbitrary JavaScript APIs to deal with users pasting other types of data, such as images. -`wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Layout.cshtml` (Blazor Server) immediately after the Blazor script: +`wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js`: -```html -<script> - Blazor.registerCustomEventType('custompaste', { - browserEventName: 'paste', - createEventArgs: event => { - return { - eventTimestamp: new Date(), - pastedData: event.clipboardData.getData('text') - }; - } +```javascript +export function afterStarted(blazor) { + blazor.registerCustomEventType('custompaste', { + browserEventName: 'paste', + createEventArgs: event => { + return { + eventTimestamp: new Date(), + pastedData: event.clipboardData.getData('text') + }; + } }); -</script> +} ``` +In the preceding example, the `{PACKAGE ID/ASSEMBLY NAME}` placeholder of the file name represents the package ID or assembly name of the app. + +> [!NOTE] +> For the call to `registerCustomEventType`, use the `blazor` parameter (lowercase `b`) provided by `afterStarted`. Although the registration is valid when using the `Blazor` object (uppercase `B`), the preferred approach is to use the parameter. + The preceding code tells the browser that when a native [`paste`](https://developer.mozilla.org/docs/Web/API/Element/paste_event) event occurs: * Raise a `custompaste` event. @@ -208,6 +304,7 @@ In a Razor component, attach the custom handler to an element. ```razor @page "/custom-paste-arguments" +@using BlazorSample.CustomEvents <label> Try pasting into the following text box: @@ -229,222 +326,148 @@ In a Razor component, attach the custom handler to an element. } ``` +:::moniker-end + ## Lambda expressions [Lambda expressions](/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions) are supported as the delegate event handler. `Pages/EventHandlerExample4.razor`: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor" highlight="6"::: - -It's often convenient to close over additional values using C# method parameters, such as when iterating over a set of elements. The following example creates three buttons, each of which calls `UpdateHeading` and passes the following data: +:::moniker range=">= aspnetcore-7.0" -* An event argument (<xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs>) in `e`. -* The button number in `buttonNumber`. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor" highlight="6"::: -`Pages/EventHandlerExample5.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor" highlight="10,19"::: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -> [!NOTE] -> Do **not** use a loop variable directly in a lambda expression, such as `i` in the preceding `for` loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Always capture the variable's value in a local variable and then use it. In the preceding example: -> -> * The loop variable `i` is assigned to `buttonNumber`. -> * `buttonNumber` is used in the lambda expression. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor" highlight="6"::: -Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see <xref:blazor/performance#avoid-recreating-delegates-for-many-repeated-elements-or-components>. +:::moniker-end -## EventCallback +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -A common scenario with nested components executes a parent component's method when a child component event occurs. An `onclick` event occurring in the child component is a common use case. To expose events across components, use an <xref:Microsoft.AspNetCore.Components.EventCallback>. A parent component can assign a callback method to a child component's <xref:Microsoft.AspNetCore.Components.EventCallback>. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor" highlight="6"::: -The following `Child` component demonstrates how a button's `onclick` handler is set up to receive an <xref:Microsoft.AspNetCore.Components.EventCallback> delegate from the sample's `ParentComponent`. The <xref:Microsoft.AspNetCore.Components.EventCallback> is typed with `MouseEventArgs`, which is appropriate for an `onclick` event from a peripheral device. +:::moniker-end -`Shared/Child.razor`: +:::moniker range="< aspnetcore-5.0" -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/event-handling/Child.razor"::: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor" highlight="6"::: -The `Parent` component sets the child's <xref:Microsoft.AspNetCore.Components.EventCallback%601> (`OnClickCallback`) to its `ShowMessage` method. +:::moniker-end -`Pages/Parent.razor`: +It's often convenient to close over additional values using C# method parameters, such as when iterating over a set of elements. The following example creates three buttons, each of which calls `UpdateHeading` and passes the following data: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor"::: +* An event argument (<xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs>) in `e`. +* The button number in `buttonNumber`. -When the button is selected in the `ChildComponent`: +`Pages/EventHandlerExample5.razor`: -* The `Parent` component's `ShowMessage` method is called. `message` is updated and displayed in the `Parent` component. -* A call to [`StateHasChanged`](xref:blazor/components/lifecycle#state-changes-statehaschanged) isn't required in the callback's method (`ShowMessage`). <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called automatically to rerender the `Parent` component, just as child events trigger component rerendering in event handlers that execute within the child. For more information, see <xref:blazor/components/rendering>. +:::moniker range=">= aspnetcore-7.0" -<xref:Microsoft.AspNetCore.Components.EventCallback> and <xref:Microsoft.AspNetCore.Components.EventCallback%601> permit asynchronous delegates. <xref:Microsoft.AspNetCore.Components.EventCallback> is weakly typed and allows passing any type argument in `InvokeAsync(Object)`. <xref:Microsoft.AspNetCore.Components.EventCallback%601> is strongly typed and requires passing a `T` argument in `InvokeAsync(T)` that's assignable to `TValue`. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor" highlight="10,19"::: -```razor -<ChildComponent - OnClickCallback="@(async () => { await Task.Yield(); messageText = "Blaze It!"; })" /> -``` +:::moniker-end -Invoke an <xref:Microsoft.AspNetCore.Components.EventCallback> or <xref:Microsoft.AspNetCore.Components.EventCallback%601> with <xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A> and await the <xref:System.Threading.Tasks.Task>: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```csharp -await OnClickCallback.InvokeAsync(arg); -``` +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor" highlight="10,19"::: -Use <xref:Microsoft.AspNetCore.Components.EventCallback> and <xref:Microsoft.AspNetCore.Components.EventCallback%601> for event handling and binding component parameters. +:::moniker-end -Prefer the strongly typed <xref:Microsoft.AspNetCore.Components.EventCallback%601> over <xref:Microsoft.AspNetCore.Components.EventCallback>. <xref:Microsoft.AspNetCore.Components.EventCallback%601> provides enhanced error feedback to users of the component. Similar to other UI event handlers, specifying the event parameter is optional. Use <xref:Microsoft.AspNetCore.Components.EventCallback> when there's no value passed to the callback. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -## Prevent default actions +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor" highlight="10,19"::: -Use the [`@on{DOM EVENT}:preventDefault`](xref:mvc/views/razor#oneventpreventdefault) directive attribute to prevent the default action for an event, where the `{DOM EVENT}` placeholder is a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events). +:::moniker-end -When a key is selected on an input device and the element focus is on a text box, a browser normally displays the key's character in the text box. In the following example, the default behavior is prevented by specifying the `@onkeydown:preventDefault` directive attribute. When the focus is on the `<input>` element, the counter increments with the key sequence <kbd>Shift</kbd>+<kbd>+</kbd>. The `+` character isn't assigned to the `<input>` element's value. For more information on `keydown`, see [`MDN Web Docs: Document: keydown` event](https://developer.mozilla.org/docs/Web/API/Document/keydown_event). +:::moniker range="< aspnetcore-5.0" -`Pages/EventHandlerExample6.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor" highlight="10,19"::: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor" highlight="4"::: +:::moniker-end -Specifying the `@on{DOM EVENT}:preventDefault` attribute without a value is equivalent to `@on{DOM EVENT}:preventDefault="true"`. +Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see <xref:blazor/performance#avoid-recreating-delegates-for-many-repeated-elements-or-components>. -An expression is also a permitted value of the attribute. In the following example, `shouldPreventDefault` is a `bool` field set to either `true` or `false`: +Avoid using a loop variable directly in a lambda expression, such as `i` in the preceding `for` loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Capture the variable's value in a local variable. In the preceding example: -```razor -<input @onkeydown:preventDefault="shouldPreventDefault" /> +* The loop variable `i` is assigned to `buttonNumber`. +* `buttonNumber` is used in the lambda expression. -... +Alternatively, use a `foreach` loop with <xref:System.Linq.Enumerable.Range%2A?displayProperty=nameWithType>, which doesn't suffer from the preceding problem: -@code { - private bool shouldPreventDefault = true; +```razor +@foreach (var buttonNumber in Enumerable.Range(1,3)) +{ + <p> + <button @onclick="@(e => UpdateHeading(e, buttonNumber))"> + Button #@buttonNumber + </button> + </p> } ``` -## Stop event propagation - -Use the [`@on{DOM EVENT}:stopPropagation`](xref:mvc/views/razor#oneventstoppropagation) directive attribute to stop event propagation within the Blazor scope. `{DOM EVENT}` is a placeholder for a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events). - -The `stopPropagation` directive attribute's effect is limited to the Blazor scope and doesn't extend to the HTML DOM. Events must propagate to the HTML DOM root before Blazor can act upon them. For a mechanism to prevent HTML DOM event propagation, consider the following approach: +## EventCallback -* Obtain the event's path by calling [`Event.composedPath()`](https://developer.mozilla.org/docs/Web/API/Event/composedPath). -* Filter events based on the composed [event targets (`EventTarget`)](https://developer.mozilla.org/docs/Web/API/EventTarget). +A common scenario with nested components executes a parent component's method when a child component event occurs. An `onclick` event occurring in the child component is a common use case. To expose events across components, use an <xref:Microsoft.AspNetCore.Components.EventCallback>. A parent component can assign a callback method to a child component's <xref:Microsoft.AspNetCore.Components.EventCallback>. -In the following example, selecting the checkbox prevents click events from the second child `<div>` from propagating to the parent `<div>`. Since propagated click events normally fire the `OnSelectParentDiv` method, selecting the second child `<div>` results in the parent `<div>` message appearing unless the checkbox is selected. +The following `Child` component demonstrates how a button's `onclick` handler is set up to receive an <xref:Microsoft.AspNetCore.Components.EventCallback> delegate from the sample's `ParentComponent`. The <xref:Microsoft.AspNetCore.Components.EventCallback> is typed with `MouseEventArgs`, which is appropriate for an `onclick` event from a peripheral device. -`Pages/EventHandlerExample7.razor`: +`Shared/Child.razor`: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: +:::moniker range=">= aspnetcore-7.0" -## Focus an element +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/event-handling/Child.razor"::: -Call <xref:Microsoft.AspNetCore.Components.ElementReferenceExtensions.FocusAsync%2A> on an [element reference](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements) to focus an element in code. In the following example, select the button to focus the `<input>` element. +:::moniker-end -`Pages/EventHandlerExample8.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample8.razor" highlight="16"::: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/event-handling/Child.razor"::: :::moniker-end :::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -Specify delegate event handlers in Razor component markup with [`@on{DOM EVENT}="{DELEGATE}"`](xref:mvc/views/razor#onevent) Razor syntax: - -* The `{DOM EVENT}` placeholder is a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events) (for example, `click`). -* The `{DELEGATE}` placeholder is the C# delegate event handler. - -For event handling: - -* Asynchronous delegate event handlers that return a <xref:System.Threading.Tasks.Task> are supported. -* Delegate event handlers automatically trigger a UI render, so there's no need to manually call [`StateHasChanged`](xref:blazor/components/lifecycle#state-changes-statehaschanged). -* Exceptions are logged. - -The following code: - -* Calls the `UpdateHeading` method when the button is selected in the UI. -* Calls the `CheckChanged` method when the checkbox is changed in the UI. - -`Pages/EventHandlerExample1.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor?highlight=10,17,27-30,32-35)] - -In the following example, `UpdateHeading`: - -* Is called asynchronously when the button is selected. -* Waits two seconds before updating the heading. - -`Pages/EventHandlerExample2.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor?highlight=10,19-24)] - -## Event arguments - -For events that support an event argument type, specifying an event parameter in the event method definition is only necessary if the event type is used in the method. In the following example, <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> is used in the `ReportPointerLocation` method to set message text that reports the mouse coordinates when the user selects a button in the UI. - -`Pages/EventHandlerExample3.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor?highlight=17-20)] - -Supported <xref:System.EventArgs> are shown in the following table. - -| Event | Class | [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events and notes | -| ---------------- | ------ | --- | -| Clipboard | <xref:Microsoft.AspNetCore.Components.Web.ClipboardEventArgs> | `oncut`, `oncopy`, `onpaste` | -| Drag | <xref:Microsoft.AspNetCore.Components.Web.DragEventArgs> | `ondrag`, `ondragstart`, `ondragenter`, `ondragleave`, `ondragover`, `ondrop`, `ondragend`<br><br><xref:Microsoft.AspNetCore.Components.Web.DataTransfer> and <xref:Microsoft.AspNetCore.Components.Web.DataTransferItem> hold dragged item data.<br><br>Implement drag and drop in Blazor apps using [JS interop](xref:blazor/js-interop/call-javascript-from-dotnet) with [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API). | -| Error | <xref:Microsoft.AspNetCore.Components.Web.ErrorEventArgs> | `onerror` | -| Event | <xref:System.EventArgs> | *General*<br>`onactivate`, `onbeforeactivate`, `onbeforedeactivate`, `ondeactivate`, `onfullscreenchange`, `onfullscreenerror`, `onloadeddata`, `onloadedmetadata`, `onpointerlockchange`, `onpointerlockerror`, `onreadystatechange`, `onscroll`<br><br>*Clipboard*<br>`onbeforecut`, `onbeforecopy`, `onbeforepaste`<br><br>*Input*<br>`oninvalid`, `onreset`, `onselect`, `onselectionchange`, `onselectstart`, `onsubmit`<br><br>*Media*<br>`oncanplay`, `oncanplaythrough`, `oncuechange`, `ondurationchange`, `onemptied`, `onended`, `onpause`, `onplay`, `onplaying`, `onratechange`, `onseeked`, `onseeking`, `onstalled`, `onstop`, `onsuspend`, `ontimeupdate`, `ontoggle`, `onvolumechange`, `onwaiting`<br><br><xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. | -| Focus | <xref:Microsoft.AspNetCore.Components.Web.FocusEventArgs> | `onfocus`, `onblur`, `onfocusin`, `onfocusout`<br><br>Doesn't include support for `relatedTarget`. | -| Input | <xref:Microsoft.AspNetCore.Components.ChangeEventArgs> | `onchange`, `oninput` | -| Keyboard | <xref:Microsoft.AspNetCore.Components.Web.KeyboardEventArgs> | `onkeydown`, `onkeypress`, `onkeyup` | -| Mouse | <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> | `onclick`, `oncontextmenu`, `ondblclick`, `onmousedown`, `onmouseup`, `onmouseover`, `onmousemove`, `onmouseout` | -| Mouse pointer | <xref:Microsoft.AspNetCore.Components.Web.PointerEventArgs> | `onpointerdown`, `onpointerup`, `onpointercancel`, `onpointermove`, `onpointerover`, `onpointerout`, `onpointerenter`, `onpointerleave`, `ongotpointercapture`, `onlostpointercapture` | -| Mouse wheel | <xref:Microsoft.AspNetCore.Components.Web.WheelEventArgs> | `onwheel`, `onmousewheel` | -| Progress | <xref:Microsoft.AspNetCore.Components.Web.ProgressEventArgs> | `onabort`, `onload`, `onloadend`, `onloadstart`, `onprogress`, `ontimeout` | -| Touch | <xref:Microsoft.AspNetCore.Components.Web.TouchEventArgs> | `ontouchstart`, `ontouchend`, `ontouchmove`, `ontouchenter`, `ontouchleave`, `ontouchcancel`<br><br><xref:Microsoft.AspNetCore.Components.Web.TouchPoint> represents a single contact point on a touch-sensitive device. | - -For more information, see the following resources: - -* [`EventArgs` classes in the ASP.NET Core reference source (dotnet/aspnetcore `main` branch)](https://github.com/dotnet/aspnetcore/tree/main/src/Components/Web/src/Web) - - [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -* [MDN web docs: GlobalEventHandlers](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers): Includes information on which HTML elements support each DOM event. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/event-handling/Child.razor"::: -## Lambda expressions +:::moniker-end -[Lambda expressions](/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions) are supported as the delegate event handler. +:::moniker range="< aspnetcore-5.0" -`Pages/EventHandlerExample4.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/event-handling/Child.razor"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor?highlight=6)] +:::moniker-end -It's often convenient to close over additional values using C# method parameters, such as when iterating over a set of elements. The following example creates three buttons, each of which calls `UpdateHeading` and passes the following data: +The `Parent` component sets the child's <xref:Microsoft.AspNetCore.Components.EventCallback%601> (`OnClickCallback`) to its `ShowMessage` method. -* An event argument (<xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs>) in `e`. -* The button number in `buttonNumber`. +`Pages/Parent.razor`: -`Pages/EventHandlerExample5.razor`: +:::moniker range=">= aspnetcore-7.0" -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor?highlight=10,19)] +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor"::: -> [!NOTE] -> Do **not** use a loop variable directly in a lambda expression, such as `i` in the preceding `for` loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Always capture the variable's value in a local variable and then use it. In the preceding example: -> -> * The loop variable `i` is assigned to `buttonNumber`. -> * `buttonNumber` is used in the lambda expression. +:::moniker-end -Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see <xref:blazor/performance#avoid-recreating-delegates-for-many-repeated-elements-or-components>. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -## EventCallback +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor"::: -A common scenario with nested components executes a parent component's method when a child component event occurs. An `onclick` event occurring in the child component is a common use case. To expose events across components, use an <xref:Microsoft.AspNetCore.Components.EventCallback>. A parent component can assign a callback method to a child component's <xref:Microsoft.AspNetCore.Components.EventCallback>. +:::moniker-end -The following `Child` component demonstrates how a button's `onclick` handler is set up to receive an <xref:Microsoft.AspNetCore.Components.EventCallback> delegate from the sample's `ParentComponent`. The <xref:Microsoft.AspNetCore.Components.EventCallback> is typed with `MouseEventArgs`, which is appropriate for an `onclick` event from a peripheral device. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -`Shared/Child.razor`: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/event-handling/Child.razor)] +:::moniker-end -The `Parent` component sets the child's <xref:Microsoft.AspNetCore.Components.EventCallback%601> (`OnClickCallback`) to its `ShowMessage` method. +:::moniker range="< aspnetcore-5.0" -`Pages/Parent.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor)] +:::moniker-end When the button is selected in the `ChildComponent`: @@ -476,7 +499,29 @@ When a key is selected on an input device and the element focus is on a text box `Pages/EventHandlerExample6.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor?highlight=4)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor" highlight="4"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor" highlight="4"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor" highlight="4"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor" highlight="4"::: + +:::moniker-end Specifying the `@on{DOM EVENT}:preventDefault` attribute without a value is equivalent to `@on{DOM EVENT}:preventDefault="true"`. @@ -505,182 +550,48 @@ In the following example, selecting the checkbox prevents click events from the `Pages/EventHandlerExample7.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor?highlight=4,15-16)] - -## Focus an element - -Call <xref:Microsoft.AspNetCore.Components.ElementReferenceExtensions.FocusAsync%2A> on an [element reference](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements) to focus an element in code. In the following example, select the button to focus the `<input>` element. - -`Pages/EventHandlerExample8.razor`: +:::moniker range=">= aspnetcore-7.0" -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample8.razor?highlight=16)] +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: :::moniker-end -:::moniker range="< aspnetcore-5.0" - -Specify delegate event handlers in Razor component markup with [`@on{DOM EVENT}="{DELEGATE}"`](xref:mvc/views/razor#onevent) Razor syntax: - -* The `{DOM EVENT}` placeholder is a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events) (for example, `click`). -* The `{DELEGATE}` placeholder is the C# delegate event handler. - -For event handling: - -* Asynchronous delegate event handlers that return a <xref:System.Threading.Tasks.Task> are supported. -* Delegate event handlers automatically trigger a UI render, so there's no need to manually call [`StateHasChanged`](xref:blazor/components/lifecycle#state-changes-statehaschanged). -* Exceptions are logged. - -The following code: - -* Calls the `UpdateHeading` method when the button is selected in the UI. -* Calls the `CheckChanged` method when the checkbox is changed in the UI. - -`Pages/EventHandlerExample1.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor?highlight=10,17,27-30,32-35)] - -In the following example, `UpdateHeading`: - -* Is called asynchronously when the button is selected. -* Waits two seconds before updating the heading. - -`Pages/EventHandlerExample2.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor?highlight=10,19-24)] - -## Event arguments - -For events that support an event argument type, specifying an event parameter in the event method definition is only necessary if the event type is used in the method. In the following example, <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> is used in the `ReportPointerLocation` method to set message text that reports the mouse coordinates when the user selects a button in the UI. - -`Pages/EventHandlerExample3.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor?highlight=17-20)] - -Supported <xref:System.EventArgs> are shown in the following table. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -| Event | Class | [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events and notes | -| ---------------- | ----- | --- | -| Clipboard | <xref:Microsoft.AspNetCore.Components.Web.ClipboardEventArgs> | `oncut`, `oncopy`, `onpaste` | -| Drag | <xref:Microsoft.AspNetCore.Components.Web.DragEventArgs> | `ondrag`, `ondragstart`, `ondragenter`, `ondragleave`, `ondragover`, `ondrop`, `ondragend`<br><br><xref:Microsoft.AspNetCore.Components.Web.DataTransfer> and <xref:Microsoft.AspNetCore.Components.Web.DataTransferItem> hold dragged item data.<br><br>Implement drag and drop in Blazor apps using [JS interop](xref:blazor/js-interop/call-javascript-from-dotnet) with [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API). | -| Error | <xref:Microsoft.AspNetCore.Components.Web.ErrorEventArgs> | `onerror` | -| Event | <xref:System.EventArgs> | *General*<br>`onactivate`, `onbeforeactivate`, `onbeforedeactivate`, `ondeactivate`, `onfullscreenchange`, `onfullscreenerror`, `onloadeddata`, `onloadedmetadata`, `onpointerlockchange`, `onpointerlockerror`, `onreadystatechange`, `onscroll`<br><br>*Clipboard*<br>`onbeforecut`, `onbeforecopy`, `onbeforepaste`<br><br>*Input*<br>`oninvalid`, `onreset`, `onselect`, `onselectionchange`, `onselectstart`, `onsubmit`<br><br>*Media*<br>`oncanplay`, `oncanplaythrough`, `oncuechange`, `ondurationchange`, `onemptied`, `onended`, `onpause`, `onplay`, `onplaying`, `onratechange`, `onseeked`, `onseeking`, `onstalled`, `onstop`, `onsuspend`, `ontimeupdate`, `onvolumechange`, `onwaiting`<br><br><xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. | -| Focus | <xref:Microsoft.AspNetCore.Components.Web.FocusEventArgs> | `onfocus`, `onblur`, `onfocusin`, `onfocusout`<br><br>Doesn't include support for `relatedTarget`. | -| Input | <xref:Microsoft.AspNetCore.Components.ChangeEventArgs> | `onchange`, `oninput` | -| Keyboard | <xref:Microsoft.AspNetCore.Components.Web.KeyboardEventArgs> | `onkeydown`, `onkeypress`, `onkeyup` | -| Mouse | <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> | `onclick`, `oncontextmenu`, `ondblclick`, `onmousedown`, `onmouseup`, `onmouseover`, `onmousemove`, `onmouseout` | -| Mouse pointer | <xref:Microsoft.AspNetCore.Components.Web.PointerEventArgs> | `onpointerdown`, `onpointerup`, `onpointercancel`, `onpointermove`, `onpointerover`, `onpointerout`, `onpointerenter`, `onpointerleave`, `ongotpointercapture`, `onlostpointercapture` | -| Mouse wheel | <xref:Microsoft.AspNetCore.Components.Web.WheelEventArgs> | `onwheel`, `onmousewheel` | -| Progress | <xref:Microsoft.AspNetCore.Components.Web.ProgressEventArgs> | `onabort`, `onload`, `onloadend`, `onloadstart`, `onprogress`, `ontimeout` | -| Touch | <xref:Microsoft.AspNetCore.Components.Web.TouchEventArgs> | `ontouchstart`, `ontouchend`, `ontouchmove`, `ontouchenter`, `ontouchleave`, `ontouchcancel`<br><br><xref:Microsoft.AspNetCore.Components.Web.TouchPoint> represents a single contact point on a touch-sensitive device. | - -For more information, see the following resources: - -* [`EventArgs` classes in the ASP.NET Core reference source (dotnet/aspnetcore `main` branch)](https://github.com/dotnet/aspnetcore/tree/main/src/Components/Web/src/Web) - - [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -* [MDN web docs: GlobalEventHandlers](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers): Includes information on which HTML elements support each DOM event. - -## Lambda expressions - -[Lambda expressions](/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions) are supported as the delegate event handler. - -`Pages/EventHandlerExample4.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor?highlight=6)] - -It's often convenient to close over additional values using C# method parameters, such as when iterating over a set of elements. The following example creates three buttons, each of which calls `UpdateHeading` and passes the following data: - -* An event argument (<xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs>) in `e`. -* The button number in `buttonNumber`. - -`Pages/EventHandlerExample5.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor?highlight=10,19)] - -> [!NOTE] -> Do **not** use a loop variable directly in a lambda expression, such as `i` in the preceding `for` loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Always capture the variable's value in a local variable and then use it. In the preceding example: -> -> * The loop variable `i` is assigned to `buttonNumber`. -> * `buttonNumber` is used in the lambda expression. - -Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see <xref:blazor/performance#avoid-recreating-delegates-for-many-repeated-elements-or-components>. - -## EventCallback - -A common scenario with nested components executes a parent component's method when a child component event occurs. An `onclick` event occurring in the child component is a common use case. To expose events across components, use an <xref:Microsoft.AspNetCore.Components.EventCallback>. A parent component can assign a callback method to a child component's <xref:Microsoft.AspNetCore.Components.EventCallback>. - -The following `Child` component demonstrates how a button's `onclick` handler is set up to receive an <xref:Microsoft.AspNetCore.Components.EventCallback> delegate from the sample's `ParentComponent`. The <xref:Microsoft.AspNetCore.Components.EventCallback> is typed with `MouseEventArgs`, which is appropriate for an `onclick` event from a peripheral device. - -`Shared/Child.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/event-handling/Child.razor)] - -The `Parent` component sets the child's <xref:Microsoft.AspNetCore.Components.EventCallback%601> (`OnClickCallback`) to its `ShowMessage` method. - -`Pages/Parent.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor)] - -When the button is selected in the `ChildComponent`: - -* The `Parent` component's `ShowMessage` method is called. `message` is updated and displayed in the `Parent` component. -* A call to [`StateHasChanged`](xref:blazor/components/lifecycle#state-changes-statehaschanged) isn't required in the callback's method (`ShowMessage`). <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called automatically to rerender the `Parent` component, just as child events trigger component rerendering in event handlers that execute within the child. For more information, see <xref:blazor/components/rendering>. - -<xref:Microsoft.AspNetCore.Components.EventCallback> and <xref:Microsoft.AspNetCore.Components.EventCallback%601> permit asynchronous delegates. <xref:Microsoft.AspNetCore.Components.EventCallback> is weakly typed and allows passing any type argument in `InvokeAsync(Object)`. <xref:Microsoft.AspNetCore.Components.EventCallback%601> is strongly typed and requires passing a `T` argument in `InvokeAsync(T)` that's assignable to `TValue`. - -```razor -<ChildComponent - OnClickCallback="@(async () => { await Task.Yield(); messageText = "Blaze It!"; })" /> -``` - -Invoke an <xref:Microsoft.AspNetCore.Components.EventCallback> or <xref:Microsoft.AspNetCore.Components.EventCallback%601> with <xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A> and await the <xref:System.Threading.Tasks.Task>: - -```csharp -await OnClickCallback.InvokeAsync(arg); -``` - -Use <xref:Microsoft.AspNetCore.Components.EventCallback> and <xref:Microsoft.AspNetCore.Components.EventCallback%601> for event handling and binding component parameters. - -Prefer the strongly typed <xref:Microsoft.AspNetCore.Components.EventCallback%601> over <xref:Microsoft.AspNetCore.Components.EventCallback>. <xref:Microsoft.AspNetCore.Components.EventCallback%601> provides enhanced error feedback to users of the component. Similar to other UI event handlers, specifying the event parameter is optional. Use <xref:Microsoft.AspNetCore.Components.EventCallback> when there's no value passed to the callback. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: -## Prevent default actions +:::moniker-end -Use the [`@on{DOM EVENT}:preventDefault`](xref:mvc/views/razor#oneventpreventdefault) directive attribute to prevent the default action for an event, where the `{DOM EVENT}` placeholder is a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events). +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -When a key is selected on an input device and the element focus is on a text box, a browser normally displays the key's character in the text box. In the following example, the default behavior is prevented by specifying the `@onkeydown:preventDefault` directive attribute. When the focus is on the `<input>` element, the counter increments with the key sequence <kbd>Shift</kbd>+<kbd>+</kbd>. The `+` character isn't assigned to the `<input>` element's value. For more information on `keydown`, see [`MDN Web Docs: Document: keydown` event](https://developer.mozilla.org/docs/Web/API/Document/keydown_event). +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: -`Pages/EventHandlerExample6.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor?highlight=4)] +:::moniker range="< aspnetcore-5.0" -Specifying the `@on{DOM EVENT}:preventDefault` attribute without a value is equivalent to `@on{DOM EVENT}:preventDefault="true"`. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: -An expression is also a permitted value of the attribute. In the following example, `shouldPreventDefault` is a `bool` field set to either `true` or `false`: +:::moniker-end -```razor -<input @onkeydown:preventDefault="shouldPreventDefault" /> +:::moniker range=">= aspnetcore-6.0" -... +## Focus an element -@code { - private bool shouldPreventDefault = true; -} -``` +Call <xref:Microsoft.AspNetCore.Components.ElementReferenceExtensions.FocusAsync%2A> on an [element reference](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements) to focus an element in code. In the following example, select the button to focus the `<input>` element. -## Stop event propagation +`Pages/EventHandlerExample8.razor`: -Use the [`@on{DOM EVENT}:stopPropagation`](xref:mvc/views/razor#oneventstoppropagation) directive attribute to stop event propagation within the Blazor scope. `{DOM EVENT}` is a placeholder for a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events). +:::moniker-end -The `stopPropagation` directive attribute's effect is limited to the Blazor scope and doesn't extend to the HTML DOM. Events must propagate to the HTML DOM root before Blazor can act upon them. For a mechanism to prevent HTML DOM event propagation, consider the following approach: +:::moniker range=">= aspnetcore-7.0" -* Obtain the event's path by calling [`Event.composedPath()`](https://developer.mozilla.org/docs/Web/API/Event/composedPath). -* Filter events based on the composed [event targets (`EventTarget`)](https://developer.mozilla.org/docs/Web/API/EventTarget). +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample8.razor" highlight="16"::: -In the following example, selecting the checkbox prevents click events from the second child `<div>` from propagating to the parent `<div>`. Since propagated click events normally fire the `OnSelectParentDiv` method, selecting the second child `<div>` results in the parent `<div>` message appearing unless the checkbox is selected. +:::moniker-end -`Pages/EventHandlerExample7.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor?highlight=4,15-16)] +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample8.razor" highlight="16"::: :::moniker-end diff --git a/aspnetcore/blazor/components/generic-type-support.md b/aspnetcore/blazor/components/generic-type-support.md new file mode 100644 index 000000000000..6cb2bd2350e1 --- /dev/null +++ b/aspnetcore/blazor/components/generic-type-support.md @@ -0,0 +1,405 @@ +--- +title: ASP.NET Core Razor component generic type support +author: guardrex +description: Learn about generic type support in ASP.NET Core Razor components. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 05/09/2023 +uid: blazor/components/generic-type-support +--- +# ASP.NET Core Razor component generic type support + +[!INCLUDE[](~/includes/not-latest-version.md)] + +This article describes generic type support in Razor components. + +## Generic type parameter support + +The [`@typeparam`](xref:mvc/views/razor#typeparam) directive declares a [generic type parameter](/dotnet/csharp/programming-guide/generics/generic-type-parameters) for the generated component class: + +```razor +@typeparam TItem +``` + +C# syntax with [`where`](/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) type constraints is supported: + +```razor +@typeparam TEntity where TEntity : IEntity +``` + +In the following example, the `ListGenericTypeItems1` component is generically typed as `TExample`. + +`Shared/ListGenericTypeItems1.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/generic-type-support/ListGenericTypeItems1.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/generic-type-support/ListGenericTypeItems1.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/generic-type-support/ListGenericTypeItems1.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/generic-type-support/ListGenericTypeItems1.razor"::: + +:::moniker-end + +The following `GenericTypeExample1` component renders two `ListGenericTypeItems1` components: + +* String or integer data is assigned to the `ExampleList` parameter of each component. +* Type `string` or `int` that matches the type of the assigned data is set for the type parameter (`TExample`) of each component. + +`Pages/GenericTypeExample1.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/generic-type-support/GenericTypeExample1.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/generic-type-support/GenericTypeExample1.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/generic-type-support/GenericTypeExample1.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/generic-type-support/GenericTypeExample1.razor"::: + +:::moniker-end + +For more information, see <xref:mvc/views/razor#typeparam>. For an example of generic typing with templated components, see <xref:blazor/components/templated-components>. + +:::moniker range=">= aspnetcore-6.0" + +## Cascaded generic type support + +An ancestor component can cascade a type parameter by name to descendants using the [`[CascadingTypeParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute). This attribute allows a generic type inference to use the specified type parameter automatically with descendants that have a type parameter with the same name. + +By adding `@attribute [CascadingTypeParameter(...)]` to a component, the specified generic type argument is automatically used by descendants that: + +* Are nested as child content for the component in the same `.razor` document. +* Also declare a [`@typeparam`](xref:mvc/views/razor#typeparam) with the exact same name. +* Don't have another value explicitly supplied or implicitly inferred for the type parameter. If another value is supplied or inferred, it takes precedence over the cascaded generic type. + +When receiving a cascaded type parameter, components obtain the parameter value from the closest ancestor that has a <xref:Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute> with a matching name. Cascaded generic type parameters are overridden within a particular subtree. + +Matching is only performed by name. Therefore, we recommend avoiding a cascaded generic type parameter with a generic name, for example `T` or `TItem`. If a developer opts into cascading a type parameter, they're implicitly promising that its name is unique enough not to clash with other cascaded type parameters from unrelated components. + +Generic types can be cascaded to child components in either of the following approaches with ancestor (parent) components, which are demonstrated in the following two sub-sections: + +* Explicitly set the cascaded generic type. +* Infer the cascaded generic type. + +The following subsections provide examples of the preceding approaches using the following two `ListDisplay` components. The components receive and render list data and are generically typed as `TExample`. These components are for demonstration purposes and only differ in the color of text that the list is rendered. If you wish to experiment with the components in the following sub-sections in a local test app, add the following two components to the app first. + +`Shared/ListDisplay1.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:blue"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [Parameter] + public IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +`Shared/ListDisplay2.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:red"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [Parameter] + public IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +### Explicit generic types based on ancestor components + +The demonstration in this section cascades a type explicitly for `TExample`. + +> [!NOTE] +> This section uses the two `ListDisplay` components in the [Cascaded generic type support](#cascaded-generic-type-support) section. + +The following `ListGenericTypeItems2` component receives data and cascades a generic type parameter named `TExample` to its descendent components. In the upcoming parent component, the `ListGenericTypeItems2` component is used to display list data with the preceding `ListDisplay` component. + +`Shared/ListGenericTypeItems2.razor`: + +```razor +@attribute [CascadingTypeParameter(nameof(TExample))] +@typeparam TExample + +<h2>List Generic Type Items 2</h2> + +@ChildContent + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } +} +``` + +The following `GenericTypeExample2` parent component sets the child content (<xref:Microsoft.AspNetCore.Components.RenderFragment>) of two `ListGenericTypeItems2` components specifying the `ListGenericTypeItems2` types (`TExample`), which are cascaded to child components. `ListDisplay` components are rendered with the list item data shown in the example. String data is used with the first `ListGenericTypeItems2` component, and integer data is used with the second `ListGenericTypeItems2` component. + +`Pages/GenericTypeExample2.razor`: + +```razor +@page "/generic-type-example-2" + +<h1>Generic Type Example 2</h1> + +<ListGenericTypeItems2 TExample="string"> + <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" /> + <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" /> +</ListGenericTypeItems2> + +<ListGenericTypeItems2 TExample="int"> + <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" /> + <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" /> +</ListGenericTypeItems2> +``` + +Specifying the type explicitly also allows the use of [cascading values and parameters](xref:blazor/components/cascading-values-and-parameters) to provide data to child components, as the following demonstration shows. + +`Shared/ListDisplay3.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:blue"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [CascadingParameter] + protected IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +`Shared/ListDisplay4.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:red"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [CascadingParameter] + protected IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +`Shared/ListGenericTypeItems3.razor`: + +```razor +@attribute [CascadingTypeParameter(nameof(TExample))] +@typeparam TExample + +<h2>List Generic Type Items 3</h2> + +@ChildContent + +@if (ExampleList is not null) +{ + <ul style="color:green"> + @foreach(var item in ExampleList) + { + <li>@item</li> + } + </ul> + + <p> + Type of <code>TExample</code>: @typeof(TExample) + </p> +} + +@code { + [CascadingParameter] + protected IEnumerable<TExample>? ExampleList { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } +} +``` + +When cascading the data in the following example, the type must be provided to the `ListGenericTypeItems3` component. + +`Pages/GenericTypeExample3.razor`: + +```razor +@page "/generic-type-example-3" + +<h1>Generic Type Example 3</h1> + +<CascadingValue Value="@stringData"> + <ListGenericTypeItems3 TExample="string"> + <ListDisplay3 /> + <ListDisplay4 /> + </ListGenericTypeItems3> +</CascadingValue> + +<CascadingValue Value="@integerData"> + <ListGenericTypeItems3 TExample="int"> + <ListDisplay3 /> + <ListDisplay4 /> + </ListGenericTypeItems3> +</CascadingValue> + +@code { + private List<string> stringData = new() { "Item 1", "Item 2" }; + private List<int> integerData = new() { 1, 2, 3 }; +} +``` + +When multiple generic types are cascaded, values for all generic types in the set must be passed. In the following example, `TItem`, `TValue`, and `TEdit` are `GridColumn` generic types, but the parent component that places `GridColumn` doesn't specify the `TItem` type: + +```razor +<GridColumn TValue="string" TEdit="@TextEdit" /> +``` + +The preceding example generates a compile-time error that the `GridColumn` component is missing the `TItem` type parameter. Valid code specifies all of the types: + +```razor +<GridColumn TValue="string" TEdit="@TextEdit" TItem="@User" /> +``` + +### Infer generic types based on ancestor components + +The demonstration in this section cascades a type inferred for `TExample`. + +> [!NOTE] +> This section uses the two `ListDisplay` components in the [Cascaded generic type support](#cascaded-generic-type-support) section. + +`Shared/ListGenericTypeItems4.razor`: + +```razor +@attribute [CascadingTypeParameter(nameof(TExample))] +@typeparam TExample + +<h2>List Generic Type Items 4</h2> + +@ChildContent + +@if (ExampleList is not null) +{ + <ul style="color:green"> + @foreach(var item in ExampleList) + { + <li>@item</li> + } + </ul> + + <p> + Type of <code>TExample</code>: @typeof(TExample) + </p> +} + +@code { + [Parameter] + public IEnumerable<TExample>? ExampleList { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } +} +``` + +The following `GenericTypeExample4` component with inferred cascaded types provides different data for display. + +`Pages/GenericTypeExample4.razor`: + +```razor +@page "/generic-type-example-4" + +<h1>Generic Type Example 4</h1> + +<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })"> + <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" /> + <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" /> +</ListGenericTypeItems4> + +<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })"> + <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" /> + <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" /> +</ListGenericTypeItems4> +``` + +The following `GenericTypeExample5` component with inferred cascaded types provides the same data for display. The following example directly assigns the data to the components. + +`Pages/GenericTypeExample5.razor`: + +```razor +@page "/generic-type-example-5" + +<h1>Generic Type Example 5</h1> + +<ListGenericTypeItems4 ExampleList="@stringData"> + <ListDisplay1 ExampleList="@stringData" /> + <ListDisplay2 ExampleList="@stringData" /> +</ListGenericTypeItems4> + +<ListGenericTypeItems4 ExampleList="@integerData"> + <ListDisplay1 ExampleList="@integerData" /> + <ListDisplay2 ExampleList="@integerData" /> +</ListGenericTypeItems4> + +@code { + private List<string> stringData = new() { "Item 1", "Item 2" }; + private List<int> integerData = new() { 1, 2, 3 }; +} +``` + +:::moniker-end diff --git a/aspnetcore/blazor/components/index.md b/aspnetcore/blazor/components/index.md index 17efe3b1affa..56ae6587a1e0 100644 --- a/aspnetcore/blazor/components/index.md +++ b/aspnetcore/blazor/components/index.md @@ -5,14 +5,14 @@ description: Learn how to create and use Razor components in Blazor apps, includ monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 01/20/2023 uid: blazor/components/index --- # ASP.NET Core Razor components -This article explains how to create and use Razor components in Blazor apps, including guidance on Razor syntax, component naming, namespaces, and component parameters. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains how to create and use Razor components in Blazor apps, including guidance on Razor syntax, component naming, namespaces, and component parameters. Blazor apps are built using *Razor components*, informally known as *Blazor components*. A component is a self-contained portion of user interface (UI) with processing logic to enable dynamic behavior. Components can be nested, reused, shared among projects, and [used in MVC and Razor Pages apps](xref:blazor/components/prerendering-and-integration). @@ -20,6 +20,14 @@ Blazor apps are built using *Razor components*, informally known as *Blazor comp Components are implemented using a combination of C# and HTML markup in [Razor](xref:mvc/views/razor) component files with the `.razor` file extension. +By default, <xref:Microsoft.AspNetCore.Components.ComponentBase> is the base class for components described by Razor component files. <xref:Microsoft.AspNetCore.Components.ComponentBase> implements the lowest abstraction of components, the <xref:Microsoft.AspNetCore.Components.IComponent> interface. <xref:Microsoft.AspNetCore.Components.ComponentBase> defines component properties and methods for basic functionality, for example, to process a set of built-in component lifecycle events. + +[`ComponentBase` in `dotnet/aspnetcore` reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs): The reference source contains additional remarks on the built-in lifecycle events. However, keep in mind that the internal implementations of component features are subject to change at any time without notice. + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +Developers typically create Razor components from Razor component files (`.razor`) or base their components on <xref:Microsoft.AspNetCore.Components.ComponentBase>, but components can also be built by implementing <xref:Microsoft.AspNetCore.Components.IComponent>. Developer-built components that implement <xref:Microsoft.AspNetCore.Components.IComponent> can take low-level control over rendering at the cost of having to manually trigger rendering with events and lifecycle methods that the developer must create and maintain. + ### Razor syntax Components use [Razor syntax](xref:mvc/views/razor). Two Razor features are extensively used by components, *directives* and *directive attributes*. These are reserved keywords prefixed with `@` that appear in Razor markup: @@ -29,88 +37,24 @@ Components use [Razor syntax](xref:mvc/views/razor). Two Razor features are exte Directives and directive attributes used in components are explained further in this article and other articles of the Blazor documentation set. For general information on Razor syntax, see <xref:mvc/views/razor>. -### Names +### Component name, class name, and namespace A component's name must start with an uppercase character: -* `ProductDetail.razor` is valid. -* `productDetail.razor` is invalid. +<span aria-hidden="true">✔️</span><span class="visually-hidden">Supported:</span> `ProductDetail.razor` + +<span aria-hidden="true">❌</span><span class="visually-hidden">Unsupported:</span> `productDetail.razor` Common Blazor naming conventions used throughout the Blazor documentation include: * Component file paths use Pascal case† and appear before showing component code examples. Paths indicate typical folder locations. For example, `Pages/ProductDetail.razor` indicates that the `ProductDetail` component has a file name of `ProductDetail.razor` and resides in the `Pages` folder of the app. -* Component file paths for routable components match their URLs with hyphens appearing for spaces between words in a component's route template. For example, a `ProductDetail` component with a route template of `/product-detail` (`@page "/product-detail"`) is requested in a browser at the relative URL `/product-detail`. - -†Pascal case (upper camel case) is a naming convention without spaces and punctuation and with the first letter of each word capitalized, including the first word. - -### Routing - -Routing in Blazor is achieved by providing a route template to each accessible component in the app with an [`@page`][9] directive. When a Razor file with an [`@page`][9] directive is compiled, the generated class is given a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> specifying the route template. At runtime, the router searches for component classes with a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> and renders whichever component has a route template that matches the requested URL. - -The following `HelloWorld` component uses a route template of `/hello-world`. The rendered webpage for the component is reached at the relative URL `/hello-world`. When running a Blazor app locally with the default protocol, host, and port, the `HelloWorld` component is requested in the browser at `https://localhost:5001/hello-world`. Components that produce webpages usually reside in the `Pages` folder, but you can use any folder to hold components, including within nested folders. - -`Pages/HelloWorld.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: - -The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. Optionally, components can be added to the `NavMenu` component so that a link to the component appears in the app's UI-based navigation. - -For the preceding `HelloWorld` component, you can add a `NavLink` component to the `NavMenu` component in the `Shared` folder. For more information, including descriptions of the `NavLink` and `NavMenu` components, see <xref:blazor/fundamentals/routing>. - -### Markup - -A component's UI is defined using [Razor syntax](xref:mvc/views/razor), which consists of Razor markup, C#, and HTML. When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. The name of the generated class matches the name of the file. - -Members of the component class are defined in one or more [`@code`][1] blocks. In [`@code`][1] blocks, component state is specified and processed with C#: - -* Property and field initializers. -* Parameter values from arguments passed by parent components and route parameters. -* Methods for user event handling, lifecycle events, and custom component logic. +* Component file paths for routable components match their URLs in kebab case‡ with hyphens appearing between words in a component's route template. For example, a `ProductDetail` component with a route template of `/product-detail` (`@page "/product-detail"`) is requested in a browser at the relative URL `/product-detail`. -Component members are used in rendering logic using C# expressions that start with the `@` symbol. For example, a C# field is rendered by prefixing `@` to the field name. The following `Markup` component evaluates and renders: - -* `headingFontStyle` for the CSS property value `font-style` of the heading element. -* `headingText` for the content of the heading element. - -`Pages/Markup.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/Markup.razor"::: - -> [!NOTE] -> Examples throughout the Blazor documentation specify the [`private` access modifier](/dotnet/csharp/language-reference/keywords/private) for private members. Private members are scoped to a component's class. However, C# assumes the `private` access modifier when no access modifier is present, so explicitly marking members "`private`" in your own code is optional. For more information on access modifiers, see [Access Modifiers (C# Programming Guide)](/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers). - -The Blazor framework processes a component internally as a [*render tree*](https://developer.mozilla.org/docs/Web/Performance/How_browsers_work#render), which is the combination of a component's [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) and [Cascading Style Sheet Object Model (CSSOM)](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model). After the component is initially rendered, the component's render tree is regenerated in response to events. Blazor compares the new render tree against the previous render tree and applies any modifications to the browser's DOM for display. For more information, see <xref:blazor/components/rendering>. +†Pascal case (upper camel case) is a naming convention without spaces and punctuation and with the first letter of each word capitalized, including the first word. +‡kebab case is a naming convention without spaces and punctuation that uses lowercase letters and dashes between words. Components are ordinary [C# classes](/dotnet/csharp/programming-guide/classes-and-structs/classes) and can be placed anywhere within a project. Components that produce webpages usually reside in the `Pages` folder. Non-page components are frequently placed in the `Shared` folder or a custom folder added to the project. -Razor syntax for C# control structures, directives, and directive attributes are lowercase (examples: [`@if`](xref:mvc/views/razor#conditionals-if-else-if-else-and-switch), [`@code`](xref:mvc/views/razor#code), [`@bind`](xref:mvc/views/razor#bind)). Property names are uppercase (example: `@Body` for <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body?displayProperty=nameWithType>). - -### Asynchronous methods (`async`) don't support returning `void` - -The Blazor framework doesn't track `void`-returning asynchronous methods (`async`). As a result, exceptions aren't caught if `void` is returned. Always return a <xref:System.Threading.Tasks.Task> from asynchronous methods. - -### Nested components - -Components can include other components by declaring them using HTML syntax. The markup for using a component looks like an HTML tag where the name of the tag is the component type. - -Consider the following `Heading` component, which can be used by other components to display a heading. - -`Shared/Heading.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/Heading.razor"::: - -The following markup in the `HeadingExample` component renders the preceding `Heading` component at the location where the `<Heading />` tag appears. - -`Pages/HeadingExample.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor"::: - -If a component contains an HTML element with an uppercase first letter that doesn't match a component name within the same namespace, a warning is emitted indicating that the element has an unexpected name. Adding an [`@using`][2] directive for the component's namespace makes the component available, which resolves the warning. For more information, see the [Namespaces](#namespaces) section. - -The `Heading` component example shown in this section doesn't have an [`@page`][9] directive, so the `Heading` component isn't directly accessible to a user via a direct request in the browser. However, any component with an [`@page`][9] directive can be nested in another component. If the `Heading` component was directly accessible by including `@page "/heading"` at the top of its Razor file, then the component would be rendered for browser requests at both `/heading` and `/heading-example`. - -### Namespaces - Typically, a component's namespace is derived from the app's root namespace and the component's location (folder) within the app. If the app's root namespace is `BlazorSample` and the `Counter` component resides in the `Pages` folder: * The `Counter` component's namespace is `BlazorSample.Pages`. @@ -152,23 +96,91 @@ Components are generated as [C# partial classes](/dotnet/csharp/programming-guid * A single file contains C# code defined in one or more [`@code`][1] blocks, HTML markup, and Razor markup. Blazor project templates define their components using this single-file approach. * HTML and Razor markup are placed in a Razor file (`.razor`). C# code is placed in a code-behind file defined as a partial class (`.cs`). +:::moniker range=">= aspnetcore-5.0" + > [!NOTE] > A component stylesheet that defines component-specific styles is a separate file (`.css`). Blazor CSS isolation is described later in <xref:blazor/components/css-isolation>. +:::moniker-end + The following example shows the default `Counter` component with an [`@code`][1] block in an app generated from a Blazor project template. Markup and C# code are in the same file. This is the most common approach taken in component authoring. `Pages/Counter.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/Counter.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/Counter.razor"::: -The following `Counter` component splits HTML and Razor markup from C# code using a code-behind file with a partial class: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/Counter.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/Counter.razor"::: + +:::moniker-end + +The following `Counter` component splits presentation HTML and Razor markup from the C# code using a code-behind file with a partial class. Splitting the markup from the C# code is favored by some organizations and developers to organize their component code to suit how they prefer to work. For example, the organization's UI expert can work on the presentation layer independently of another developer working on the component's C# logic. The approach is also useful when working with automatically-generated code or source generators. For more information, see [Partial Classes and Methods (C# Programming Guide)](/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods). `Pages/CounterPartialClass.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/CounterPartialClass.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/CounterPartialClass.razor"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/CounterPartialClass.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/CounterPartialClass.razor"::: + +:::moniker-end + `Pages/CounterPartialClass.razor.cs`: +:::moniker range=">= aspnetcore-6.0" + +```csharp +namespace BlazorSample.Pages; + +public partial class CounterPartialClass +{ + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + ```csharp namespace BlazorSample.Pages { @@ -176,7 +188,7 @@ namespace BlazorSample.Pages { private int currentCount = 0; - void IncrementCount() + private void IncrementCount() { currentCount++; } @@ -184,10 +196,14 @@ namespace BlazorSample.Pages } ``` +:::moniker-end + [`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). Add namespaces to a partial class file as needed. Typical namespaces used by components: +:::moniker range=">= aspnetcore-6.0" + ```csharp using System.Net.Http; using Microsoft.AspNetCore.Authorization; @@ -199,6 +215,20 @@ using Microsoft.AspNetCore.Components.Web.Virtualization; using Microsoft.JSInterop; ``` +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +```csharp +using System.Net.Http; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; +``` + +:::moniker-end + Typical namespaces also include the namespace of the app and the namespace corresponding to the app's `Shared` folder: ```csharp @@ -208,2382 +238,144 @@ using BlazorSample.Shared; ### Specify a base class -The [`@inherits`][6] directive is used to specify a base class for a component. The following example shows how a component can inherit a base class to provide the component's properties and methods. The `BlazorRocksBase` base class derives from <xref:Microsoft.AspNetCore.Components.ComponentBase>. +The [`@inherits`][6] directive is used to specify a base class for a component. Unlike using [partial classes](#partial-class-support), which only split markup from C# logic, using a base class allows you to inherit C# code for use across a group of components that share the base class's properties and methods. Using base classes reduce code redundancy in apps and are useful when supplying base code from class libraries to multiple apps. For more information, see [Inheritance in C# and .NET](/dotnet/csharp/fundamentals/tutorials/inheritance). -`Pages/BlazorRocks.razor`: +In the following example, the `BlazorRocksBase` base class derives from <xref:Microsoft.AspNetCore.Components.ComponentBase>. -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor"::: +`Pages/BlazorRocks.razor`: -`BlazorRocksBase.cs`: +:::moniker range=">= aspnetcore-7.0" -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/BlazorRocksBase.cs"::: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor"::: -## Component parameters +:::moniker-end -*Component parameters* pass data to components and are defined using public [C# properties](/dotnet/csharp/programming-guide/classes-and-structs/properties) on the component class with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). In the following example, a built-in reference type (<xref:System.String?displayProperty=fullName>) and a user-defined reference type (`PanelBody`) are passed as component parameters. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`PanelBody.cs`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor"::: -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/PanelBody.cs"::: +:::moniker-end -`Shared/ParameterChild.razor`: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor"::: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor"::: -> [!WARNING] -> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. +:::moniker-end -The `Title` and `Body` component parameters of the `ParameterChild` component are set by arguments in the HTML tag that renders the instance of the component. The following `ParameterParent` component renders two `ParameterChild` components: +:::moniker range="< aspnetcore-5.0" -* The first `ParameterChild` component is rendered without supplying parameter arguments. -* The second `ParameterChild` component receives values for `Title` and `Body` from the `ParameterParent` component, which uses an [explicit C# expression](xref:mvc/views/razor#explicit-razor-expressions) to set the values of the `PanelBody`'s properties. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor"::: -`Pages/ParameterParent.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor"::: +`BlazorRocksBase.cs`: -The following rendered HTML markup from the `ParameterParent` component shows `ParameterChild` component default values when the `ParameterParent` component doesn't supply component parameter values. When the `ParameterParent` component provides component parameter values, they replace the `ParameterChild` component's default values. +:::moniker range=">= aspnetcore-7.0" -> [!NOTE] -> For clarity, rendered CSS style classes aren't shown in the following rendered HTML markup. +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/BlazorRocksBase.cs"::: -```html -<h1>Child component (without attribute values)</h1> +:::moniker-end -<div> - <div>Set By Child</div> - <div>Set by child.</div> -</div> +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -<h1>Child component (with attribute values)</h1> +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/BlazorRocksBase.cs"::: -<div> - <div>Set by Parent</div> - <div>Set by parent.</div> -</div> -``` +:::moniker-end -Assign a C# field, property, or result of a method to a component parameter as an HTML attribute value using [Razor's reserved `@` symbol](xref:mvc/views/razor#razor-syntax). The following `ParameterParent2` component displays four instances of the preceding `ParameterChild` component and sets their `Title` parameter values to: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* The value of the `title` field. -* The result of the `GetTitle` C# method. -* The current local date in long format with <xref:System.DateTime.ToLongDateString%2A>, which uses an [implicit C# expression](xref:mvc/views/razor#implicit-razor-expressions). -* The `panelData` object's `Title` property. +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/BlazorRocksBase.cs"::: -The `@` prefix is required for string parameters. Otherwise, the framework assumes that a string literal is set. +:::moniker-end -Outside of string parameters, we recommend use the use of the `@` prefix for nonliterals, even when they aren't strictly required. +:::moniker range="< aspnetcore-5.0" -We don't recommend the use of the `@` prefix for literals (for example, boolean values), keywords (for example, `this`), or `null`, but you can choose to use them if you wish. For example, `IsFixed="@true"` is uncommon but supported. +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/BlazorRocksBase.cs"::: -Quotes around parameter attribute values are optional in most cases per the HTML5 specification. For example, `Value=this` is supported, instead of `Value="this"`. However, we recommend using quotes because it's easier to remember and widely adopted across web-based technologies. +:::moniker-end -Throughout the documentation, code examples: +### Routing -* Always use quotes. Example: `Value="this"`. -* Nonliterals always use the `@` prefix, even when it's optional. Examples: `Title="@title"`, where `title` is a string-typed variable. `Count="@ct"`, where `ct` is a number-typed variable. -* Literals, outside of Razor expressions, always avoid `@`. Example: `IsFixed="true"`. +Routing in Blazor is achieved by providing a route template to each accessible component in the app with an [`@page`][9] directive. When a Razor file with an [`@page`][9] directive is compiled, the generated class is given a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> specifying the route template. At runtime, the router searches for component classes with a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> and renders whichever component has a route template that matches the requested URL. -`Pages/ParameterParent2.razor`: +The following `HelloWorld` component uses a route template of `/hello-world`, and the rendered webpage for the component is reached at the relative URL `/hello-world`. Components that produce webpages usually reside in the `Pages` folder, but you can use any folder to hold components, including within nested folders. -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor"::: +`Pages/HelloWorld.razor`: -> [!NOTE] -> When assigning a C# member to a component parameter, prefix the member with the `@` symbol and never prefix the parameter's HTML attribute. -> -> Correct: -> -> ```razor -> <ParameterChild Title="@title" /> -> ``` -> -> Incorrect: -> -> ```razor -> <ParameterChild @Title="title" /> -> ``` +:::moniker range=">= aspnetcore-7.0" -Unlike in Razor pages (`.cshtml`), Blazor can't perform asynchronous work in a Razor expression while rendering a component. This is because Blazor is designed for rendering interactive UIs. In an interactive UI, the screen must always display something, so it doesn't make sense to block the rendering flow. Instead, asynchronous work is performed during one of the [asynchronous lifecycle events](xref:blazor/components/lifecycle). After each asynchronous lifecycle event, the component may render again. The following Razor syntax is **not** supported: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: -```razor -<ParameterChild Title="@await ..." /> -``` +:::moniker-end -The code in the preceding example generates a *compiler error* when the app is built: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -> The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: -To obtain a value for the `Title` parameter in the preceding example asynchronously, the component can use the [`OnInitializedAsync` lifecycle event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync), as the following example demonstrates: +:::moniker-end -```razor -<ParameterChild Title="@title" /> +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -@code { - private string? title; - - protected override async Task OnInitializedAsync() - { - title = await ...; - } -} -``` +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: -For more information, see <xref:blazor/components/lifecycle>. +:::moniker-end -Use of an explicit Razor expression to concatenate text with an expression result for assignment to a parameter is **not** supported. The following example seeks to concatenate the text "`Set by `" with an object's property value. Although this syntax is supported in a Razor page (`.cshtml`), it isn't valid for assignment to the child's `Title` parameter in a component. The following Razor syntax is **not** supported: +:::moniker range="< aspnetcore-5.0" -```razor -<ParameterChild Title="Set by @(panelData.Title)" /> -``` +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: -The code in the preceding example generates a *compiler error* when the app is built: +:::moniker-end -> Component attributes do not support complex content (mixed C# and markup). +The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. Optionally, components can be added to the `NavMenu` component so that a link to the component appears in the app's UI-based navigation. -To support the assignment of a composed value, use a method, field, or property. The following example performs the concatenation of "`Set by `" and an object's property value in the C# method `GetTitle`: +For the preceding `HelloWorld` component, you can add a `NavLink` component to the `NavMenu` component in the `Shared` folder. For more information, including descriptions of the `NavLink` and `NavMenu` components, see <xref:blazor/fundamentals/routing>. -`Pages/ParameterParent3.razor`: +### Markup -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor"::: +A component's UI is defined using [Razor syntax](xref:mvc/views/razor), which consists of Razor markup, C#, and HTML. When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. The name of the generated class matches the name of the file. -For more information, see <xref:mvc/views/razor>. +Members of the component class are defined in one or more [`@code`][1] blocks. In [`@code`][1] blocks, component state is specified and processed with C#: -> [!WARNING] -> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. +* Property and field initializers. +* Parameter values from arguments passed by parent components and route parameters. +* Methods for user event handling, lifecycle events, and custom component logic. -Component parameters should be declared as *auto-properties*, meaning that they shouldn't contain custom logic in their `get` or `set` accessors. For example, the following `StartData` property is an auto-property: +Component members are used in rendering logic using C# expressions that start with the `@` symbol. For example, a C# field is rendered by prefixing `@` to the field name. The following `Markup` component evaluates and renders: -```csharp -[Parameter] -public DateTime StartData { get; set; } -``` - -Don't place custom logic in the `get` or `set` accessor because component parameters are purely intended for use as a channel for a parent component to flow information to a child component. If a `set` accessor of a child component property contains logic that causes rerendering of the parent component, an infinite rendering loop results. - -To transform a received parameter value: - -* Leave the parameter property as an auto-property to represent the supplied raw data. -* Create a different property or method to supply the transformed data based on the parameter property. - -Override [`OnParametersSetAsync`](xref:blazor/components/lifecycle#after-parameters-are-set-onparameterssetasync) to transform a received parameter each time new data is received. - -Writing an initial value to a component parameter is supported because initial value assignments don't interfere with the Blazor's automatic component rendering. The following assignment of the current local <xref:System.DateTime> with <xref:System.DateTime.Now?displayProperty=nameWithType> to `StartData` is valid syntax in a component: - -```csharp -[Parameter] -public DateTime StartData { get; set; } = DateTime.Now; -``` - -After the initial assignment of <xref:System.DateTime.Now?displayProperty=nameWithType>, do **not** assign a value to `StartData` in developer code. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. - -Apply the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) to specify a required component parameter. If a parameter value isn't provided, editors or build tools may display warnings to the user. This attribute is only valid on properties also marked with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). The <xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute> is enforced at design-time and when the app is built. The attribute isn't enforced at runtime, and it doesn't guarantee a non-`null` parameter value. - -```csharp -[Parameter] -[EditorRequired] -public string? Title { get; set; } -``` - -Single-line attribute lists are also supported: - -```csharp -[Parameter, EditorRequired] -public string? Title { get; set; } -``` - -[`Tuples`](/dotnet/csharp/language-reference/builtin-types/value-tuples) ([API documentation](xref:System.Tuple)) are supported for component parameters and [`RenderFragment`](#child-content-render-fragments) types. The following component parameter example passes three values in a `Tuple`: - -`Shared/RenderTupleChild.razor`: - -```csharp -<div class="card w-50" style="margin-bottom:15px"> - <div class="card-header font-weight-bold"><code>Tuple</code> Card</div> - <div class="card-body"> - <ul> - <li>Integer: @Data?.Item1</li> - <li>String: @Data?.Item2</li> - <li>Boolean: @Data?.Item3</li> - </ul> - </div> -</div> - -@code { - [Parameter] - public Tuple<int, string, bool>? Data { get; set; } -} -``` - -`Pages/RenderTupleParent.razor`: - -```csharp -@page "/render-tuple-parent" - -<h1>Render <code>Tuple</code> Parent</h1> - -<RenderTupleChild Data="@data" /> - -@code { - private Tuple<int, string, bool> data = new(999, "I aim to misbehave.", true); -} -``` - -Only ***unnamed tuples*** are supported for C# 7.0 or later in Razor components. [Named tuples](/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-field-names) support in Razor components is planned for a future ASP.NET Core release. For more information, see [Blazor Transpiler issue with named Tuples (dotnet/aspnetcore #28982)](https://github.com/dotnet/aspnetcore/issues/28982). - -Quote ©2005 [Universal Pictures](https://www.uphe.com): [Serenity](https://www.uphe.com/movies/serenity-2005) ([Nathan Fillion](https://www.imdb.com/name/nm0277213/)) - -## Route parameters - -Components can specify route parameters in the route template of the [`@page`][9] directive. The [Blazor router](xref:blazor/fundamentals/routing) uses route parameters to populate corresponding component parameters. - -Optional route parameters are supported. In the following example, the `text` optional parameter assigns the value of the route segment to the component's `Text` property. If the segment isn't present, the value of `Text` is set to "`fantastic`" in the [`OnInitialized` lifecycle method](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). - -`Pages/RouteParameter.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor" highlight="1,6-7"::: - -For information on catch-all route parameters (`{*pageRoute}`), which capture paths across multiple folder boundaries, see <xref:blazor/fundamentals/routing#catch-all-route-parameters>. - -## Child content render fragments - -Components can set the content of another component. The assigning component provides the content between the child component's opening and closing tags. - -In the following example, the `RenderFragmentChild` component has a `ChildContent` component parameter that represents a segment of the UI to render as a <xref:Microsoft.AspNetCore.Components.RenderFragment>. The position of `ChildContent` in the component's Razor markup is where the content is rendered in the final HTML output. - -`Shared/RenderFragmentChild.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor"::: - -> [!IMPORTANT] -> The property receiving the <xref:Microsoft.AspNetCore.Components.RenderFragment> content must be named `ChildContent` by convention. -> -> [Event callbacks](xref:blazor/components/event-handling#eventcallback) aren't supported for <xref:Microsoft.AspNetCore.Components.RenderFragment>. - -The following `RenderFragmentParent` component provides content for rendering the `RenderFragmentChild` by placing the content inside the child component's opening and closing tags. - -`Pages/RenderFragmentParent.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor"::: - -Due to the way that Blazor renders child content, rendering components inside a [`for`](/dotnet/csharp/language-reference/keywords/for) loop requires a local index variable if the incrementing loop variable is used in the `RenderFragmentChild` component's content. The following example can be added to the preceding `RenderFragmentParent` component: - -```razor -<h1>Three children with an index variable</h1> - -@for (int c = 0; c < 3; c++) -{ - var current = c; - - <RenderFragmentChild> - Count: @current - </RenderFragmentChild> -} -``` - -Alternatively, use a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop with <xref:System.Linq.Enumerable.Range%2A?displayProperty=nameWithType> instead of a [`for`](/dotnet/csharp/language-reference/keywords/for) loop. The following example can be added to the preceding `RenderFragmentParent` component: - -```razor -<h1>Second example of three children with an index variable</h1> - -@foreach (var c in Enumerable.Range(0,3)) -{ - <RenderFragmentChild> - Count: @c - </RenderFragmentChild> -} -``` - -Render fragments are used to render child content throughout Blazor apps and are described with examples in the following articles and article sections: - -* [Blazor layouts](xref:blazor/components/layouts) -* [Pass data across a component hierarchy](xref:blazor/components/cascading-values-and-parameters#pass-data-across-a-component-hierarchy) -* [Templated components](xref:blazor/components/templated-components) -* [Global exception handling](xref:blazor/fundamentals/handle-errors#global-exception-handling) - -> [!NOTE] -> Blazor framework's [built-in Razor components](xref:blazor/components/built-in-components) use the same `ChildContent` component parameter convention to set their content. You can see the components that set child content by searching for the component parameter property name `ChildContent` in the [API documentation (filters API with the search term "ChildContent")](/dotnet/api/?term=ChildContent). - -## Render fragments for reusable rendering logic - -You can factor out child components purely as a way of reusing rendering logic. In any component's `@code` block, define a <xref:Microsoft.AspNetCore.Components.RenderFragment> and render the fragment from any location as many times as needed: - -```razor -<h1>Hello, world!</h1> - -@RenderWelcomeInfo - -<p>Render the welcome info a second time:</p> - -@RenderWelcomeInfo - -@code { - private RenderFragment RenderWelcomeInfo = __builder => - { - <p>Welcome to your new app!</p> - }; -} -``` - -For more information, see [Reuse rendering logic](xref:blazor/performance#define-reusable-renderfragments-in-code). - -## Overwritten parameters - -The Blazor framework generally imposes safe parent-to-child parameter assignment: - -* Parameters aren't overwritten unexpectedly. -* Side effects are minimized. For example, additional renders are avoided because they may create infinite rendering loops. - -A child component receives new parameter values that possibly overwrite existing values when the parent component rerenders. Accidentally overwriting parameter values in a child component often occurs when developing the component with one or more data-bound parameters and the developer writes directly to a parameter in the child: - -* The child component is rendered with one or more parameter values from the parent component. -* The child writes directly to the value of a parameter. -* The parent component rerenders and overwrites the value of the child's parameter. - -The potential for overwriting parameter values extends into the child component's property `set` accessors, too. - -> [!IMPORTANT] -> Our general guidance is not to create components that directly write to their own parameters after the component is rendered for the first time. - -Consider the following `Expander` component that: - -* Renders child content. -* Toggles showing child content with a component parameter (`Expanded`). - -After the following `Expander` component demonstrates an overwritten parameter, a modified `Expander` component is shown to demonstrate the correct approach for this scenario. The following examples can be placed in a local sample app to experience the behaviors described. - -`Shared/Expander.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/BadExpander.razor"::: - -The `Expander` component is added to the following `ExpanderExample` parent component that may call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>: - -* Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in developer code notifies a component that its state has changed and typically triggers component rerendering to update the UI. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is covered in more detail later in <xref:blazor/components/lifecycle> and <xref:blazor/components/rendering>. -* The button's `@onclick` directive attribute attaches an event handler to the button's `onclick` event. Event handling is covered in more detail later in <xref:blazor/components/event-handling>. - -`Pages/ExpanderExample.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ExpanderExample.razor"::: - -Initially, the `Expander` components behave independently when their `Expanded` properties are toggled. The child components maintain their states as expected. - -If <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called in a parent component, the Blazor framework rerenders child components if their parameters might have changed: - -* For a group of parameter types that Blazor explicitly checks, Blazor rerenders a child component if it detects that any of the parameters have changed. -* For unchecked parameter types, Blazor rerenders the child component *regardless of whether or not the parameters have changed*. Child content falls into this category of parameter types because child content is of type <xref:Microsoft.AspNetCore.Components.RenderFragment>, which is a delegate that refers to other mutable objects. - -For the `ExpanderExample` component: - -* The first `Expander` component sets child content in a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment>, so a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component automatically rerenders the component and potentially overwrites the value of `Expanded` to its intitial value of `true`. -* The second `Expander` component doesn't set child content. Therefore, a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment> doesn't exist. A call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component doesn't automatically rerender the child component, so the component's `Expanded` value isn't overwritten. - -To maintain state in the preceding scenario, use a *private field* in the `Expander` component to maintain its toggled state. - -The following revised `Expander` component: - -* Accepts the `Expanded` component parameter value from the parent. -* Assigns the component parameter value to a *private field* (`expanded`) in the [`OnInitialized` event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). -* Uses the private field to maintain its internal toggle state, which demonstrates how to avoid writing directly to a parameter. - -> [!NOTE] -> The advice in this section extends to similar logic in component parameter `set` accessors, which can result in similar undesirable side effects. - -`Shared/Expander.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/Expander.razor"::: - -For two-way parent-child binding examples, see <xref:blazor/components/data-binding#binding-with-component-parameters>. For additional information, see [Blazor Two Way Binding Error (dotnet/aspnetcore #24599)](https://github.com/dotnet/aspnetcore/issues/24599). - -For more information on change detection, inlcuding information on the exact types that Blazor checks, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. - -## Attribute splatting and arbitrary parameters - -Components can capture and render additional attributes in addition to the component's declared parameters. Additional attributes can be captured in a dictionary and then *splatted* onto an element when the component is rendered using the [`@attributes`][3] Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations. For example, it can be tedious to define attributes separately for an `<input>` that supports many parameters. - -In the following `Splat` component: - -* The first `<input>` element (`id="useIndividualParams"`) uses individual component parameters. -* The second `<input>` element (`id="useAttributesDict"`) uses attribute splatting. - -`Pages/Splat.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/Splat.razor"::: - -The rendered `<input>` elements in the webpage are identical: - -```html -<input id="useIndividualParams" - maxlength="10" - placeholder="Input placeholder text" - required="required" - size="50"> - -<input id="useAttributesDict" - maxlength="10" - placeholder="Input placeholder text" - required="required" - size="50"> -``` - -To accept arbitrary attributes, define a [component parameter](#component-parameters) with the <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property set to `true`: - -```razor -@code { - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary<string, object>? InputAttributes { get; set; } -} -``` - -The <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property on [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) allows the parameter to match all attributes that don't match any other parameter. A component can only define a single parameter with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues>. The property type used with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> must be assignable from [`Dictionary<string, object>`](xref:System.Collections.Generic.Dictionary%602) with string keys. Use of [`IEnumerable<KeyValuePair<string, object>>`](xref:System.Collections.Generic.IEnumerable%601) or [`IReadOnlyDictionary<string, object>`](xref:System.Collections.Generic.IReadOnlyDictionary%602) are also options in this scenario. - -The position of [`@attributes`][3] relative to the position of element attributes is important. When [`@attributes`][3] are splatted on the element, the attributes are processed from right to left (last to first). Consider the following example of a parent component that consumes a child component: - -`Shared/AttributeOrderChild1.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild1.razor"::: - -`Pages/AttributeOrderParent1.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent1.razor"::: - -The `AttributeOrderChild1` component's `extra` attribute is set to the right of [`@attributes`][3]. The `AttributeOrderParent1` component's rendered `<div>` contains `extra="5"` when passed through the additional attribute because the attributes are processed right to left (last to first): - -```html -<div extra="5" /> -``` - -In the following example, the order of `extra` and [`@attributes`][3] is reversed in the child component's `<div>`: - -`Shared/AttributeOrderChild2.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild2.razor"::: - -`Pages/AttributeOrderParent2.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent2.razor"::: - -The `<div>` in the parent component's rendered webpage contains `extra="10"` when passed through the additional attribute: - -```html -<div extra="10" /> -``` - -## Capture references to components - -Component references provide a way to reference a component instance for issuing commands. To capture a component reference: - -* Add an [`@ref`][4] attribute to the child component. -* Define a field with the same type as the child component. - -When the component is rendered, the field is populated with the component instance. You can then invoke .NET methods on the instance. - -Consider the following `ReferenceChild` component that logs a message when its `ChildMethod` is called. - -`Shared/ReferenceChild.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor"::: - -A component reference is only populated after the component is rendered and its output includes `ReferenceChild`'s element. Until the component is rendered, there's nothing to reference. - -To manipulate component references after the component has finished rendering, use the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). - -To use a reference variable with an event handler, use a lambda expression or assign the event handler delegate in the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). This ensures that the reference variable is assigned before the event handler is assigned. - -The following lambda approach uses the preceding `ReferenceChild` component. - -`Pages/ReferenceParent1.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor"::: - -The following delegate approach uses the preceding `ReferenceChild` component. - -`Pages/ReferenceParent2.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor"::: - -While capturing component references use a similar syntax to [capturing element references](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements), capturing component references isn't a JavaScript interop feature. Component references aren't passed to JavaScript code. Component references are only used in .NET code. - -> [!IMPORTANT] -> Do **not** use component references to mutate the state of child components. Instead, use normal declarative component parameters to pass data to child components. Use of component parameters result in child components that rerender at the correct times automatically. For more information, see the [component parameters](#component-parameters) section and the <xref:blazor/components/data-binding> article. - -## Synchronization context - -Blazor uses a synchronization context (<xref:System.Threading.SynchronizationContext>) to enforce a single logical thread of execution. A component's [lifecycle methods](xref:blazor/components/lifecycle) and event callbacks raised by Blazor are executed on the synchronization context. - -Blazor Server's synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. At any given point in time, work is performed on exactly one thread, which yields the impression of a single logical thread. No two operations execute concurrently. - -### Avoid thread-blocking calls - -Generally, don't call the following methods in components. The following methods block the execution thread and thus block the app from resuming work until the underlying <xref:System.Threading.Tasks.Task> is complete: - -* <xref:System.Threading.Tasks.Task%601.Result%2A> -* <xref:System.Threading.Tasks.Task.Wait%2A> -* <xref:System.Threading.Tasks.Task.WaitAny%2A> -* <xref:System.Threading.Tasks.Task.WaitAll%2A> -* <xref:System.Threading.Thread.Sleep%2A> -* <xref:System.Runtime.CompilerServices.TaskAwaiter.GetResult%2A> - -> [!NOTE] -> Blazor documentation examples that use the thread-blocking methods mentioned in this section are only using the methods for demonstration purposes, not as recommended coding guidance. For example, a few component code demonstrations simulate a long-running process by calling <xref:System.Threading.Thread.Sleep%2A?displayProperty=nameWithType>. - -### Invoke component methods externally to update state - -In the event a component must be updated based on an external event, such as a timer or other notification, use the `InvokeAsync` method, which dispatches code execution to Blazor's synchronization context. For example, consider the following *notifier service* that can notify any listening component about updated state. The `Update` method can be called from anywhere in the app. - -`TimerService.cs`: - -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/TimerService.cs"::: - -`NotifierService.cs`: - -:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/NotifierService.cs"::: - -Register the services: - -* In a Blazor WebAssembly app, register the services as singletons in `Program.cs`: - - ```csharp - builder.Services.AddSingleton<NotifierService>(); - builder.Services.AddSingleton<TimerService>(); - ``` - -* In a Blazor Server app, register the services as scoped in `Program.cs`: - - ```csharp - builder.Services.AddScoped<NotifierService>(); - builder.Services.AddScoped<TimerService>(); - ``` - -Use the `NotifierService` to update a component. - -`Pages/ReceiveNotifications.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ReceiveNotifications.razor"::: - -In the preceding example: - -* `NotifierService` invokes the component's `OnNotify` method outside of Blazor's synchronization context. `InvokeAsync` is used to switch to the correct context and queue a render. For more information, see <xref:blazor/components/rendering>. -* The component implements <xref:System.IDisposable>. The `OnNotify` delegate is unsubscribed in the `Dispose` method, which is called by the framework when the component is disposed. For more information, see <xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable>. - -## Use `@key` to control the preservation of elements and components - -When rendering a list of elements or components and the elements or components subsequently change, Blazor must decide which of the previous elements or components can be retained and how model objects should map to them. Normally, this process is automatic and can be ignored, but there are cases where you may want to control the process. - -Consider the following `Details` and `People` components: - -* The `Details` component receives data (`Data`) from the parent `People` component, which is displayed in an `<input>` element. Any given displayed `<input>` element can receive the focus of the page from the user when they select one of the `<input>` elements. -* The `People` component creates a list of person objects for display using the `Details` component. Every three seconds, a new person is added to the collection. - -This demonstration allows you to: - -* Select an `<input>` from among several rendered `Details` components. -* Study the behavior of the page's focus as the people collection automatically grows. - -`Shared/Details.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/Details.razor"::: - -In the following `People` component, each iteration of adding a person in `OnTimerCallback` results in Blazor rebuilding the entire collection. The page's focus remains on the *same index* position of `<input>` elements, so the focus shifts each time a person is added. *Shifting the focus away from what the user selected isn't desirable behavior.* After demonstrating the poor behavior with the following component, the [`@key`][5] directive attribute is used to improve the user's experience. - -`Pages/People.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/People.razor"::: - -The contents of the `people` collection changes with inserted, deleted, or re-ordered entries. Rerendering can lead to visible behavior differences. Each time a person is inserted into the `people` collection, the *preceding element* of the currently focused element receives the focus. The user's focus is lost. - -The mapping process of elements or components to a collection can be controlled with the [`@key`][5] directive attribute. Use of [`@key`][5] guarantees the preservation of elements or components based on the key's value. If the `Details` component in the preceding example is keyed on the `person` item, Blazor ignores rerendering `Details` components that haven't changed. - -To modify the `People` component to use the [`@key`][5] directive attribute with the `people` collection, update the `<Details>` element to the following: - -```razor -<Details @key="person" Data="@person.Data" /> -``` - -When the `people` collection changes, the association between `Details` instances and `person` instances is retained. When a `Person` is inserted at the beginning of the collection, one new `Details` instance is inserted at that corresponding position. Other instances are left unchanged. Therefore, the user's focus isn't lost as people are added to the collection. - -Other collection updates exhibit the same behavior when the [`@key`][5] directive attribute is used: - -* If an instance is deleted from the collection, only the corresponding component instance is removed from the UI. Other instances are left unchanged. -* If collection entries are re-ordered, the corresponding component instances are preserved and re-ordered in the UI. - -> [!IMPORTANT] -> Keys are local to each container element or component. Keys aren't compared globally across the document. - -### When to use `@key` - -Typically, it makes sense to use [`@key`][5] whenever a list is rendered (for example, in a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) block) and a suitable value exists to define the [`@key`][5]. - -You can also use [`@key`][5] to preserve an element or component subtree when an object doesn't change, as the following examples show. - -Example 1: - -```razor -<li @key="person"> - <input value="@person.Data" /> -</li> -``` - -Example 2: - -```razor -<div @key="person"> - @* other HTML elements *@ -</div> -``` - -If an `person` instance changes, the [`@key`][5] attribute directive forces Blazor to: - -* Discard the entire `<li>` or `<div>` and their descendants. -* Rebuild the subtree within the UI with new elements and components. - -This is useful to guarantee that no UI state is preserved when the collection changes within a subtree. - -### Scope of `@key` - -The [`@key`][5] attribute directive is scoped to its own siblings within its parent. - -Consider the following example. The `first` and `second` keys are compared against each other within the same scope of the outer `<div>` element: - -```razor -<div> - <div @key="first">...</div> - <div @key="second">...</div> -</div> -``` - -The following example demonstrates `first` and `second` keys in their own scopes, unrelated to each other and without influence on each other. Each [`@key`][5] scope only applies to its parent `<div>` element, not across the parent `<div>` elements: - -```razor -<div> - <div @key="first">...</div> -</div> -<div> - <div @key="second">...</div> -</div> -``` - -For the `Details` component shown earlier, the following examples render `person` data within the same [`@key`][5] scope and demonstrate typical use cases for [`@key`][5]: - -```razor -<div> - @foreach (var person in people) - { - <Details @key="person" Data="@person.Data" /> - } -</div> -``` - -```razor -@foreach (var person in people) -{ - <div @key="person"> - <Details Data="@person.Data" /> - </div> -} -``` - -```razor -<ol> - @foreach (var person in people) - { - <li @key="person"> - <Details Data="@person.Data" /> - </li> - } -</ol> -``` - -The following examples only scope [`@key`][5] to the `<div>` or `<li>` element that surrounds each `Details` component instance. Therefore, `person` data for each member of the `people` collection is **not** keyed on each `person` instance across the rendered `Details` components. Avoid the following patterns when using [`@key`][5]: - -```razor -@foreach (var person in people) -{ - <div> - <Details @key="person" Data="@person.Data" /> - </div> -} -``` - -```razor -<ol> - @foreach (var person in people) - { - <li> - <Details @key="person" Data="@person.Data" /> - </li> - } -</ol> -``` - -### When not to use `@key` - -There's a performance cost when rendering with [`@key`][5]. The performance cost isn't large, but only specify [`@key`][5] if preserving the element or component benefits the app. - -Even if [`@key`][5] isn't used, Blazor preserves child element and component instances as much as possible. The only advantage to using [`@key`][5] is control over *how* model instances are mapped to the preserved component instances, instead of Blazor selecting the mapping. - -### Values to use for `@key` - -Generally, it makes sense to supply one of the following values for [`@key`][5]: - -* Model object instances. For example, the `Person` instance (`person`) was used in the earlier example. This ensures preservation based on object reference equality. -* Unique identifiers. For example, unique identifiers can be based on primary key values of type `int`, `string`, or `Guid`. - -Ensure that values used for [`@key`][5] don't clash. If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Only use distinct values, such as object instances or primary key values. - -## Apply an attribute - -Attributes can be applied to components with the [`@attribute`][7] directive. The following example applies the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the component's class: - -```razor -@page "/" -@attribute [Authorize] -``` - -## Conditional HTML element attributes - -HTML element attribute properties are conditionally set based on the .NET value. If the value is `false` or `null`, the property isn't set. If the value is `true`, the property is set. - -In the following example, `IsCompleted` determines if the `<input>` element's `checked` property is set. - -`Pages/ConditionalAttribute.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor"::: - -For more information, see <xref:mvc/views/razor>. - -> [!WARNING] -> Some HTML attributes, such as [`aria-pressed`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Roles/button_role#Toggle_buttons), don't function properly when the .NET type is a `bool`. In those cases, use a `string` type instead of a `bool`. - -## Raw HTML - -Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. To render raw HTML, wrap the HTML content in a <xref:Microsoft.AspNetCore.Components.MarkupString> value. The value is parsed as HTML or SVG and inserted into the DOM. - -> [!WARNING] -> Rendering raw HTML constructed from any untrusted source is a **security risk** and should **always** be avoided. - -The following example shows using the <xref:Microsoft.AspNetCore.Components.MarkupString> type to add a block of static HTML content to the rendered output of a component. - -`Pages/MarkupStringExample.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor"::: - -## Razor templates - -Render fragments can be defined using Razor template syntax to define a UI snippet. Razor templates use the following format: - -```razor -@<{HTML tag}>...</{HTML tag}> -``` - -The following example illustrates how to specify <xref:Microsoft.AspNetCore.Components.RenderFragment> and <xref:Microsoft.AspNetCore.Components.RenderFragment%601> values and render templates directly in a component. Render fragments can also be passed as arguments to [templated components](xref:blazor/components/templated-components). - -`Pages/RazorTemplate.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor"::: - -Rendered output of the preceding code: - -```html -<p>The time is 4/19/2021 8:54:46 AM.</p> -<p>Pet: Nutty Rex</p> -``` - -## Static assets - -Blazor follows the convention of ASP.NET Core apps for static assets. Static assets are located in the project's [`web root` (`wwwroot`) folder](xref:fundamentals/index#web-root) or folders under the `wwwroot` folder. - -Use a base-relative path (`/`) to refer to the web root for a static asset. In the following example, `logo.png` is physically located in the `{PROJECT ROOT}/wwwroot/images` folder. `{PROJECT ROOT}` is the app's project root. - -```razor -<img alt="Company logo" src="/images/logo.png" /> -``` - -Components do **not** support tilde-slash notation (`~/`). - -For information on setting an app's base path, see <xref:blazor/host-and-deploy/index#app-base-path>. - -## Tag Helpers aren't supported in components - -[`Tag Helpers`](xref:mvc/views/tag-helpers/intro) aren't supported in components. To provide Tag Helper-like functionality in Blazor, create a component with the same functionality as the Tag Helper and use the component instead. - -## Scalable Vector Graphics (SVG) images - -Since Blazor renders HTML, browser-supported images, including [Scalable Vector Graphics (SVG) images (`.svg`)](https://developer.mozilla.org/docs/Web/SVG), are supported via the `<img>` tag: - -```html -<img alt="Example image" src="image.svg" /> -``` - -Similarly, SVG images are supported in the CSS rules of a stylesheet file (`.css`): - -```css -.element-class { - background-image: url("image.svg"); -} -``` - -Blazor supports the [`<foreignObject>`](https://developer.mozilla.org/docs/Web/SVG/Element/foreignObject) element to display arbitrary HTML within an SVG. The markup can represent arbitrary HTML, a <xref:Microsoft.AspNetCore.Components.RenderFragment>, or a Razor component. - -The following example demonstrates: - -* Display of a `string` (`@message`). -* Two-way binding with an `<input>` element and a `value` field. -* A `Robot` component. - -```razor -<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> - <rect x="0" y="0" rx="10" ry="10" width="200" height="200" stroke="black" - fill="none" /> - <foreignObject x="20" y="20" width="160" height="160"> - <p>@message</p> - </foreignObject> -</svg> - -<svg xmlns="http://www.w3.org/2000/svg"> - <foreignObject width="200" height="200"> - <label> - Two-way binding: - <input @bind="value" @bind:event="oninput" /> - </label> - </foreignObject> -</svg> - -<svg xmlns="http://www.w3.org/2000/svg"> - <foreignObject> - <Robot /> - </foreignObject> -</svg> - -@code { - private string message = "Lorem ipsum dolor sit amet, consectetur adipiscing " + - "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; - - private string? value; -} -``` - -## Whitespace rendering behavior - -Unless the [`@preservewhitespace`](xref:mvc/views/razor#preservewhitespace) directive is used with a value of `true`, extra whitespace is removed by default if: - -* Leading or trailing within an element. -* Leading or trailing within a <xref:Microsoft.AspNetCore.Components.RenderFragment>/<xref:Microsoft.AspNetCore.Components.RenderFragment%601> parameter (for example, child content passed to another component). -* It precedes or follows a C# code block, such as `@if` or `@foreach`. - -Whitespace removal might affect the rendered output when using a CSS rule, such as `white-space: pre`. To disable this performance optimization and preserve the whitespace, take one of the following actions: - -* Add the `@preservewhitespace true` directive at the top of the Razor file (`.razor`) to apply the preference to a specific component. -* Add the `@preservewhitespace true` directive inside an `_Imports.razor` file to apply the preference to a subdirectory or to the entire project. - -In most cases, no action is required, as apps typically continue to behave normally (but faster). If stripping whitespace causes a rendering problem for a particular component, use `@preservewhitespace true` in that component to disable this optimization. - -## Generic type parameter support - -The [`@typeparam`][11] directive declares a [generic type parameter](/dotnet/csharp/programming-guide/generics/generic-type-parameters) for the generated component class: - -```razor -@typeparam TItem -``` - -C# syntax with [`where`](/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) type constraints is supported: - -```razor -@typeparam TEntity where TEntity : IEntity -``` - -In the following example, the `ListGenericTypeItems1` component is generically typed as `TExample`. - -`Shared/ListGenericTypeItems1.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/ListGenericTypeItems1.razor"::: - -The following `GenericTypeExample1` component renders two `ListGenericTypeItems1` components: - -* String or integer data is assigned to the `ExampleList` parameter of each component. -* Type `string` or `int` that matches the type of the assigned data is set for the type parameter (`TExample`) of each component. - -`Pages/GenericTypeExample1.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/GenericTypeExample1.razor"::: - -For more information, see <xref:mvc/views/razor#typeparam>. For an example of generic typing with templated components, see <xref:blazor/components/templated-components>. - -## Cascaded generic type support - -An ancestor component can cascade a type parameter by name to descendants using the [`[CascadingTypeParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute). This attribute allows a generic type inference to use the specified type parameter automatically with descendants that have a type parameter with the same name. - -By adding `@attribute [CascadingTypeParameter(...)]` to a component, the specified generic type argument is automatically used by descendants that: - -* Are nested as child content for the component in the same `.razor` document. -* Also declare a [`@typeparam`](xref:mvc/views/razor#typeparam) with the exact same name. -* Don't have another value explicitly supplied or implicitly inferred for the type parameter. If another value is supplied or inferred, it takes precedence over the cascaded generic type. - -When receiving a cascaded type parameter, components obtain the parameter value from the closest ancestor that has a <xref:Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute> with a matching name. Cascaded generic type parameters are overridden within a particular subtree. - -Matching is only performed by name. Therefore, we recommend avoiding a cascaded generic type parameter with a generic name, for example `T` or `TItem`. If a developer opts into cascading a type parameter, they're implicitly promising that its name is unique enough not to clash with other cascaded type parameters from unrelated components. - -Generic types can be cascaded to child components in either of the following approaches with ancestor (parent) components, which are demonstrated in the following two sub-sections: - -* Explicitly set the cascaded generic type. -* Infer the cascaded generic type. - -The following subsections provide examples of the preceding approaches using the following two `ListDisplay` components. The components receive and render list data and are generically typed as `TExample`. These components are for demonstration purposes and only differ in the color of text that the list is rendered. If you wish to experiment with the components in the following sub-sections in a local test app, add the following two components to the app first. - -`Shared/ListDisplay1.razor`: - -```razor -@typeparam TExample - -@if (ExampleList is not null) -{ - <ul style="color:blue"> - @foreach (var item in ExampleList) - { - <li>@item</li> - } - </ul> -} - -@code { - [Parameter] - public IEnumerable<TExample>? ExampleList { get; set; } -} -``` - -`Shared/ListDisplay2.razor`: - -```razor -@typeparam TExample - -@if (ExampleList is not null) -{ - <ul style="color:red"> - @foreach (var item in ExampleList) - { - <li>@item</li> - } - </ul> -} - -@code { - [Parameter] - public IEnumerable<TExample>? ExampleList { get; set; } -} -``` - -### Explicit generic types based on ancestor components - -The demonstration in this section cascades a type explicitly for `TExample`. - -> [!NOTE] -> This section uses the two `ListDisplay` components in the [Cascaded generic type support](#cascaded-generic-type-support) section. - -The following `ListGenericTypeItems2` component receives data and cascades a generic type parameter named `TExample` to its descendent components. In the upcoming parent component, the `ListGenericTypeItems2` component is used to display list data with the preceding `ListDisplay` component. - -`Shared/ListGenericTypeItems2.razor`: - -```razor -@attribute [CascadingTypeParameter(nameof(TExample))] -@typeparam TExample - -<h2>List Generic Type Items 2</h2> - -@ChildContent - -@code { - [Parameter] - public RenderFragment? ChildContent { get; set; } -} -``` - -The following `GenericTypeExample2` parent component sets the child content (<xref:Microsoft.AspNetCore.Components.RenderFragment>) of two `ListGenericTypeItems2` components specifying the `ListGenericTypeItems2` types (`TExample`), which are cascaded to child components. `ListDisplay` components are rendered with the list item data shown in the example. String data is used with the first `ListGenericTypeItems2` component, and integer data is used with the second `ListGenericTypeItems2` component. - -`Pages/GenericTypeExample2.razor`: - -```razor -@page "/generic-type-example-2" - -<h1>Generic Type Example 2</h1> - -<ListGenericTypeItems2 TExample="string"> - <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" /> - <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" /> -</ListGenericTypeItems2> - -<ListGenericTypeItems2 TExample="int"> - <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" /> - <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" /> -</ListGenericTypeItems2> -``` - -Specifying the type explicitly also allows the use of [cascading values and parameters](xref:blazor/components/cascading-values-and-parameters) to provide data to child components, as the following demonstration shows. - -`Shared/ListDisplay3.razor`: - -```razor -@typeparam TExample - -@if (ExampleList is not null) -{ - <ul style="color:blue"> - @foreach (var item in ExampleList) - { - <li>@item</li> - } - </ul> -} - -@code { - [CascadingParameter] - protected IEnumerable<TExample>? ExampleList { get; set; } -} -``` - -`Shared/ListDisplay4.razor`: - -```razor -@typeparam TExample - -@if (ExampleList is not null) -{ - <ul style="color:red"> - @foreach (var item in ExampleList) - { - <li>@item</li> - } - </ul> -} - -@code { - [CascadingParameter] - protected IEnumerable<TExample>? ExampleList { get; set; } -} -``` - -`Shared/ListGenericTypeItems3.razor`: - -```razor -@attribute [CascadingTypeParameter(nameof(TExample))] -@typeparam TExample - -<h2>List Generic Type Items 3</h2> - -@ChildContent - -@if (ExampleList is not null) -{ - <ul style="color:green"> - @foreach(var item in ExampleList) - { - <li>@item</li> - } - </ul> - - <p> - Type of <code>TExample</code>: @typeof(TExample) - </p> -} - -@code { - [CascadingParameter] - protected IEnumerable<TExample>? ExampleList { get; set; } - - [Parameter] - public RenderFragment? ChildContent { get; set; } -} -``` - -When cascading the data in the following example, the type must be provided to the `ListGenericTypeItems3` component. - -`Pages/GenericTypeExample3.razor`: - -```razor -@page "/generic-type-example-3" - -<h1>Generic Type Example 3</h1> - -<CascadingValue Value="@stringData"> - <ListGenericTypeItems3 TExample="string"> - <ListDisplay3 /> - <ListDisplay4 /> - </ListGenericTypeItems3> -</CascadingValue> - -<CascadingValue Value="@integerData"> - <ListGenericTypeItems3 TExample="int"> - <ListDisplay3 /> - <ListDisplay4 /> - </ListGenericTypeItems3> -</CascadingValue> - -@code { - private List<string> stringData = new() { "Item 1", "Item 2" }; - private List<int> integerData = new() { 1, 2, 3 }; -} -``` - -When multiple generic types are cascaded, values for all generic types in the set must be passed. In the following example, `TItem`, `TValue`, and `TEdit` are `GridColumn` generic types, but the parent component that places `GridColumn` doesn't specify the `TItem` type: - -```razor -<GridColumn TValue="string" TEdit="@TextEdit" /> -``` - -The preceding example generates a compile-time error that the `GridColumn` component is missing the `TItem` type parameter. Valid code specifies all of the types: - -```razor -<GridColumn TValue="string" TEdit="@TextEdit" TItem="@User" /> -``` - -### Infer generic types based on ancestor components - -The demonstration in this section cascades a type inferred for `TExample`. - -> [!NOTE] -> This section uses the two `ListDisplay` components in the [Cascaded generic type support](#cascaded-generic-type-support) section. - -`Shared/ListGenericTypeItems4.razor`: - -```razor -@attribute [CascadingTypeParameter(nameof(TExample))] -@typeparam TExample - -<h2>List Generic Type Items 4</h2> - -@ChildContent - -@if (ExampleList is not null) -{ - <ul style="color:green"> - @foreach(var item in ExampleList) - { - <li>@item</li> - } - </ul> - - <p> - Type of <code>TExample</code>: @typeof(TExample) - </p> -} - -@code { - [Parameter] - public IEnumerable<TExample>? ExampleList { get; set; } - - [Parameter] - public RenderFragment? ChildContent { get; set; } -} -``` - -The following `GenericTypeExample4` component with inferred cascaded types provides different data for display. - -`Pages/GenericTypeExample4.razor`: - -```razor -@page "/generic-type-example-4" - -<h1>Generic Type Example 4</h1> - -<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })"> - <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" /> - <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" /> -</ListGenericTypeItems4> - -<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })"> - <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" /> - <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" /> -</ListGenericTypeItems4> -``` - -The following `GenericTypeExample5` component with inferred cascaded types provides the same data for display. The following example directly assigns the data to the components. - -`Pages/GenericTypeExample5.razor`: - -```razor -@page "/generic-type-example-5" - -<h1>Generic Type Example 5</h1> - -<ListGenericTypeItems4 ExampleList="@stringData"> - <ListDisplay1 ExampleList="@stringData" /> - <ListDisplay2 ExampleList="@stringData" /> -</ListGenericTypeItems4> - -<ListGenericTypeItems4 ExampleList="@integerData"> - <ListDisplay1 ExampleList="@integerData" /> - <ListDisplay2 ExampleList="@integerData" /> -</ListGenericTypeItems4> - -@code { - private List<string> stringData = new() { "Item 1", "Item 2" }; - private List<int> integerData = new() { 1, 2, 3 }; -} -``` - -## Render Razor components from JavaScript - -Razor components can be dynamically-rendered from JavaScript (JS) for existing JS apps. - -To render a Razor component from JS, register the component as a root component for JS rendering and assign the component an identifier: - -* In a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`: - - ```csharp - builder.Services.AddServerSideBlazor(options => - { - options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); - }); - ``` - - > [!NOTE] - > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. - -* In a Blazor WebAssembly app, call <xref:Microsoft.AspNetCore.Components.Web.JSComponentConfigurationExtensions.RegisterForJavaScript%2A> on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`: - - ```csharp - builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); - ``` - - > [!NOTE] - > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. - -Load Blazor into the JS app (`blazor.server.js` or `blazor.webassembly.js`). Render the component from JS into a container element using the registered identifier, passing component parameters as needed: - -```javascript -let containerElement = document.getElementById('my-counter'); -await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 }); -``` - -## Blazor custom elements - -*Experimental* support is available for building custom elements using the [`Microsoft.AspNetCore.Components.CustomElements` NuGet package](https://www.nuget.org/packages/microsoft.aspnetcore.components.customelements). Custom elements use standard HTML interfaces to implement custom HTML elements. - -> [!WARNING] -> Experimental features are provided for the purpose of exploring feature viability and may not ship in a stable version. - -Register a root component as a custom element: - -* In a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`: - - ```csharp - builder.Services.AddServerSideBlazor(options => - { - options.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); - }); - ``` - - > [!NOTE] - > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. - -* In a Blazor WebAssembly app, call `RegisterAsCustomElement` on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`: - - ```csharp - builder.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); - ``` - - > [!NOTE] - > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. - -Include the following `<script>` tag in the app's HTML ***before*** the Blazor script tag: - -```html -<script src="/_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script> -``` - -Use the custom element with any web framework. For example, the preceding counter custom element is used in a React app with the following markup: - -```html -<my-counter increment-amount={incrementAmount}></my-counter> -``` - -For a complete example of how to create custom elements with Blazor, see the [Blazor Custom Elements sample project](https://github.com/aspnet/AspLabs/tree/main/src/BlazorCustomElements). - -> [!WARNING] -> The custom elements feature is currently **experimental, unsupported, and subject to change or be removed at any time**. We welcome your feedback on how well this particular approach meets your requirements. - -## Generate Angular and React components - -Generate framework-specific JavaScript (JS) components from Razor components for web frameworks, such as Angular or React. This capability isn't included with .NET 6, but is enabled by the new support for rendering Razor components from JS. The [JS component generation sample on GitHub](https://github.com/aspnet/samples/tree/main/samples/aspnetcore/blazor/JSComponentGeneration) demonstrates how to generate Angular and React components from Razor components. See the GitHub sample app's `README.md` file for additional information. - -> [!WARNING] -> The Angular and React component features are currently **experimental, unsupported, and subject to change or be removed at any time**. We welcome your feedback on how well this particular approach meets your requirements. - -:::moniker-end - -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -Blazor apps are built using *Razor components*, informally known as *Blazor components*. A component is a self-contained portion of user interface (UI) with processing logic to enable dynamic behavior. Components can be nested, reused, shared among projects, and [used in MVC and Razor Pages apps](xref:blazor/components/prerendering-and-integration). - -## Component classes - -Components are implemented using a combination of C# and HTML markup in [Razor](xref:mvc/views/razor) component files with the `.razor` file extension. - -### Razor syntax - -Components use [Razor syntax](xref:mvc/views/razor). Two Razor features are extensively used by components, *directives* and *directive attributes*. These are reserved keywords prefixed with `@` that appear in Razor markup: - -* [Directives](xref:mvc/views/razor#directives): Change the way component markup is parsed or functions. For example, the [`@page`][9] directive specifies a routable component with a route template and can be reached directly by a user's request in the browser at a specific URL. -* [Directive attributes](xref:mvc/views/razor#directive-attributes): Change the way a component element is parsed or functions. For example, the [`@bind`][10] directive attribute for an `<input>` element binds data to the element's value. - -Directives and directive attributes used in components are explained further in this article and other articles of the Blazor documentation set. For general information on Razor syntax, see <xref:mvc/views/razor>. - -### Names - -A component's name must start with an uppercase character: - -* `ProductDetail.razor` is valid. -* `productDetail.razor` is invalid. - -Common Blazor naming conventions used throughout the Blazor documentation include: - -* Component file paths use Pascal case† and appear before showing component code examples. Paths indicate typical folder locations. For example, `Pages/ProductDetail.razor` indicates that the `ProductDetail` component has a file name of `ProductDetail.razor` and resides in the `Pages` folder of the app. -* Component file paths for routable components match their URLs with hyphens appearing for spaces between words in a component's route template. For example, a `ProductDetail` component with a route template of `/product-detail` (`@page "/product-detail"`) is requested in a browser at the relative URL `/product-detail`. - -†Pascal case (upper camel case) is a naming convention without spaces and punctuation and with the first letter of each word capitalized, including the first word. - -### Routing - -Routing in Blazor is achieved by providing a route template to each accessible component in the app with an [`@page`][9] directive. When a Razor file with an [`@page`][9] directive is compiled, the generated class is given a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> specifying the route template. At runtime, the router searches for component classes with a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> and renders whichever component has a route template that matches the requested URL. - -The following `HelloWorld` component uses a route template of `/hello-world`. The rendered webpage for the component is reached at the relative URL `/hello-world`. When running a Blazor app locally with the default protocol, host, and port, the `HelloWorld` component is requested in the browser at `https://localhost:5001/hello-world`. Components that produce webpages usually reside in the `Pages` folder, but you can use any folder to hold components, including within nested folders. - -`Pages/HelloWorld.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor)] - -The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. Optionally, components can be added to the `NavMenu` component so that a link to the component appears in the app's UI-based navigation. - -For the preceding `HelloWorld` component, you can add a `NavLink` component to the `NavMenu` component in the `Shared` folder. For more information, including descriptions of the `NavLink` and `NavMenu` components, see <xref:blazor/fundamentals/routing>. - -### Markup - -A component's UI is defined using [Razor syntax](xref:mvc/views/razor), which consists of Razor markup, C#, and HTML. When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. The name of the generated class matches the name of the file. - -Members of the component class are defined in one or more [`@code`][1] blocks. In [`@code`][1] blocks, component state is specified and processed with C#: - -* Property and field initializers. -* Parameter values from arguments passed by parent components and route parameters. -* Methods for user event handling, lifecycle events, and custom component logic. - -Component members are used in rendering logic using C# expressions that start with the `@` symbol. For example, a C# field is rendered by prefixing `@` to the field name. The following `Markup` component evaluates and renders: - -* `headingFontStyle` for the CSS property value `font-style` of the heading element. -* `headingText` for the content of the heading element. - -`Pages/Markup.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/Markup.razor)] - -> [!NOTE] -> Examples throughout the Blazor documentation specify the [`private` access modifier](/dotnet/csharp/language-reference/keywords/private) for private members. Private members are scoped to a component's class. However, C# assumes the `private` access modifier when no access modifier is present, so explicitly marking members "`private`" in your own code is optional. For more information on access modifiers, see [Access Modifiers (C# Programming Guide)](/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers). - -The Blazor framework processes a component internally as a [*render tree*](https://developer.mozilla.org/docs/Web/Performance/How_browsers_work#render), which is the combination of a component's [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) and [Cascading Style Sheet Object Model (CSSOM)](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model). After the component is initially rendered, the component's render tree is regenerated in response to events. Blazor compares the new render tree against the previous render tree and applies any modifications to the browser's DOM for display. For more information, see <xref:blazor/components/rendering>. - -Components are ordinary [C# classes](/dotnet/csharp/programming-guide/classes-and-structs/classes) and can be placed anywhere within a project. Components that produce webpages usually reside in the `Pages` folder. Non-page components are frequently placed in the `Shared` folder or a custom folder added to the project. - -Razor syntax for C# control structures, directives, and directive attributes are lowercase (examples: [`@if`](xref:mvc/views/razor#conditionals-if-else-if-else-and-switch), [`@code`](xref:mvc/views/razor#code), [`@bind`](xref:mvc/views/razor#bind)). Property names are uppercase (example: `@Body` for <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body?displayProperty=nameWithType>). - -### Asynchronous methods (`async`) don't support returning `void` - -The Blazor framework doesn't track `void`-returning asynchronous methods (`async`). As a result, exceptions aren't caught if `void` is returned. Always return a <xref:System.Threading.Tasks.Task> from asynchronous methods. - -### Nested components - -Components can include other components by declaring them using HTML syntax. The markup for using a component looks like an HTML tag where the name of the tag is the component type. - -Consider the following `Heading` component, which can be used by other components to display a heading. - -`Shared/Heading.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/Heading.razor)] - -The following markup in the `HeadingExample` component renders the preceding `Heading` component at the location where the `<Heading />` tag appears. - -`Pages/HeadingExample.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor)] - -If a component contains an HTML element with an uppercase first letter that doesn't match a component name within the same namespace, a warning is emitted indicating that the element has an unexpected name. Adding an [`@using`][2] directive for the component's namespace makes the component available, which resolves the warning. For more information, see the [Namespaces](#namespaces) section. - -The `Heading` component example shown in this section doesn't have an [`@page`][9] directive, so the `Heading` component isn't directly accessible to a user via a direct request in the browser. However, any component with an [`@page`][9] directive can be nested in another component. If the `Heading` component was directly accessible by including `@page "/heading"` at the top of its Razor file, then the component would be rendered for browser requests at both `/heading` and `/heading-example`. - -### Namespaces - -Typically, a component's namespace is derived from the app's root namespace and the component's location (folder) within the app. If the app's root namespace is `BlazorSample` and the `Counter` component resides in the `Pages` folder: - -* The `Counter` component's namespace is `BlazorSample.Pages`. -* The fully qualified type name of the component is `BlazorSample.Pages.Counter`. - -For custom folders that hold components, add an [`@using`][2] directive to the parent component or to the app's `_Imports.razor` file. The following example makes components in the `Components` folder available: - -```razor -@using BlazorSample.Components -``` - -> [!NOTE] -> [`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). - -Components can also be referenced using their fully qualified names, which doesn't require an [`@using`][2] directive. The following example directly references the `ProductDetail` component in the `Components` folder of the app: - -```razor -<BlazorSample.Components.ProductDetail /> -``` - -The namespace of a component authored with Razor is based on the following (in priority order): - -* The [`@namespace`][8] directive in the Razor file's markup (for example, `@namespace BlazorSample.CustomNamespace`). -* The project's `RootNamespace` in the project file (for example, `<RootNamespace>BlazorSample</RootNamespace>`). -* The project name, taken from the project file's file name (`.csproj`), and the path from the project root to the component. For example, the framework resolves `{PROJECT ROOT}/Pages/Index.razor` with a project namespace of `BlazorSample` (`BlazorSample.csproj`) to the namespace `BlazorSample.Pages` for the `Index` component. `{PROJECT ROOT}` is the project root path. Components follow C# name binding rules. For the `Index` component in this example, the components in scope are all of the components: - * In the same folder, `Pages`. - * The components in the project's root that don't explicitly specify a different namespace. - -The following are **not** supported: - -* The [`global::`](/dotnet/csharp/language-reference/operators/namespace-alias-qualifier) qualification. -* Importing components with aliased [`using`](/dotnet/csharp/language-reference/keywords/using-statement) statements. For example, `@using Foo = Bar` isn't supported. -* Partially-qualified names. For example, you can't add `@using BlazorSample` to a component and then reference the `NavMenu` component in the app's `Shared` folder (`Shared/NavMenu.razor`) with `<Shared.NavMenu></Shared.NavMenu>`. - -### Partial class support - -Components are generated as [C# partial classes](/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods) and are authored using either of the following approaches: - -* A single file contains C# code defined in one or more [`@code`][1] blocks, HTML markup, and Razor markup. Blazor project templates define their components using this single-file approach. -* HTML and Razor markup are placed in a Razor file (`.razor`). C# code is placed in a code-behind file defined as a partial class (`.cs`). - -> [!NOTE] -> A component stylesheet that defines component-specific styles is a separate file (`.css`). Blazor CSS isolation is described later in <xref:blazor/components/css-isolation>. - -The following example shows the default `Counter` component with an [`@code`][1] block in an app generated from a Blazor project template. Markup and C# code are in the same file. This is the most common approach taken in component authoring. - -`Pages/Counter.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/Counter.razor)] - -The following `Counter` component splits HTML and Razor markup from C# code using a code-behind file with a partial class: - -`Pages/CounterPartialClass.razor`: - -```razor -@page "/counter-partial-class" - -<h1>Counter</h1> - -<p>Current count: @currentCount</p> - -<button class="btn btn-primary" @onclick="IncrementCount">Click me</button> -``` - -`Pages/CounterPartialClass.razor.cs`: - -```csharp -namespace BlazorSample.Pages -{ - public partial class CounterPartialClass - { - private int currentCount = 0; - - void IncrementCount() - { - currentCount++; - } - } -} -``` - -[`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). Add namespaces to a partial class file as needed. - -Typical namespaces used by components: - -```csharp -using System.Net.Http; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Routing; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.Web.Virtualization; -using Microsoft.JSInterop; -``` - -Typical namespaces also include the namespace of the app and the namespace corresponding to the app's `Shared` folder: - -```csharp -using BlazorSample; -using BlazorSample.Shared; -``` - -### Specify a base class - -The [`@inherits`][6] directive is used to specify a base class for a component. The following example shows how a component can inherit a base class to provide the component's properties and methods. The `BlazorRocksBase` base class derives from <xref:Microsoft.AspNetCore.Components.ComponentBase>. - -`Pages/BlazorRocks.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor)] - -`BlazorRocksBase.cs`: - -[!code-csharp[](~/blazor/samples/5.0/BlazorSample_WebAssembly/BlazorRocksBase.cs)] - -## Component parameters - -*Component parameters* pass data to components and are defined using public [C# properties](/dotnet/csharp/programming-guide/classes-and-structs/properties) on the component class with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). In the following example, a built-in reference type (<xref:System.String?displayProperty=fullName>) and a user-defined reference type (`PanelBody`) are passed as component parameters. - -`PanelBody.cs`: - -[!code-csharp[](~/blazor/samples/5.0/BlazorSample_WebAssembly/PanelBody.cs)] - -`Shared/ParameterChild.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor)] - -> [!WARNING] -> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. - -The `Title` and `Body` component parameters of the `ParameterChild` component are set by arguments in the HTML tag that renders the instance of the component. The following `ParameterParent` component renders two `ParameterChild` components: - -* The first `ParameterChild` component is rendered without supplying parameter arguments. -* The second `ParameterChild` component receives values for `Title` and `Body` from the `ParameterParent` component, which uses an [explicit C# expression](xref:mvc/views/razor#explicit-razor-expressions) to set the values of the `PanelBody`'s properties. - -`Pages/ParameterParent.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor)] - -The following rendered HTML markup from the `ParameterParent` component shows `ParameterChild` component default values when the `ParameterParent` component doesn't supply component parameter values. When the `ParameterParent` component provides component parameter values, they replace the `ParameterChild` component's default values. - -> [!NOTE] -> For clarity, rendered CSS style classes aren't shown in the following rendered HTML markup. - -```html -<h1>Child component (without attribute values)</h1> - -<div> - <div>Set By Child</div> - <div>Set by child.</div> -</div> - -<h1>Child component (with attribute values)</h1> - -<div> - <div>Set by Parent</div> - <div>Set by parent.</div> -</div> -``` - -Assign a C# field, property, or result of a method to a component parameter as an HTML attribute value using [Razor's reserved `@` symbol](xref:mvc/views/razor#razor-syntax). The following `ParameterParent2` component displays four instances of the preceding `ParameterChild` component and sets their `Title` parameter values to: - -* The value of the `title` field. -* The result of the `GetTitle` C# method. -* The current local date in long format with <xref:System.DateTime.ToLongDateString%2A>, which uses an [implicit C# expression](xref:mvc/views/razor#implicit-razor-expressions). -* The `panelData` object's `Title` property. - -The `@` prefix is required for string parameters. Otherwise, the framework assumes that a string literal is set. - -Outside of string parameters, we recommend use the use of the `@` prefix for nonliterals, even when they aren't strictly required. - -We don't recommend the use of the `@` prefix for literals (for example, boolean values), keywords (for example, `this`), or `null`, but you can choose to use them if you wish. For example, `IsFixed="@true"` is uncommon but supported. - -Quotes around parameter attribute values are optional in most cases per the HTML5 specification. For example, `Value=this` is supported, instead of `Value="this"`. However, we recommend using quotes because it's easier to remember and widely adopted across web-based technologies. - -Throughout the documentation, code examples: - -* Always use quotes. Example: `Value="this"`. -* Nonliterals always use the `@` prefix, even when it's optional. Examples: `Title="@title"`, where `title` is a string-typed variable. `Count="@ct"`, where `ct` is a number-typed variable. -* Literals, outside of Razor expressions, always avoid `@`. Example: `IsFixed="true"`. - -`Pages/ParameterParent2.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor)] - -> [!NOTE] -> When assigning a C# member to a component parameter, prefix the member with the `@` symbol and never prefix the parameter's HTML attribute. -> -> Correct: -> -> ```razor -> <ParameterChild Title="@title" /> -> ``` -> -> Incorrect: -> -> ```razor -> <ParameterChild @Title="title" /> -> ``` - -Unlike in Razor pages (`.cshtml`), Blazor can't perform asynchronous work in a Razor expression while rendering a component. This is because Blazor is designed for rendering interactive UIs. In an interactive UI, the screen must always display something, so it doesn't make sense to block the rendering flow. Instead, asynchronous work is performed during one of the [asynchronous lifecycle events](xref:blazor/components/lifecycle). After each asynchronous lifecycle event, the component may render again. The following Razor syntax is **not** supported: - -```razor -<ParameterChild Title="@await ..." /> -``` - -The code in the preceding example generates a *compiler error* when the app is built: - -> The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. - -To obtain a value for the `Title` parameter in the preceding example asynchronously, the component can use the [`OnInitializedAsync` lifecycle event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync), as the following example demonstrates: - -```razor -<ParameterChild Title="@title" /> - -@code { - private string title; - - protected override async Task OnInitializedAsync() - { - title = await ...; - } -} -``` - -For more information, see <xref:blazor/components/lifecycle>. - -Use of an explicit Razor expression to concatenate text with an expression result for assignment to a parameter is **not** supported. The following example seeks to concatenate the text "`Set by `" with an object's property value. Although this syntax is supported in a Razor page (`.cshtml`), it isn't valid for assignment to the child's `Title` parameter in a component. The following Razor syntax is **not** supported: - -```razor -<ParameterChild Title="Set by @(panelData.Title)" /> -``` - -The code in the preceding example generates a *compiler error* when the app is built: - -> Component attributes do not support complex content (mixed C# and markup). - -To support the assignment of a composed value, use a method, field, or property. The following example performs the concatenation of "`Set by `" and an object's property value in the C# method `GetTitle`: - -`Pages/ParameterParent3.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor)] - -For more information, see <xref:mvc/views/razor>. - -> [!WARNING] -> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. - -Component parameters should be declared as *auto-properties*, meaning that they shouldn't contain custom logic in their `get` or `set` accessors. For example, the following `StartData` property is an auto-property: - -```csharp -[Parameter] -public DateTime StartData { get; set; } -``` - -Don't place custom logic in the `get` or `set` accessor because component parameters are purely intended for use as a channel for a parent component to flow information to a child component. If a `set` accessor of a child component property contains logic that causes rerendering of the parent component, an infinite rendering loop results. - -To transform a received parameter value: - -* Leave the parameter property as an auto-property to represent the supplied raw data. -* Create a different property or method to supply the transformed data based on the parameter property. - -Override [`OnParametersSetAsync`](xref:blazor/components/lifecycle#after-parameters-are-set-onparameterssetasync) to transform a received parameter each time new data is received. - -Writing an initial value to a component parameter is supported because initial value assignments don't interfere with the Blazor's automatic component rendering. The following assignment of the current local <xref:System.DateTime> with <xref:System.DateTime.Now?displayProperty=nameWithType> to `StartData` is valid syntax in a component: - -```csharp -[Parameter] -public DateTime StartData { get; set; } = DateTime.Now; -``` - -After the initial assignment of <xref:System.DateTime.Now?displayProperty=nameWithType>, do **not** assign a value to `StartData` in developer code. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. - -## Route parameters - -Components can specify route parameters in the route template of the [`@page`][9] directive. The [Blazor router](xref:blazor/fundamentals/routing) uses route parameters to populate corresponding component parameters. - -Optional route parameters are supported. In the following example, the `text` optional parameter assigns the value of the route segment to the component's `Text` property. If the segment isn't present, the value of `Text` is set to "`fantastic`" in the [`OnInitialized` lifecycle method](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). - -`Pages/RouteParameter.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor?highlight=1,6-7)] - -For information on catch-all route parameters (`{*pageRoute}`), which capture paths across multiple folder boundaries, see <xref:blazor/fundamentals/routing#catch-all-route-parameters>. - -## Child content render fragments - -Components can set the content of another component. The assigning component provides the content between the child component's opening and closing tags. - -In the following example, the `RenderFragmentChild` component has a `ChildContent` component parameter that represents a segment of the UI to render as a <xref:Microsoft.AspNetCore.Components.RenderFragment>. The position of `ChildContent` in the component's Razor markup is where the content is rendered in the final HTML output. - -`Shared/RenderFragmentChild.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor)] - -> [!IMPORTANT] -> The property receiving the <xref:Microsoft.AspNetCore.Components.RenderFragment> content must be named `ChildContent` by convention. -> -> [Event callbacks](xref:blazor/components/event-handling#eventcallback) aren't supported for <xref:Microsoft.AspNetCore.Components.RenderFragment>. - -The following `RenderFragmentParent` component provides content for rendering the `RenderFragmentChild` by placing the content inside the child component's opening and closing tags. - -`Pages/RenderFragmentParent.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor)] - -Due to the way that Blazor renders child content, rendering components inside a [`for`](/dotnet/csharp/language-reference/keywords/for) loop requires a local index variable if the incrementing loop variable is used in the `RenderFragmentChild` component's content. The following example can be added to the preceding `RenderFragmentParent` component: - -```razor -<h1>Three children with an index variable</h1> - -@for (int c = 0; c < 3; c++) -{ - var current = c; - - <RenderFragmentChild> - Count: @current - </RenderFragmentChild> -} -``` - -Alternatively, use a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop with <xref:System.Linq.Enumerable.Range%2A?displayProperty=nameWithType> instead of a [`for`](/dotnet/csharp/language-reference/keywords/for) loop. The following example can be added to the preceding `RenderFragmentParent` component: - -```razor -<h1>Second example of three children with an index variable</h1> - -@foreach (var c in Enumerable.Range(0,3)) -{ - <RenderFragmentChild> - Count: @c - </RenderFragmentChild> -} -``` - -Render fragments are used to render child content throughout Blazor apps and are described with examples in the following articles and article sections: - -* [Blazor layouts](xref:blazor/components/layouts) -* [Pass data across a component hierarchy](xref:blazor/components/cascading-values-and-parameters#pass-data-across-a-component-hierarchy) -* [Templated components](xref:blazor/components/templated-components) -* [Global exception handling](xref:blazor/fundamentals/handle-errors#global-exception-handling) - -> [!NOTE] -> Blazor framework's [built-in Razor components](xref:blazor/components/built-in-components) use the same `ChildContent` component parameter convention to set their content. You can see the components that set child content by searching for the component parameter property name `ChildContent` in the [API documentation (filters API with the search term "ChildContent")](/dotnet/api/?term=ChildContent). - -## Render fragments for reusable rendering logic - -You can factor out child components purely as a way of reusing rendering logic. In any component's `@code` block, define a <xref:Microsoft.AspNetCore.Components.RenderFragment> and render the fragment from any location as many times as needed: - -```razor -<h1>Hello, world!</h1> - -@RenderWelcomeInfo - -<p>Render the welcome info a second time:</p> - -@RenderWelcomeInfo - -@code { - private RenderFragment RenderWelcomeInfo = __builder => - { - <p>Welcome to your new app!</p> - }; -} -``` - -For more information, see [Reuse rendering logic](xref:blazor/performance#define-reusable-renderfragments-in-code). - -## Overwritten parameters - -The Blazor framework generally imposes safe parent-to-child parameter assignment: - -* Parameters aren't overwritten unexpectedly. -* Side effects are minimized. For example, additional renders are avoided because they may create infinite rendering loops. - -A child component receives new parameter values that possibly overwrite existing values when the parent component rerenders. Accidentally overwriting parameter values in a child component often occurs when developing the component with one or more data-bound parameters and the developer writes directly to a parameter in the child: - -* The child component is rendered with one or more parameter values from the parent component. -* The child writes directly to the value of a parameter. -* The parent component rerenders and overwrites the value of the child's parameter. - -The potential for overwriting parameter values extends into the child component's property `set` accessors, too. - -> [!IMPORTANT] -> Our general guidance is not to create components that directly write to their own parameters after the component is rendered for the first time. - -Consider the following `Expander` component that: - -* Renders child content. -* Toggles showing child content with a component parameter (`Expanded`). - -After the following `Expander` component demonstrates an overwritten parameter, a modified `Expander` component is shown to demonstrate the correct approach for this scenario. The following examples can be placed in a local sample app to experience the behaviors described. - -`Shared/Expander.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/BadExpander.razor)] - -The `Expander` component is added to the following `ExpanderExample` parent component that may call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>: - -* Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in developer code notifies a component that its state has changed and typically triggers component rerendering to update the UI. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is covered in more detail later in <xref:blazor/components/lifecycle> and <xref:blazor/components/rendering>. -* The button's `@onclick` directive attribute attaches an event handler to the button's `onclick` event. Event handling is covered in more detail later in <xref:blazor/components/event-handling>. - -`Pages/ExpanderExample.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ExpanderExample.razor)] - -Initially, the `Expander` components behave independently when their `Expanded` properties are toggled. The child components maintain their states as expected. - -If <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called in a parent component, the Blazor framework rerenders child components if their parameters might have changed: - -* For a group of parameter types that Blazor explicitly checks, Blazor rerenders a child component if it detects that any of the parameters have changed. -* For unchecked parameter types, Blazor rerenders the child component *regardless of whether or not the parameters have changed*. Child content falls into this category of parameter types because child content is of type <xref:Microsoft.AspNetCore.Components.RenderFragment>, which is a delegate that refers to other mutable objects. - -For the `ExpanderExample` component: - -* The first `Expander` component sets child content in a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment>, so a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component automatically rerenders the component and potentially overwrites the value of `Expanded` to its intitial value of `true`. -* The second `Expander` component doesn't set child content. Therefore, a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment> doesn't exist. A call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component doesn't automatically rerender the child component, so the component's `Expanded` value isn't overwritten. - -To maintain state in the preceding scenario, use a *private field* in the `Expander` component to maintain its toggled state. - -The following revised `Expander` component: - -* Accepts the `Expanded` component parameter value from the parent. -* Assigns the component parameter value to a *private field* (`expanded`) in the [`OnInitialized` event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). -* Uses the private field to maintain its internal toggle state, which demonstrates how to avoid writing directly to a parameter. - -> [!NOTE] -> The advice in this section extends to similar logic in component parameter `set` accessors, which can result in similar undesirable side effects. - -`Shared/Expander.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/Expander.razor)] - -For additional information, see [Blazor Two Way Binding Error (dotnet/aspnetcore #24599)](https://github.com/dotnet/aspnetcore/issues/24599). - -For more information on change detection, inlcuding information on the exact types that Blazor checks, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. - -## Attribute splatting and arbitrary parameters - -Components can capture and render additional attributes in addition to the component's declared parameters. Additional attributes can be captured in a dictionary and then *splatted* onto an element when the component is rendered using the [`@attributes`][3] Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations. For example, it can be tedious to define attributes separately for an `<input>` that supports many parameters. - -In the following `Splat` component: - -* The first `<input>` element (`id="useIndividualParams"`) uses individual component parameters. -* The second `<input>` element (`id="useAttributesDict"`) uses attribute splatting. - -`Pages/Splat.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/Splat.razor)] - -The rendered `<input>` elements in the webpage are identical: - -```html -<input id="useIndividualParams" - maxlength="10" - placeholder="Input placeholder text" - required="required" - size="50"> - -<input id="useAttributesDict" - maxlength="10" - placeholder="Input placeholder text" - required="required" - size="50"> -``` - -To accept arbitrary attributes, define a [component parameter](#component-parameters) with the <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property set to `true`: - -```razor -@code { - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary<string, object> InputAttributes { get; set; } -} -``` - -The <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property on [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) allows the parameter to match all attributes that don't match any other parameter. A component can only define a single parameter with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues>. The property type used with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> must be assignable from [`Dictionary<string, object>`](xref:System.Collections.Generic.Dictionary%602) with string keys. Use of [`IEnumerable<KeyValuePair<string, object>>`](xref:System.Collections.Generic.IEnumerable%601) or [`IReadOnlyDictionary<string, object>`](xref:System.Collections.Generic.IReadOnlyDictionary%602) are also options in this scenario. - -The position of [`@attributes`][3] relative to the position of element attributes is important. When [`@attributes`][3] are splatted on the element, the attributes are processed from right to left (last to first). Consider the following example of a parent component that consumes a child component: - -`Shared/AttributeOrderChild1.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild1.razor)] - -`Pages/AttributeOrderParent1.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent1.razor)] - -The `AttributeOrderChild1` component's `extra` attribute is set to the right of [`@attributes`][3]. The `AttributeOrderParent1` component's rendered `<div>` contains `extra="5"` when passed through the additional attribute because the attributes are processed right to left (last to first): - -```html -<div extra="5" /> -``` - -In the following example, the order of `extra` and [`@attributes`][3] is reversed in the child component's `<div>`: - -`Shared/AttributeOrderChild2.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild2.razor)] - -`Pages/AttributeOrderParent2.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent2.razor)] - -The `<div>` in the parent component's rendered webpage contains `extra="10"` when passed through the additional attribute: - -```html -<div extra="10" /> -``` - -## Capture references to components - -Component references provide a way to reference a component instance for issuing commands. To capture a component reference: - -* Add an [`@ref`][4] attribute to the child component. -* Define a field with the same type as the child component. - -When the component is rendered, the field is populated with the component instance. You can then invoke .NET methods on the instance. - -Consider the following `ReferenceChild` component that logs a message when its `ChildMethod` is called. - -`Shared/ReferenceChild.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor)] - -A component reference is only populated after the component is rendered and its output includes `ReferenceChild`'s element. Until the component is rendered, there's nothing to reference. - -To manipulate component references after the component has finished rendering, use the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). - -To use a reference variable with an event handler, use a lambda expression or assign the event handler delegate in the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). This ensures that the reference variable is assigned before the event handler is assigned. - -The following lambda approach uses the preceding `ReferenceChild` component. - -`Pages/ReferenceParent1.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor)] - -The following delegate approach uses the preceding `ReferenceChild` component. - -`Pages/ReferenceParent2.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor)] - -While capturing component references use a similar syntax to [capturing element references](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements), capturing component references isn't a JavaScript interop feature. Component references aren't passed to JavaScript code. Component references are only used in .NET code. - -> [!IMPORTANT] -> Do **not** use component references to mutate the state of child components. Instead, use normal declarative component parameters to pass data to child components. Use of component parameters result in child components that rerender at the correct times automatically. For more information, see the [component parameters](#component-parameters) section and the <xref:blazor/components/data-binding> article. - -## Synchronization context - -Blazor uses a synchronization context (<xref:System.Threading.SynchronizationContext>) to enforce a single logical thread of execution. A component's [lifecycle methods](xref:blazor/components/lifecycle) and event callbacks raised by Blazor are executed on the synchronization context. - -Blazor Server's synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. At any given point in time, work is performed on exactly one thread, which yields the impression of a single logical thread. No two operations execute concurrently. - -### Avoid thread-blocking calls - -Generally, don't call the following methods in components. The following methods block the execution thread and thus block the app from resuming work until the underlying <xref:System.Threading.Tasks.Task> is complete: - -* <xref:System.Threading.Tasks.Task%601.Result%2A> -* <xref:System.Threading.Tasks.Task.Wait%2A> -* <xref:System.Threading.Tasks.Task.WaitAny%2A> -* <xref:System.Threading.Tasks.Task.WaitAll%2A> -* <xref:System.Threading.Thread.Sleep%2A> -* <xref:System.Runtime.CompilerServices.TaskAwaiter.GetResult%2A> - -> [!NOTE] -> Blazor documentation examples that use the thread-blocking methods mentioned in this section are only using the methods for demonstration purposes, not as recommended coding guidance. For example, a few component code demonstrations simulate a long-running process by calling <xref:System.Threading.Thread.Sleep%2A?displayProperty=nameWithType>. - -### Invoke component methods externally to update state - -In the event a component must be updated based on an external event, such as a timer or other notification, use the `InvokeAsync` method, which dispatches code execution to Blazor's synchronization context. For example, consider the following *notifier service* that can notify any listening component about updated state. The `Update` method can be called from anywhere in the app. - -`TimerService.cs`: - -[!code-csharp[](~/blazor/samples/5.0/BlazorSample_WebAssembly/TimerService.cs)] - -`NotifierService.cs`: - -[!code-csharp[](~/blazor/samples/5.0/BlazorSample_WebAssembly/NotifierService.cs)] - -Register the services: - -* In a Blazor WebAssembly app, register the services as singletons in `Program.cs`: - - ```csharp - builder.Services.AddSingleton<NotifierService>(); - builder.Services.AddSingleton<TimerService>(); - ``` - -* In a Blazor Server app, register the services as scoped in `Startup.ConfigureServices`: - - ```csharp - services.AddScoped<NotifierService>(); - services.AddScoped<TimerService>(); - ``` - -Use the `NotifierService` to update a component. - -`Pages/ReceiveNotifications.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ReceiveNotifications.razor)] - -In the preceding example: - -* `NotifierService` invokes the component's `OnNotify` method outside of Blazor's synchronization context. `InvokeAsync` is used to switch to the correct context and queue a render. For more information, see <xref:blazor/components/rendering>. -* The component implements <xref:System.IDisposable>. The `OnNotify` delegate is unsubscribed in the `Dispose` method, which is called by the framework when the component is disposed. For more information, see <xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable>. - -## Use `@key` to control the preservation of elements and components - -When rendering a list of elements or components and the elements or components subsequently change, Blazor must decide which of the previous elements or components can be retained and how model objects should map to them. Normally, this process is automatic and can be ignored, but there are cases where you may want to control the process. - -Consider the following `Details` and `People` components: - -* The `Details` component receives data (`Data`) from the parent `People` component, which is displayed in an `<input>` element. Any given displayed `<input>` element can receive the focus of the page from the user when they select one of the `<input>` elements. -* The `People` component creates a list of person objects for display using the `Details` component. Every three seconds, a new person is added to the collection. - -This demonstration allows you to: - -* Select an `<input>` from among several rendered `Details` components. -* Study the behavior of the page's focus as the people collection automatically grows. - -`Shared/Details.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/Details.razor)] - -In the following `People` component, each iteration of adding a person in `OnTimerCallback` results in Blazor rebuilding the entire collection. The page's focus remains on the *same index* position of `<input>` elements, so the focus shifts each time a person is added. *Shifting the focus away from what the user selected isn't desirable behavior.* After demonstrating the poor behavior with the following component, the [`@key`][5] directive attribute is used to improve the user's experience. - -`Pages/People.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/People.razor)] - -The contents of the `people` collection changes with inserted, deleted, or re-ordered entries. Rerendering can lead to visible behavior differences. Each time a person is inserted into the `people` collection, the *preceding element* of the currently focused element receives the focus. The user's focus is lost. - -The mapping process of elements or components to a collection can be controlled with the [`@key`][5] directive attribute. Use of [`@key`][5] guarantees the preservation of elements or components based on the key's value. If the `Details` component in the preceding example is keyed on the `person` item, Blazor ignores rerendering `Details` components that haven't changed. - -To modify the `People` component to use the [`@key`][5] directive attribute with the `people` collection, update the `<Details>` element to the following: - -```razor -<Details @key="person" Data="@person.Data" /> -``` - -When the `people` collection changes, the association between `Details` instances and `person` instances is retained. When a `Person` is inserted at the beginning of the collection, one new `Details` instance is inserted at that corresponding position. Other instances are left unchanged. Therefore, the user's focus isn't lost as people are added to the collection. - -Other collection updates exhibit the same behavior when the [`@key`][5] directive attribute is used: - -* If an instance is deleted from the collection, only the corresponding component instance is removed from the UI. Other instances are left unchanged. -* If collection entries are re-ordered, the corresponding component instances are preserved and re-ordered in the UI. - -> [!IMPORTANT] -> Keys are local to each container element or component. Keys aren't compared globally across the document. - -### When to use `@key` - -Typically, it makes sense to use [`@key`][5] whenever a list is rendered (for example, in a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) block) and a suitable value exists to define the [`@key`][5]. - -You can also use [`@key`][5] to preserve an element or component subtree when an object doesn't change, as the following examples show. - -Example 1: - -```razor -<li @key="person"> - <input value="@person.Data" /> -</li> -``` - -Example 2: - -```razor -<div @key="person"> - @* other HTML elements *@ -</div> -``` - -If an `person` instance changes, the [`@key`][5] attribute directive forces Blazor to: - -* Discard the entire `<li>` or `<div>` and their descendants. -* Rebuild the subtree within the UI with new elements and components. - -This is useful to guarantee that no UI state is preserved when the collection changes within a subtree. - -### Scope of `@key` - -The [`@key`][5] attribute directive is scoped to its own siblings within its parent. - -Consider the following example. The `first` and `second` keys are compared against each other within the same scope of the outer `<div>` element: - -```razor -<div> - <div @key="first">...</div> - <div @key="second">...</div> -</div> -``` - -The following example demonstrates `first` and `second` keys in their own scopes, unrelated to each other and without influence on each other. Each [`@key`][5] scope only applies to its parent `<div>` element, not across the parent `<div>` elements: - -```razor -<div> - <div @key="first">...</div> -</div> -<div> - <div @key="second">...</div> -</div> -``` - -For the `Details` component shown earlier, the following examples render `person` data within the same [`@key`][5] scope and demonstrate typical use cases for [`@key`][5]: - -```razor -<div> - @foreach (var person in people) - { - <Details @key="person" Data="@person.Data" /> - } -</div> -``` - -```razor -@foreach (var person in people) -{ - <div @key="person"> - <Details Data="@person.Data" /> - </div> -} -``` - -```razor -<ol> - @foreach (var person in people) - { - <li @key="person"> - <Details Data="@person.Data" /> - </li> - } -</ol> -``` - -The following examples only scope [`@key`][5] to the `<div>` or `<li>` element that surrounds each `Details` component instance. Therefore, `person` data for each member of the `people` collection is **not** keyed on each `person` instance across the rendered `Details` components. Avoid the following patterns when using [`@key`][5]: - -```razor -@foreach (var person in people) -{ - <div> - <Details @key="person" Data="@person.Data" /> - </div> -} -``` - -```razor -<ol> - @foreach (var person in people) - { - <li> - <Details @key="person" Data="@person.Data" /> - </li> - } -</ol> -``` - -### When not to use `@key` - -There's a performance cost when rendering with [`@key`][5]. The performance cost isn't large, but only specify [`@key`][5] if preserving the element or component benefits the app. - -Even if [`@key`][5] isn't used, Blazor preserves child element and component instances as much as possible. The only advantage to using [`@key`][5] is control over *how* model instances are mapped to the preserved component instances, instead of Blazor selecting the mapping. - -### Values to use for `@key` - -Generally, it makes sense to supply one of the following values for [`@key`][5]: - -* Model object instances. For example, the `Person` instance (`person`) was used in the earlier example. This ensures preservation based on object reference equality. -* Unique identifiers. For example, unique identifiers can be based on primary key values of type `int`, `string`, or `Guid`. - -Ensure that values used for [`@key`][5] don't clash. If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Only use distinct values, such as object instances or primary key values. - -## Apply an attribute - -Attributes can be applied to components with the [`@attribute`][7] directive. The following example applies the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the component's class: - -```razor -@page "/" -@attribute [Authorize] -``` - -## Conditional HTML element attributes - -HTML element attribute properties are conditionally set based on the .NET value. If the value is `false` or `null`, the property isn't set. If the value is `true`, the property is set. - -In the following example, `IsCompleted` determines if the `<input>` element's `checked` property is set. - -`Pages/ConditionalAttribute.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor)] - -For more information, see <xref:mvc/views/razor>. - -> [!WARNING] -> Some HTML attributes, such as [`aria-pressed`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Roles/button_role#Toggle_buttons), don't function properly when the .NET type is a `bool`. In those cases, use a `string` type instead of a `bool`. - -## Raw HTML - -Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. To render raw HTML, wrap the HTML content in a <xref:Microsoft.AspNetCore.Components.MarkupString> value. The value is parsed as HTML or SVG and inserted into the DOM. - -> [!WARNING] -> Rendering raw HTML constructed from any untrusted source is a **security risk** and should **always** be avoided. - -The following example shows using the <xref:Microsoft.AspNetCore.Components.MarkupString> type to add a block of static HTML content to the rendered output of a component. - -`Pages/MarkupStringExample.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor)] - -## Razor templates - -Render fragments can be defined using Razor template syntax to define a UI snippet. Razor templates use the following format: - -```razor -@<{HTML tag}>...</{HTML tag}> -``` - -The following example illustrates how to specify <xref:Microsoft.AspNetCore.Components.RenderFragment> and <xref:Microsoft.AspNetCore.Components.RenderFragment%601> values and render templates directly in a component. Render fragments can also be passed as arguments to [templated components](xref:blazor/components/templated-components). - -`Pages/RazorTemplate.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor)] - -Rendered output of the preceding code: - -```html -<p>The time is 4/19/2021 8:54:46 AM.</p> -<p>Pet: Nutty Rex</p> -``` - -## Static assets - -Blazor follows the convention of ASP.NET Core apps for static assets. Static assets are located in the project's [`web root` (`wwwroot`) folder](xref:fundamentals/index#web-root) or folders under the `wwwroot` folder. - -Use a base-relative path (`/`) to refer to the web root for a static asset. In the following example, `logo.png` is physically located in the `{PROJECT ROOT}/wwwroot/images` folder. `{PROJECT ROOT}` is the app's project root. - -```razor -<img alt="Company logo" src="/images/logo.png" /> -``` - -Components do **not** support tilde-slash notation (`~/`). - -For information on setting an app's base path, see <xref:blazor/host-and-deploy/index#app-base-path>. - -## Tag Helpers aren't supported in components - -[`Tag Helpers`](xref:mvc/views/tag-helpers/intro) aren't supported in components. To provide Tag Helper-like functionality in Blazor, create a component with the same functionality as the Tag Helper and use the component instead. - -## Scalable Vector Graphics (SVG) images - -Since Blazor renders HTML, browser-supported images, including [Scalable Vector Graphics (SVG) images (`.svg`)](https://developer.mozilla.org/docs/Web/SVG), are supported via the `<img>` tag: - -```html -<img alt="Example image" src="image.svg" /> -``` - -Similarly, SVG images are supported in the CSS rules of a stylesheet file (`.css`): - -```css -.element-class { - background-image: url("image.svg"); -} -``` - -## Whitespace rendering behavior - -Unless the [`@preservewhitespace`](xref:mvc/views/razor#preservewhitespace) directive is used with a value of `true`, extra whitespace is removed by default if: - -* Leading or trailing within an element. -* Leading or trailing within a <xref:Microsoft.AspNetCore.Components.RenderFragment>/<xref:Microsoft.AspNetCore.Components.RenderFragment%601> parameter (for example, child content passed to another component). -* It precedes or follows a C# code block, such as `@if` or `@foreach`. - -Whitespace removal might affect the rendered output when using a CSS rule, such as `white-space: pre`. To disable this performance optimization and preserve the whitespace, take one of the following actions: - -* Add the `@preservewhitespace true` directive at the top of the Razor file (`.razor`) to apply the preference to a specific component. -* Add the `@preservewhitespace true` directive inside an `_Imports.razor` file to apply the preference to a subdirectory or to the entire project. - -In most cases, no action is required, as apps typically continue to behave normally (but faster). If stripping whitespace causes a rendering problem for a particular component, use `@preservewhitespace true` in that component to disable this optimization. - -## Generic type parameter support - -The [`@typeparam`][11] directive declares a [generic type parameter](/dotnet/csharp/programming-guide/generics/generic-type-parameters) for the generated component class: - -```razor -@typeparam TItem -``` +* `headingFontStyle` for the CSS property value `font-style` of the heading element. +* `headingText` for the content of the heading element. -In the following example, the `ListGenericTypeItems1` component is generically typed as `TExample`. +`Pages/Markup.razor`: -`Shared/ListGenericTypeItems1.razor`: +:::moniker range=">= aspnetcore-7.0" -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/index/ListGenericTypeItems1.razor)] +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/Markup.razor"::: -The following `GenericTypeExample1` component renders two `ListGenericTypeItems1` components: +:::moniker-end -* String or integer data is assigned to the `ExampleList` parameter of each component. -* Type `string` or `int` that matches the type of the assigned data is set for the type parameter (`TExample`) of each component. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`Pages/GenericTypeExample1.razor`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/Markup.razor"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/index/GenericTypeExample1.razor)] +:::moniker-end -For more information, see the following articles: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* <xref:mvc/views/razor#typeparam> -* <xref:blazor/components/templated-components> +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/Markup.razor"::: :::moniker-end :::moniker range="< aspnetcore-5.0" -Blazor apps are built using *Razor components*, informally known as *Blazor components*. A component is a self-contained portion of user interface (UI) with processing logic to enable dynamic behavior. Components can be nested, reused, shared among projects, and [used in MVC and Razor Pages apps](xref:blazor/components/prerendering-and-integration). - -## Component classes - -Components are implemented using a combination of C# and HTML markup in [Razor](xref:mvc/views/razor) component files with the `.razor` file extension. - -### Razor syntax - -Components use [Razor syntax](xref:mvc/views/razor). Two Razor features are extensively used by components, *directives* and *directive attributes*. These are reserved keywords prefixed with `@` that appear in Razor markup: - -* [Directives](xref:mvc/views/razor#directives): Change the way component markup is parsed or functions. For example, the [`@page`][9] directive specifies a routable component with a route template and can be reached directly by a user's request in the browser at a specific URL. -* [Directive attributes](xref:mvc/views/razor#directive-attributes): Change the way a component element is parsed or functions. For example, the [`@bind`][10] directive attribute for an `<input>` element binds data to the element's value. - -Directives and directive attributes used in components are explained further in this article and other articles of the Blazor documentation set. For general information on Razor syntax, see <xref:mvc/views/razor>. - -### Names - -A component's name must start with an uppercase character: - -* `ProductDetail.razor` is valid. -* `productDetail.razor` is invalid. - -Common Blazor naming conventions used throughout the Blazor documentation include: - -* Component file paths use Pascal case† and appear before showing component code examples. Paths indicate typical folder locations. For example, `Pages/ProductDetail.razor` indicates that the `ProductDetail` component has a file name of `ProductDetail.razor` and resides in the `Pages` folder of the app. -* Component file paths for routable components match their URLs with hyphens appearing for spaces between words in a component's route template. For example, a `ProductDetail` component with a route template of `/product-detail` (`@page "/product-detail"`) is requested in a browser at the relative URL `/product-detail`. - -†Pascal case (upper camel case) is a naming convention without spaces and punctuation and with the first letter of each word capitalized, including the first word. - -### Routing - -Routing in Blazor is achieved by providing a route template to each accessible component in the app with an [`@page`][9] directive. When a Razor file with an [`@page`][9] directive is compiled, the generated class is given a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> specifying the route template. At runtime, the router searches for component classes with a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> and renders whichever component has a route template that matches the requested URL. - -The following `HelloWorld` component uses a route template of `/hello-world`. The rendered webpage for the component is reached at the relative URL `/hello-world`. When running a Blazor app locally with the default protocol, host, and port, the `HelloWorld` component is requested in the browser at `https://localhost:5001/hello-world`. Components that produce webpages usually reside in the `Pages` folder, but you can use any folder to hold components, including within nested folders. - -`Pages/HelloWorld.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor)] - -The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. Optionally, components can be added to the `NavMenu` component so that a link to the component appears in the app's UI-based navigation. - -For the preceding `HelloWorld` component, you can add a `NavLink` component to the `NavMenu` component in the `Shared` folder. For more information, including descriptions of the `NavLink` and `NavMenu` components, see <xref:blazor/fundamentals/routing>. - -### Markup - -A component's UI is defined using [Razor syntax](xref:mvc/views/razor), which consists of Razor markup, C#, and HTML. When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. The name of the generated class matches the name of the file. - -Members of the component class are defined in one or more [`@code`][1] blocks. In [`@code`][1] blocks, component state is specified and processed with C#: - -* Property and field initializers. -* Parameter values from arguments passed by parent components and route parameters. -* Methods for user event handling, lifecycle events, and custom component logic. - -Component members are used in rendering logic using C# expressions that start with the `@` symbol. For example, a C# field is rendered by prefixing `@` to the field name. The following `Markup` component evaluates and renders: - -* `headingFontStyle` for the CSS property value `font-style` of the heading element. -* `headingText` for the content of the heading element. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/Markup.razor"::: -`Pages/Markup.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/Markup.razor)] +:::moniker-end > [!NOTE] > Examples throughout the Blazor documentation specify the [`private` access modifier](/dotnet/csharp/language-reference/keywords/private) for private members. Private members are scoped to a component's class. However, C# assumes the `private` access modifier when no access modifier is present, so explicitly marking members "`private`" in your own code is optional. For more information on access modifiers, see [Access Modifiers (C# Programming Guide)](/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers). The Blazor framework processes a component internally as a [*render tree*](https://developer.mozilla.org/docs/Web/Performance/How_browsers_work#render), which is the combination of a component's [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) and [Cascading Style Sheet Object Model (CSSOM)](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model). After the component is initially rendered, the component's render tree is regenerated in response to events. Blazor compares the new render tree against the previous render tree and applies any modifications to the browser's DOM for display. For more information, see <xref:blazor/components/rendering>. -Components are ordinary [C# classes](/dotnet/csharp/programming-guide/classes-and-structs/classes) and can be placed anywhere within a project. Components that produce webpages usually reside in the `Pages` folder. Non-page components are frequently placed in the `Shared` folder or a custom folder added to the project. - Razor syntax for C# control structures, directives, and directive attributes are lowercase (examples: [`@if`](xref:mvc/views/razor#conditionals-if-else-if-else-and-switch), [`@code`](xref:mvc/views/razor#code), [`@bind`](xref:mvc/views/razor#bind)). Property names are uppercase (example: `@Body` for <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body?displayProperty=nameWithType>). ### Asynchronous methods (`async`) don't support returning `void` @@ -2598,143 +390,120 @@ Consider the following `Heading` component, which can be used by other component `Shared/Heading.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/Heading.razor)] +:::moniker range=">= aspnetcore-7.0" -The following markup in the `HeadingExample` component renders the preceding `Heading` component at the location where the `<Heading />` tag appears. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/Heading.razor"::: -`Pages/HeadingExample.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor)] +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -If a component contains an HTML element with an uppercase first letter that doesn't match a component name within the same namespace, a warning is emitted indicating that the element has an unexpected name. Adding an [`@using`][2] directive for the component's namespace makes the component available, which resolves the warning. For more information, see the [Namespaces](#namespaces) section. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/Heading.razor"::: -The `Heading` component example shown in this section doesn't have an [`@page`][9] directive, so the `Heading` component isn't directly accessible to a user via a direct request in the browser. However, any component with an [`@page`][9] directive can be nested in another component. If the `Heading` component was directly accessible by including `@page "/heading"` at the top of its Razor file, then the component would be rendered for browser requests at both `/heading` and `/heading-example`. +:::moniker-end -### Namespaces +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -Typically, a component's namespace is derived from the app's root namespace and the component's location (folder) within the app. If the app's root namespace is `BlazorSample` and the `Counter` component resides in the `Pages` folder: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/index/Heading.razor"::: -* The `Counter` component's namespace is `BlazorSample.Pages`. -* The fully qualified type name of the component is `BlazorSample.Pages.Counter`. +:::moniker-end -For custom folders that hold components, add an [`@using`][2] directive to the parent component or to the app's `_Imports.razor` file. The following example makes components in the `Components` folder available: +:::moniker range="< aspnetcore-5.0" -```razor -@using BlazorSample.Components -``` +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/index/Heading.razor"::: -> [!NOTE] -> [`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). +:::moniker-end -Components can also be referenced using their fully qualified names, which doesn't require an [`@using`][2] directive. The following example directly references the `ProductDetail` component in the `Components` folder of the app: +The following markup in the `HeadingExample` component renders the preceding `Heading` component at the location where the `<Heading />` tag appears. -```razor -<BlazorSample.Components.ProductDetail /> -``` +`Pages/HeadingExample.razor`: -The namespace of a component authored with Razor is based on the following (in priority order): +:::moniker range=">= aspnetcore-7.0" -* The [`@namespace`][8] directive in the Razor file's markup (for example, `@namespace BlazorSample.CustomNamespace`). -* The project's `RootNamespace` in the project file (for example, `<RootNamespace>BlazorSample</RootNamespace>`). -* The project name, taken from the project file's file name (`.csproj`), and the path from the project root to the component. For example, the framework resolves `{PROJECT ROOT}/Pages/Index.razor` with a project namespace of `BlazorSample` (`BlazorSample.csproj`) to the namespace `BlazorSample.Pages` for the `Index` component. `{PROJECT ROOT}` is the project root path. Components follow C# name binding rules. For the `Index` component in this example, the components in scope are all of the components: - * In the same folder, `Pages`. - * The components in the project's root that don't explicitly specify a different namespace. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor"::: -The following are **not** supported: +:::moniker-end -* The [`global::`](/dotnet/csharp/language-reference/operators/namespace-alias-qualifier) qualification. -* Importing components with aliased [`using`](/dotnet/csharp/language-reference/keywords/using-statement) statements. For example, `@using Foo = Bar` isn't supported. -* Partially-qualified names. For example, you can't add `@using BlazorSample` to a component and then reference the `NavMenu` component in the app's `Shared` folder (`Shared/NavMenu.razor`) with `<Shared.NavMenu></Shared.NavMenu>`. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -### Partial class support +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor"::: -Components are generated as [C# partial classes](/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods) and are authored using either of the following approaches: +:::moniker-end -* A single file contains C# code defined in one or more [`@code`][1] blocks, HTML markup, and Razor markup. Blazor project templates define their components using this single-file approach. -* HTML and Razor markup are placed in a Razor file (`.razor`). C# code is placed in a code-behind file defined as a partial class (`.cs`). +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -The following example shows the default `Counter` component with an [`@code`][1] block in an app generated from a Blazor project template. Markup and C# code are in the same file. This is the most common approach taken in component authoring. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor"::: -`Pages/Counter.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/Counter.razor)] +:::moniker range="< aspnetcore-5.0" -The following `Counter` component splits HTML and Razor markup from C# code using a code-behind file with a partial class: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor"::: -`Pages/CounterPartialClass.razor`: +:::moniker-end -```razor -@page "/counter-partial-class" +If a component contains an HTML element with an uppercase first letter that doesn't match a component name within the same namespace, a warning is emitted indicating that the element has an unexpected name. Adding an [`@using`][2] directive for the component's namespace makes the component available, which resolves the warning. For more information, see the [Component name, class name, and namespace](#component-name-class-name-and-namespace) section. -<h1>Counter</h1> +The `Heading` component example shown in this section doesn't have an [`@page`][9] directive, so the `Heading` component isn't directly accessible to a user via a direct request in the browser. However, any component with an [`@page`][9] directive can be nested in another component. If the `Heading` component was directly accessible by including `@page "/heading"` at the top of its Razor file, then the component would be rendered for browser requests at both `/heading` and `/heading-example`. -<p>Current count: @currentCount</p> +## Component parameters -<button class="btn btn-primary" @onclick="IncrementCount">Click me</button> -``` +*Component parameters* pass data to components and are defined using public [C# properties](/dotnet/csharp/programming-guide/classes-and-structs/properties) on the component class with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). In the following example, a built-in reference type (<xref:System.String?displayProperty=fullName>) and a user-defined reference type (`PanelBody`) are passed as component parameters. -`Pages/CounterPartialClass.razor.cs`: +`PanelBody.cs`: -```csharp -namespace BlazorSample.Pages -{ - public partial class CounterPartialClass - { - private int currentCount = 0; +:::moniker range=">= aspnetcore-7.0" - void IncrementCount() - { - currentCount++; - } - } -} -``` +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/PanelBody.cs"::: -[`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). Add namespaces to a partial class file as needed. +:::moniker-end -Typical namespaces used by components: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```csharp -using System.Net.Http; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Routing; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.JSInterop; -``` +:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/PanelBody.cs"::: -Typical namespaces also include the namespace of the app and the namespace corresponding to the app's `Shared` folder: +:::moniker-end -```csharp -using BlazorSample; -using BlazorSample.Shared; -``` +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/PanelBody.cs"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/PanelBody.cs"::: + +:::moniker-end + +`Shared/ParameterChild.razor`: -### Specify a base class +:::moniker range=">= aspnetcore-7.0" -The [`@inherits`][6] directive is used to specify a base class for a component. The following example shows how a component can inherit a base class to provide the component's properties and methods. The `BlazorRocksBase` base class derives from <xref:Microsoft.AspNetCore.Components.ComponentBase>. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor"::: -`Pages/BlazorRocks.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor)] +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`BlazorRocksBase.cs`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor"::: -[!code-csharp[](~/blazor/samples/3.1/BlazorSample_WebAssembly/BlazorRocksBase.cs)] +:::moniker-end -## Component parameters +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -*Component parameters* pass data to components and are defined using public [C# properties](/dotnet/csharp/programming-guide/classes-and-structs/properties) on the component class with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). In the following example, a built-in reference type (<xref:System.String?displayProperty=fullName>) and a user-defined reference type (`PanelBody`) are passed as component parameters. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor"::: -`PanelBody.cs`: +:::moniker-end -[!code-csharp[](~/blazor/samples/3.1/BlazorSample_WebAssembly/PanelBody.cs)] +:::moniker range="< aspnetcore-5.0" -`Shared/ParameterChild.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor)] +:::moniker-end > [!WARNING] -> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. +> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see <xref:blazor/components/overwriting-parameters>. The `Title` and `Body` component parameters of the `ParameterChild` component are set by arguments in the HTML tag that renders the instance of the component. The following `ParameterParent` component renders two `ParameterChild` components: @@ -2743,7 +512,29 @@ The `Title` and `Body` component parameters of the `ParameterChild` component ar `Pages/ParameterParent.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor"::: + +:::moniker-end The following rendered HTML markup from the `ParameterParent` component shows `ParameterChild` component default values when the `ParameterParent` component doesn't supply component parameter values. When the `ParameterParent` component provides component parameter values, they replace the `ParameterChild` component's default values. @@ -2766,17 +557,17 @@ The following rendered HTML markup from the `ParameterParent` component shows `P </div> ``` -Assign a C# field, property, or result of a method to a component parameter as an HTML attribute value using [Razor's reserved `@` symbol](xref:mvc/views/razor#razor-syntax). The following `ParameterParent2` component displays four instances of the preceding `ParameterChild` component and sets their `Title` parameter values to: +Assign a C# field, property, or result of a method to a component parameter as an HTML attribute value. The value of the attribute can typically be any C# expression that matches the type of the parameter. The value of the attribute can optionally lead with a [Razor reserved `@` symbol](xref:mvc/views/razor#razor-syntax), but it isn't required. + +If the component parameter is of type string, then the attribute value is instead treated as a C# string literal by default. If you want to specify a C# expression instead, then use the `@` prefix. + +The following `ParameterParent2` component displays four instances of the preceding `ParameterChild` component and sets their `Title` parameter values to: * The value of the `title` field. * The result of the `GetTitle` C# method. * The current local date in long format with <xref:System.DateTime.ToLongDateString%2A>, which uses an [implicit C# expression](xref:mvc/views/razor#implicit-razor-expressions). * The `panelData` object's `Title` property. -The `@` prefix is required for string parameters. Otherwise, the framework assumes that a string literal is set. - -Outside of string parameters, we recommend use the use of the `@` prefix for nonliterals, even when they aren't strictly required. - We don't recommend the use of the `@` prefix for literals (for example, boolean values), keywords (for example, `this`), or `null`, but you can choose to use them if you wish. For example, `IsFixed="@true"` is uncommon but supported. Quotes around parameter attribute values are optional in most cases per the HTML5 specification. For example, `Value=this` is supported, instead of `Value="this"`. However, we recommend using quotes because it's easier to remember and widely adopted across web-based technologies. @@ -2784,26 +575,56 @@ Quotes around parameter attribute values are optional in most cases per the HTML Throughout the documentation, code examples: * Always use quotes. Example: `Value="this"`. -* Nonliterals always use the `@` prefix, even when it's optional. Examples: `Title="@title"`, where `title` is a string-typed variable. `Count="@ct"`, where `ct` is a number-typed variable. -* Literals, outside of Razor expressions, always avoid `@`. Example: `IsFixed="true"`. +* Use the `@` prefix with nonliterals, ***even when it's optional***. Example: `Count="@ct"`, where `ct` is a number-typed variable. `Count="ct"` is a valid stylistic approach, but the documentation and examples don't adopt the convention. +* Always avoid `@` for literals, outside of Razor expressions. Example: `IsFixed="true"`. `Pages/ParameterParent2.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor"::: + +:::moniker-end > [!NOTE] -> When assigning a C# member to a component parameter, prefix the member with the `@` symbol and never prefix the parameter's HTML attribute. +> When assigning a C# member to a component parameter, don't prefix the parameter's HTML attribute with `@`. +> +> Correct (`Title` is a string parameter, `Count` is a number-typed parameter): > -> Correct: +> ```razor +> <ParameterChild Title="@title" Count="@ct" /> +> ``` > > ```razor -> <ParameterChild Title="@title" /> +> <ParameterChild Title="@title" Count="ct" /> > ``` > > Incorrect: > > ```razor -> <ParameterChild @Title="title" /> +> <ParameterChild @Title="@title" @Count="@ct" /> +> ``` +> +> ```razor +> <ParameterChild @Title="@title" @Count="ct" /> > ``` Unlike in Razor pages (`.cshtml`), Blazor can't perform asynchronous work in a Razor expression while rendering a component. This is because Blazor is designed for rendering interactive UIs. In an interactive UI, the screen must always display something, so it doesn't make sense to block the rendering flow. Instead, asynchronous work is performed during one of the [asynchronous lifecycle events](xref:blazor/components/lifecycle). After each asynchronous lifecycle event, the component may render again. The following Razor syntax is **not** supported: @@ -2822,7 +643,7 @@ To obtain a value for the `Title` parameter in the preceding example asynchronou <ParameterChild Title="@title" /> @code { - private string title; + private string? title; protected override async Task OnInitializedAsync() { @@ -2847,12 +668,34 @@ To support the assignment of a composed value, use a method, field, or property. `Pages/ParameterParent3.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor"::: + +:::moniker-end For more information, see <xref:mvc/views/razor>. > [!WARNING] -> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. +> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see <xref:blazor/components/overwriting-parameters>. Component parameters should be declared as *auto-properties*, meaning that they shouldn't contain custom logic in their `get` or `set` accessors. For example, the following `StartData` property is an auto-property: @@ -2877,243 +720,259 @@ Writing an initial value to a component parameter is supported because initial v public DateTime StartData { get; set; } = DateTime.Now; ``` -After the initial assignment of <xref:System.DateTime.Now?displayProperty=nameWithType>, do **not** assign a value to `StartData` in developer code. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. +After the initial assignment of <xref:System.DateTime.Now?displayProperty=nameWithType>, do **not** assign a value to `StartData` in developer code. For more information, see <xref:blazor/components/overwriting-parameters>. -## Route parameters +:::moniker range=">= aspnetcore-6.0" -Components can specify route parameters in the route template of the [`@page`][9] directive. The [Blazor router](xref:blazor/fundamentals/routing) uses route parameters to populate corresponding component parameters. +Apply the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) to specify a required component parameter. If a parameter value isn't provided, editors or build tools may display warnings to the user. This attribute is only valid on properties also marked with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). The <xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute> is enforced at design-time and when the app is built. The attribute isn't enforced at runtime, and it doesn't guarantee a non-`null` parameter value. -`Pages/RouteParameter.razor`: +```csharp +[Parameter] +[EditorRequired] +public string? Title { get; set; } +``` -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor?highlight=2,7-8)] +Single-line attribute lists are also supported: -Optional route parameters aren't supported, so two [`@page`][9] directives are applied in the preceding example. The first [`@page`][9] directive permits navigation to the component without a route parameter. The second [`@page`][9] directive receives the `{text}` route parameter and assigns the value to the `Text` property. +```csharp +[Parameter, EditorRequired] +public string? Title { get; set; } +``` -For information on catch-all route parameters (`{*pageRoute}`), which capture paths across multiple folder boundaries, see <xref:blazor/fundamentals/routing#catch-all-route-parameters>. +:::moniker-end -## Child content render fragments +:::moniker range=">= aspnetcore-7.0" -Components can set the content of another component. The assigning component provides the content between the child component's opening and closing tags. +Don't use the [`required` modifier](/dotnet/csharp/language-reference/keywords/required) or [`init` accessor](/dotnet/csharp/language-reference/keywords/init) on component parameter properties. Components are usually instantiated and assigned parameter values using reflection, which bypasses the guarantees that `init` and `required` are designed to make. Instead, use the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) to specify a required component parameter. -In the following example, the `RenderFragmentChild` component has a `ChildContent` component parameter that represents a segment of the UI to render as a <xref:Microsoft.AspNetCore.Components.RenderFragment>. The position of `ChildContent` in the component's Razor markup is where the content is rendered in the final HTML output. +:::moniker-end -`Shared/RenderFragmentChild.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor)] +Don't use the [`init` accessor](/dotnet/csharp/language-reference/keywords/init) on component parameter properties because setting component parameter values with <xref:Microsoft.AspNetCore.Components.ParameterView.SetParameterProperties%2A?displayProperty=nameWithType> uses reflection, which bypasses the init-only setter restriction. Use the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) to specify a required component parameter. -> [!IMPORTANT] -> The property receiving the <xref:Microsoft.AspNetCore.Components.RenderFragment> content must be named `ChildContent` by convention. -> -> [Event callbacks](xref:blazor/components/event-handling#eventcallback) aren't supported for <xref:Microsoft.AspNetCore.Components.RenderFragment>. +:::moniker-end -The following `RenderFragmentParent` component provides content for rendering the `RenderFragmentChild` by placing the content inside the child component's opening and closing tags. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -`Pages/RenderFragmentParent.razor`: +Don't use the [`init` accessor](/dotnet/csharp/language-reference/keywords/init) on component parameter properties because setting component parameter values with <xref:Microsoft.AspNetCore.Components.ParameterView.SetParameterProperties%2A?displayProperty=nameWithType> uses reflection, which bypasses the init-only setter restriction. -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor)] +:::moniker-end -Due to the way that Blazor renders child content, rendering components inside a [`for`](/dotnet/csharp/language-reference/keywords/for) loop requires a local index variable if the incrementing loop variable is used in the `RenderFragmentChild` component's content. The following example can be added to the preceding `RenderFragmentParent` component: +:::moniker range=">= aspnetcore-6.0" -```razor -<h1>Three children with an index variable</h1> +[`Tuples`](/dotnet/csharp/language-reference/builtin-types/value-tuples) ([API documentation](xref:System.Tuple)) are supported for component parameters and [`RenderFragment`](#child-content-render-fragments) types. The following component parameter example passes three values in a `Tuple`: -@for (int c = 0; c < 3; c++) -{ - var current = c; +`Shared/RenderTupleChild.razor`: - <RenderFragmentChild> - Count: @current - </RenderFragmentChild> +```csharp +<div class="card w-50" style="margin-bottom:15px"> + <div class="card-header font-weight-bold"><code>Tuple</code> Card</div> + <div class="card-body"> + <ul> + <li>Integer: @Data?.Item1</li> + <li>String: @Data?.Item2</li> + <li>Boolean: @Data?.Item3</li> + </ul> + </div> +</div> + +@code { + [Parameter] + public Tuple<int, string, bool>? Data { get; set; } } ``` -Alternatively, use a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop with <xref:System.Linq.Enumerable.Range%2A?displayProperty=nameWithType> instead of a [`for`](/dotnet/csharp/language-reference/keywords/for) loop. The following example can be added to the preceding `RenderFragmentParent` component: +`Pages/RenderTupleParent.razor`: -```razor -<h1>Second example of three children with an index variable</h1> +```csharp +@page "/render-tuple-parent" -@foreach (var c in Enumerable.Range(0,3)) -{ - <RenderFragmentChild> - Count: @c - </RenderFragmentChild> +<h1>Render <code>Tuple</code> Parent</h1> + +<RenderTupleChild Data="@data" /> + +@code { + private Tuple<int, string, bool> data = new(999, "I aim to misbehave.", true); } ``` + +Only ***unnamed tuples*** are supported for C# 7.0 or later in Razor components. [Named tuples](/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-field-names) support in Razor components is planned for a future ASP.NET Core release. For more information, see [Blazor Transpiler issue with named Tuples (dotnet/aspnetcore #28982)](https://github.com/dotnet/aspnetcore/issues/28982). -Render fragments are used to render child content throughout Blazor apps and are described with examples in the following articles and article sections: +Quote ©2005 [Universal Pictures](https://www.uphe.com): [Serenity](https://www.uphe.com/movies/serenity-2005) ([Nathan Fillion](https://www.imdb.com/name/nm0277213/)) -* [Blazor layouts](xref:blazor/components/layouts) -* [Pass data across a component hierarchy](xref:blazor/components/cascading-values-and-parameters#pass-data-across-a-component-hierarchy) -* [Templated components](xref:blazor/components/templated-components) -* [Global exception handling](xref:blazor/fundamentals/handle-errors#global-exception-handling) +:::moniker-end -> [!NOTE] -> Blazor framework's [built-in Razor components](xref:blazor/components/built-in-components) use the same `ChildContent` component parameter convention to set their content. You can see the components that set child content by searching for the component parameter property name `ChildContent` in the [API documentation (filters API with the search term "ChildContent")](/dotnet/api/?term=ChildContent). +## Route parameters -## Render fragments for reusable rendering logic +Components can specify route parameters in the route template of the [`@page`][9] directive. The [Blazor router](xref:blazor/fundamentals/routing) uses route parameters to populate corresponding component parameters. -You can factor out child components purely as a way of reusing rendering logic. In any component's `@code` block, define a <xref:Microsoft.AspNetCore.Components.RenderFragment> and render the fragment from any location as many times as needed: +:::moniker range=">= aspnetcore-5.0" -```razor -<h1>Hello, world!</h1> +Optional route parameters are supported. In the following example, the `text` optional parameter assigns the value of the route segment to the component's `Text` property. If the segment isn't present, the value of `Text` is set to "`fantastic`" in the [`OnInitialized` lifecycle method](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). -@RenderWelcomeInfo +:::moniker-end -<p>Render the welcome info a second time:</p> +:::moniker range="< aspnetcore-5.0" -@RenderWelcomeInfo +Optional route parameters aren't supported, so two [`@page`][9] directives are applied in the following example. The first [`@page`][9] directive permits navigation to the component without a route parameter. The second [`@page`][9] directive receives the `{text}` route parameter and assigns the value to the `Text` property. -@code { - private RenderFragment RenderWelcomeInfo = __builder => - { - <p>Welcome to your new app!</p> - }; -} -``` +:::moniker-end -For more information, see [Reuse rendering logic](xref:blazor/performance#define-reusable-renderfragments-in-code). +`Pages/RouteParameter.razor`: -## Overwritten parameters +:::moniker range=">= aspnetcore-7.0" -The Blazor framework generally imposes safe parent-to-child parameter assignment: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor" highlight="1,6-7"::: -* Parameters aren't overwritten unexpectedly. -* Side effects are minimized. For example, additional renders are avoided because they may create infinite rendering loops. +:::moniker-end -A child component receives new parameter values that possibly overwrite existing values when the parent component rerenders. Accidentally overwriting parameter values in a child component often occurs when developing the component with one or more data-bound parameters and the developer writes directly to a parameter in the child: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -* The child component is rendered with one or more parameter values from the parent component. -* The child writes directly to the value of a parameter. -* The parent component rerenders and overwrites the value of the child's parameter. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor" highlight="1,6-7"::: -The potential for overwriting parameter values extends into the child component's property `set` accessors, too. +:::moniker-end -> [!IMPORTANT] -> Our general guidance is not to create components that directly write to their own parameters after the component is rendered for the first time. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor" highlight="1,6-7"::: -Consider the following `Expander` component that: +:::moniker-end -* Renders child content. -* Toggles showing child content with a component parameter (`Expanded`). +:::moniker range="< aspnetcore-5.0" -After the following `Expander` component demonstrates an overwritten parameter, a modified `Expander` component is shown to demonstrate the correct approach for this scenario. The following examples can be placed in a local sample app to experience the behaviors described. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor" highlight="2,7-8"::: -`Shared/Expander.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/BadExpander.razor)] +For information on catch-all route parameters (`{*pageRoute}`), which capture paths across multiple folder boundaries, see <xref:blazor/fundamentals/routing#catch-all-route-parameters>. -The `Expander` component is added to the following `ExpanderExample` parent component that may call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>: +## Child content render fragments -* Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in developer code notifies a component that its state has changed and typically triggers component rerendering to update the UI. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is covered in more detail later in <xref:blazor/components/lifecycle> and <xref:blazor/components/rendering>. -* The button's `@onclick` directive attribute attaches an event handler to the button's `onclick` event. Event handling is covered in more detail later in <xref:blazor/components/event-handling>. +Components can set the content of another component. The assigning component provides the content between the child component's opening and closing tags. -`Pages/ExpanderExample.razor`: +In the following example, the `RenderFragmentChild` component has a `ChildContent` component parameter that represents a segment of the UI to render as a <xref:Microsoft.AspNetCore.Components.RenderFragment>. The position of `ChildContent` in the component's Razor markup is where the content is rendered in the final HTML output. -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ExpanderExample.razor)] +`Shared/RenderFragmentChild.razor`: -Initially, the `Expander` components behave independently when their `Expanded` properties are toggled. The child components maintain their states as expected. +:::moniker range=">= aspnetcore-7.0" -If <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called in a parent component, the Blazor framework rerenders child components if their parameters might have changed: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor"::: -* For a group of parameter types that Blazor explicitly checks, Blazor rerenders a child component if it detects that any of the parameters have changed. -* For unchecked parameter types, Blazor rerenders the child component *regardless of whether or not the parameters have changed*. Child content falls into this category of parameter types because child content is of type <xref:Microsoft.AspNetCore.Components.RenderFragment>, which is a delegate that refers to other mutable objects. +:::moniker-end -For the `ExpanderExample` component: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -* The first `Expander` component sets child content in a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment>, so a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component automatically rerenders the component and potentially overwrites the value of `Expanded` to its intitial value of `true`. -* The second `Expander` component doesn't set child content. Therefore, a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment> doesn't exist. A call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component doesn't automatically rerender the child component, so the component's `Expanded` value isn't overwritten. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor"::: -To maintain state in the preceding scenario, use a *private field* in the `Expander` component to maintain its toggled state. +:::moniker-end -The following revised `Expander` component: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* Accepts the `Expanded` component parameter value from the parent. -* Assigns the component parameter value to a *private field* (`expanded`) in the [`OnInitialized` event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). -* Uses the private field to maintain its internal toggle state, which demonstrates how to avoid writing directly to a parameter. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor"::: -> [!NOTE] -> The advice in this section extends to similar logic in component parameter `set` accessors, which can result in similar undesirable side effects. +:::moniker-end -`Shared/Expander.razor`: +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor"::: + +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/Expander.razor)] +> [!IMPORTANT] +> The property receiving the <xref:Microsoft.AspNetCore.Components.RenderFragment> content must be named `ChildContent` by convention. +> +> [Event callbacks](xref:blazor/components/event-handling#eventcallback) aren't supported for <xref:Microsoft.AspNetCore.Components.RenderFragment>. -For additional information, see [Blazor Two Way Binding Error (dotnet/aspnetcore #24599)](https://github.com/dotnet/aspnetcore/issues/24599). +The following `RenderFragmentParent` component provides content for rendering the `RenderFragmentChild` by placing the content inside the child component's opening and closing tags. -For more information on change detection, inlcuding information on the exact types that Blazor checks, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. +`Pages/RenderFragmentParent.razor`: -## Attribute splatting and arbitrary parameters +:::moniker range=">= aspnetcore-7.0" -Components can capture and render additional attributes in addition to the component's declared parameters. Additional attributes can be captured in a dictionary and then *splatted* onto an element when the component is rendered using the [`@attributes`][3] Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations. For example, it can be tedious to define attributes separately for an `<input>` that supports many parameters. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor"::: -In the following `Splat` component: +:::moniker-end -* The first `<input>` element (`id="useIndividualParams"`) uses individual component parameters. -* The second `<input>` element (`id="useAttributesDict"`) uses attribute splatting. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`Pages/Splat.razor`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/Splat.razor)] +:::moniker-end -The rendered `<input>` elements in the webpage are identical: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -```html -<input id="useIndividualParams" - maxlength="10" - placeholder="Input placeholder text" - required="required" - size="50"> - -<input id="useAttributesDict" - maxlength="10" - placeholder="Input placeholder text" - required="required" - size="50"> -``` +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor"::: -To accept arbitrary attributes, define a [component parameter](#component-parameters) with the <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property set to `true`: +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor"::: + +:::moniker-end + +Due to the way that Blazor renders child content, rendering components inside a [`for`](/dotnet/csharp/language-reference/keywords/for) loop requires a local index variable if the incrementing loop variable is used in the `RenderFragmentChild` component's content. The following example can be added to the preceding `RenderFragmentParent` component: ```razor -@code { - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary<string, object> InputAttributes { get; set; } -} -``` +<h1>Three children with an index variable</h1> -The <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property on [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) allows the parameter to match all attributes that don't match any other parameter. A component can only define a single parameter with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues>. The property type used with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> must be assignable from [`Dictionary<string, object>`](xref:System.Collections.Generic.Dictionary%602) with string keys. Use of [`IEnumerable<KeyValuePair<string, object>>`](xref:System.Collections.Generic.IEnumerable%601) or [`IReadOnlyDictionary<string, object>`](xref:System.Collections.Generic.IReadOnlyDictionary%602) are also options in this scenario. +@for (int c = 0; c < 3; c++) +{ + var current = c; -The position of [`@attributes`][3] relative to the position of element attributes is important. When [`@attributes`][3] are splatted on the element, the attributes are processed from right to left (last to first). Consider the following example of a parent component that consumes a child component: + <RenderFragmentChild> + Count: @current + </RenderFragmentChild> +} +``` -`Shared/AttributeOrderChild1.razor`: +Alternatively, use a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop with <xref:System.Linq.Enumerable.Range%2A?displayProperty=nameWithType> instead of a [`for`](/dotnet/csharp/language-reference/keywords/for) loop. The following example can be added to the preceding `RenderFragmentParent` component: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild1.razor)] +```razor +<h1>Second example of three children with an index variable</h1> -`Pages/AttributeOrderParent1.razor`: +@foreach (var c in Enumerable.Range(0,3)) +{ + <RenderFragmentChild> + Count: @c + </RenderFragmentChild> +} +``` -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent1.razor)] +Render fragments are used to render child content throughout Blazor apps and are described with examples in the following articles and article sections: -The `AttributeOrderChild1` component's `extra` attribute is set to the right of [`@attributes`][3]. The `AttributeOrderParent1` component's rendered `<div>` contains `extra="5"` when passed through the additional attribute because the attributes are processed right to left (last to first): +* [Blazor layouts](xref:blazor/components/layouts) +* [Pass data across a component hierarchy](xref:blazor/components/cascading-values-and-parameters#pass-data-across-a-component-hierarchy) +* [Templated components](xref:blazor/components/templated-components) +* [Global exception handling](xref:blazor/fundamentals/handle-errors#global-exception-handling) -```html -<div extra="5" /> -``` +> [!NOTE] +> Blazor framework's [built-in Razor components](xref:blazor/components/built-in-components) use the same `ChildContent` component parameter convention to set their content. You can see the components that set child content by searching for the component parameter property name `ChildContent` in the [API documentation (filters API with the search term "ChildContent")](/dotnet/api/?term=ChildContent). -In the following example, the order of `extra` and [`@attributes`][3] is reversed in the child component's `<div>`: +## Render fragments for reusable rendering logic -`Shared/AttributeOrderChild2.razor`: +You can factor out child components purely as a way of reusing rendering logic. In any component's `@code` block, define a <xref:Microsoft.AspNetCore.Components.RenderFragment> and render the fragment from any location as many times as needed: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild2.razor)] +```razor +<h1>Hello, world!</h1> -`Pages/AttributeOrderParent2.razor`: +@RenderWelcomeInfo -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent2.razor)] +<p>Render the welcome info a second time:</p> -The `<div>` in the parent component's rendered webpage contains `extra="10"` when passed through the additional attribute: +@RenderWelcomeInfo -```html -<div extra="10" /> +@code { + private RenderFragment RenderWelcomeInfo = __builder => + { + <p>Welcome to your new app!</p> + }; +} ``` +For more information, see [Reuse rendering logic](xref:blazor/performance#define-reusable-renderfragments-in-code). + ## Capture references to components Component references provide a way to reference a component instance for issuing commands. To capture a component reference: @@ -3127,305 +986,213 @@ Consider the following `ReferenceChild` component that logs a message when its ` `Shared/ReferenceChild.razor`: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor)] - -A component reference is only populated after the component is rendered and its output includes `ReferenceChild`'s element. Until the component is rendered, there's nothing to reference. - -To manipulate component references after the component has finished rendering, use the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). +:::moniker range=">= aspnetcore-7.0" -To use a reference variable with an event handler, use a lambda expression or assign the event handler delegate in the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). This ensures that the reference variable is assigned before the event handler is assigned. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor"::: -The following lambda approach uses the preceding `ReferenceChild` component. +:::moniker-end -`Pages/ReferenceParent1.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor)] +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor"::: -The following delegate approach uses the preceding `ReferenceChild` component. +:::moniker-end -`Pages/ReferenceParent2.razor`: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor)] +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor"::: -While capturing component references use a similar syntax to [capturing element references](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements), capturing component references isn't a JavaScript interop feature. Component references aren't passed to JavaScript code. Component references are only used in .NET code. +:::moniker-end -> [!IMPORTANT] -> Do **not** use component references to mutate the state of child components. Instead, use normal declarative component parameters to pass data to child components. Use of component parameters result in child components that rerender at the correct times automatically. For more information, see the [component parameters](#component-parameters) section and the <xref:blazor/components/data-binding> article. +:::moniker range="< aspnetcore-5.0" -## Synchronization context +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor"::: -Blazor uses a synchronization context (<xref:System.Threading.SynchronizationContext>) to enforce a single logical thread of execution. A component's [lifecycle methods](xref:blazor/components/lifecycle) and event callbacks raised by Blazor are executed on the synchronization context. +:::moniker-end -Blazor Server's synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. At any given point in time, work is performed on exactly one thread, which yields the impression of a single logical thread. No two operations execute concurrently. +A component reference is only populated after the component is rendered and its output includes `ReferenceChild`'s element. Until the component is rendered, there's nothing to reference. -### Avoid thread-blocking calls +To manipulate component references after the component has finished rendering, use the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). -Generally, don't call the following methods in components. The following methods block the execution thread and thus block the app from resuming work until the underlying <xref:System.Threading.Tasks.Task> is complete: +To use a reference variable with an event handler, use a lambda expression or assign the event handler delegate in the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). This ensures that the reference variable is assigned before the event handler is assigned. -* <xref:System.Threading.Tasks.Task%601.Result%2A> -* <xref:System.Threading.Tasks.Task.Wait%2A> -* <xref:System.Threading.Tasks.Task.WaitAny%2A> -* <xref:System.Threading.Tasks.Task.WaitAll%2A> -* <xref:System.Threading.Thread.Sleep%2A> -* <xref:System.Runtime.CompilerServices.TaskAwaiter.GetResult%2A> +The following lambda approach uses the preceding `ReferenceChild` component. -> [!NOTE] -> Blazor documentation examples that use the thread-blocking methods mentioned in this section are only using the methods for demonstration purposes, not as recommended coding guidance. For example, a few component code demonstrations simulate a long-running process by calling <xref:System.Threading.Thread.Sleep%2A?displayProperty=nameWithType>. +`Pages/ReferenceParent1.razor`: -### Invoke component methods externally to update state +:::moniker range=">= aspnetcore-7.0" -In the event a component must be updated based on an external event, such as a timer or other notification, use the `InvokeAsync` method, which dispatches code execution to Blazor's synchronization context. For example, consider the following *notifier service* that can notify any listening component about updated state. The `Update` method can be called from anywhere in the app. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor"::: -`TimerService.cs`: +:::moniker-end -[!code-csharp[](~/blazor/samples/3.1/BlazorSample_WebAssembly/TimerService.cs)] +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`NotifierService.cs`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor"::: -[!code-csharp[](~/blazor/samples/3.1/BlazorSample_WebAssembly/NotifierService.cs)] +:::moniker-end -Register the services: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* In a Blazor WebAssembly app, register the services as singletons in `Program.cs`: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor"::: - ```csharp - builder.Services.AddSingleton<NotifierService>(); - builder.Services.AddSingleton<TimerService>(); - ``` +:::moniker-end -* In a Blazor Server app, register the services as scoped in `Startup.ConfigureServices`: +:::moniker range="< aspnetcore-5.0" - ```csharp - services.AddScoped<NotifierService>(); - services.AddScoped<TimerService>(); - ``` +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor"::: -Use the `NotifierService` to update a component. +:::moniker-end -`Pages/ReceiveNotifications.razor`: +The following delegate approach uses the preceding `ReferenceChild` component. -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ReceiveNotifications.razor)] +`Pages/ReferenceParent2.razor`: -In the preceding example: +:::moniker range=">= aspnetcore-7.0" -* `NotifierService` invokes the component's `OnNotify` method outside of Blazor's synchronization context. `InvokeAsync` is used to switch to the correct context and queue a render. For more information, see <xref:blazor/components/rendering>. -* The component implements <xref:System.IDisposable>. The `OnNotify` delegate is unsubscribed in the `Dispose` method, which is called by the framework when the component is disposed. For more information, see <xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable>. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor"::: -## Use `@key` to control the preservation of elements and components +:::moniker-end -When rendering a list of elements or components and the elements or components subsequently change, Blazor must decide which of the previous elements or components can be retained and how model objects should map to them. Normally, this process is automatic and can be ignored, but there are cases where you may want to control the process. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -Consider the following `Details` and `People` components: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor"::: -* The `Details` component receives data (`Data`) from the parent `People` component, which is displayed in an `<input>` element. Any given displayed `<input>` element can receive the focus of the page from the user when they select one of the `<input>` elements. -* The `People` component creates a list of person objects for display using the `Details` component. Every three seconds, a new person is added to the collection. +:::moniker-end -This demonstration allows you to: +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* Select an `<input>` from among several rendered `Details` components. -* Study the behavior of the page's focus as the people collection automatically grows. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor"::: -`Shared/Details.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/Details.razor)] +:::moniker range="< aspnetcore-5.0" -In the following `People` component, each iteration of adding a person in `OnTimerCallback` results in Blazor rebuilding the entire collection. The page's focus remains on the *same index* position of `<input>` elements, so the focus shifts each time a person is added. *Shifting the focus away from what the user selected isn't desirable behavior.* After demonstrating the poor behavior with the following component, the [`@key`][5] directive attribute is used to improve the user's experience. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor"::: -`Pages/People.razor`: +:::moniker-end -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/People.razor)] +While capturing component references use a similar syntax to [capturing element references](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements), capturing component references isn't a JavaScript interop feature. Component references aren't passed to JavaScript code. Component references are only used in .NET code. -The contents of the `people` collection changes with inserted, deleted, or re-ordered entries. Rerendering can lead to visible behavior differences. Each time a person is inserted into the `people` collection, the *preceding element* of the currently focused element receives the focus. The user's focus is lost. +> [!IMPORTANT] +> Do **not** use component references to mutate the state of child components. Instead, use normal declarative component parameters to pass data to child components. Use of component parameters result in child components that rerender at the correct times automatically. For more information, see the [component parameters](#component-parameters) section and the <xref:blazor/components/data-binding> article. -The mapping process of elements or components to a collection can be controlled with the [`@key`][5] directive attribute. Use of [`@key`][5] guarantees the preservation of elements or components based on the key's value. If the `Details` component in the preceding example is keyed on the `person` item, Blazor ignores rerendering `Details` components that haven't changed. +## Apply an attribute -To modify the `People` component to use the [`@key`][5] directive attribute with the `people` collection, update the `<Details>` element to the following: +Attributes can be applied to components with the [`@attribute`][7] directive. The following example applies the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the component's class: ```razor -<Details @key="person" Data="@person.Data" /> +@page "/" +@attribute [Authorize] ``` -When the `people` collection changes, the association between `Details` instances and `person` instances is retained. When a `Person` is inserted at the beginning of the collection, one new `Details` instance is inserted at that corresponding position. Other instances are left unchanged. Therefore, the user's focus isn't lost as people are added to the collection. +## Conditional HTML element attributes -Other collection updates exhibit the same behavior when the [`@key`][5] directive attribute is used: +HTML element attribute properties are conditionally set based on the .NET value. If the value is `false` or `null`, the property isn't set. If the value is `true`, the property is set. -* If an instance is deleted from the collection, only the corresponding component instance is removed from the UI. Other instances are left unchanged. -* If collection entries are re-ordered, the corresponding component instances are preserved and re-ordered in the UI. +In the following example, `IsCompleted` determines if the `<input>` element's `checked` property is set. -> [!IMPORTANT] -> Keys are local to each container element or component. Keys aren't compared globally across the document. +`Pages/ConditionalAttribute.razor`: -### When to use `@key` +:::moniker range=">= aspnetcore-7.0" -Typically, it makes sense to use [`@key`][5] whenever a list is rendered (for example, in a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) block) and a suitable value exists to define the [`@key`][5]. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor"::: -You can also use [`@key`][5] to preserve an element or component subtree when an object doesn't change, as the following examples show. +:::moniker-end -Example 1: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```razor -<li @key="person"> - <input value="@person.Data" /> -</li> -``` +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor"::: -Example 2: +:::moniker-end -```razor -<div @key="person"> - @* other HTML elements *@ -</div> -``` +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -If an `person` instance changes, the [`@key`][5] attribute directive forces Blazor to: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor"::: -* Discard the entire `<li>` or `<div>` and their descendants. -* Rebuild the subtree within the UI with new elements and components. +:::moniker-end -This is useful to guarantee that no UI state is preserved when the collection changes within a subtree. +:::moniker range="< aspnetcore-5.0" -### Scope of `@key` +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor"::: -The [`@key`][5] attribute directive is scoped to its own siblings within its parent. +:::moniker-end -Consider the following example. The `first` and `second` keys are compared against each other within the same scope of the outer `<div>` element: +For more information, see <xref:mvc/views/razor>. -```razor -<div> - <div @key="first">...</div> - <div @key="second">...</div> -</div> -``` +> [!WARNING] +> Some HTML attributes, such as [`aria-pressed`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Roles/button_role#Toggle_buttons), don't function properly when the .NET type is a `bool`. In those cases, use a `string` type instead of a `bool`. -The following example demonstrates `first` and `second` keys in their own scopes, unrelated to each other and without influence on each other. Each [`@key`][5] scope only applies to its parent `<div>` element, not across the parent `<div>` elements: +## Raw HTML -```razor -<div> - <div @key="first">...</div> -</div> -<div> - <div @key="second">...</div> -</div> -``` +Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. To render raw HTML, wrap the HTML content in a <xref:Microsoft.AspNetCore.Components.MarkupString> value. The value is parsed as HTML or SVG and inserted into the DOM. -For the `Details` component shown earlier, the following examples render `person` data within the same [`@key`][5] scope and demonstrate typical use cases for [`@key`][5]: +> [!WARNING] +> Rendering raw HTML constructed from any untrusted source is a **security risk** and should **always** be avoided. -```razor -<div> - @foreach (var person in people) - { - <Details @key="person" Data="@person.Data" /> - } -</div> -``` +The following example shows using the <xref:Microsoft.AspNetCore.Components.MarkupString> type to add a block of static HTML content to the rendered output of a component. -```razor -@foreach (var person in people) -{ - <div @key="person"> - <Details Data="@person.Data" /> - </div> -} -``` +`Pages/MarkupStringExample.razor`: -```razor -<ol> - @foreach (var person in people) - { - <li @key="person"> - <Details Data="@person.Data" /> - </li> - } -</ol> -``` +:::moniker range=">= aspnetcore-7.0" -The following examples only scope [`@key`][5] to the `<div>` or `<li>` element that surrounds each `Details` component instance. Therefore, `person` data for each member of the `people` collection is **not** keyed on each `person` instance across the rendered `Details` components. Avoid the following patterns when using [`@key`][5]: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor"::: -```razor -@foreach (var person in people) -{ - <div> - <Details @key="person" Data="@person.Data" /> - </div> -} -``` +:::moniker-end -```razor -<ol> - @foreach (var person in people) - { - <li> - <Details @key="person" Data="@person.Data" /> - </li> - } -</ol> -``` +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -### When not to use `@key` +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor"::: -There's a performance cost when rendering with [`@key`][5]. The performance cost isn't large, but only specify [`@key`][5] if preserving the element or component benefits the app. +:::moniker-end -Even if [`@key`][5] isn't used, Blazor preserves child element and component instances as much as possible. The only advantage to using [`@key`][5] is control over *how* model instances are mapped to the preserved component instances, instead of Blazor selecting the mapping. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -### Values to use for `@key` +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor"::: -Generally, it makes sense to supply one of the following values for [`@key`][5]: +:::moniker-end -* Model object instances. For example, the `Person` instance (`person`) was used in the earlier example. This ensures preservation based on object reference equality. -* Unique identifiers. For example, unique identifiers can be based on primary key values of type `int`, `string`, or `Guid`. +:::moniker range="< aspnetcore-5.0" -Ensure that values used for [`@key`][5] don't clash. If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Only use distinct values, such as object instances or primary key values. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor"::: -## Apply an attribute +:::moniker-end -Attributes can be applied to components with the [`@attribute`][7] directive. The following example applies the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the component's class: +## Razor templates + +Render fragments can be defined using Razor template syntax to define a UI snippet. Razor templates use the following format: ```razor -@page "/" -@attribute [Authorize] +@<{HTML tag}>...</{HTML tag}> ``` -## Conditional HTML element attributes - -HTML element attribute properties are conditionally set based on the .NET value. If the value is `false` or `null`, the property isn't set. If the value is `true`, the property is set. - -In the following example, `IsCompleted` determines if the `<input>` element's `checked` property is set. - -`Pages/ConditionalAttribute.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor)] - -For more information, see <xref:mvc/views/razor>. +The following example illustrates how to specify <xref:Microsoft.AspNetCore.Components.RenderFragment> and <xref:Microsoft.AspNetCore.Components.RenderFragment%601> values and render templates directly in a component. Render fragments can also be passed as arguments to [templated components](xref:blazor/components/templated-components). -> [!WARNING] -> Some HTML attributes, such as [`aria-pressed`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Roles/button_role#Toggle_buttons), don't function properly when the .NET type is a `bool`. In those cases, use a `string` type instead of a `bool`. +`Pages/RazorTemplate.razor`: -## Raw HTML +:::moniker range=">= aspnetcore-7.0" -Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. To render raw HTML, wrap the HTML content in a <xref:Microsoft.AspNetCore.Components.MarkupString> value. The value is parsed as HTML or SVG and inserted into the DOM. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor"::: -> [!WARNING] -> Rendering raw HTML constructed from any untrusted source is a **security risk** and should **always** be avoided. +:::moniker-end -The following example shows using the <xref:Microsoft.AspNetCore.Components.MarkupString> type to add a block of static HTML content to the rendered output of a component. +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -`Pages/MarkupStringExample.razor`: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor)] +:::moniker-end -## Razor templates +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -Render fragments can be defined using Razor template syntax to define a UI snippet. Razor templates use the following format: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor"::: -```razor -@<{HTML tag}>...</{HTML tag}> -``` +:::moniker-end -The following example illustrates how to specify <xref:Microsoft.AspNetCore.Components.RenderFragment> and <xref:Microsoft.AspNetCore.Components.RenderFragment%601> values and render templates directly in a component. Render fragments can also be passed as arguments to [templated components](xref:blazor/components/templated-components). +:::moniker range="< aspnetcore-5.0" -`Pages/RazorTemplate.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor)] +:::moniker-end Rendered output of the preceding code: @@ -3468,8 +1235,71 @@ Similarly, SVG images are supported in the CSS rules of a stylesheet file (`.css } ``` +:::moniker range=">= aspnetcore-6.0" + +Blazor supports the [`<foreignObject>`](https://developer.mozilla.org/docs/Web/SVG/Element/foreignObject) element to display arbitrary HTML within an SVG. The markup can represent arbitrary HTML, a <xref:Microsoft.AspNetCore.Components.RenderFragment>, or a Razor component. + +The following example demonstrates: + +* Display of a `string` (`@message`). +* Two-way binding with an `<input>` element and a `value` field. +* A `Robot` component. + +```razor +<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> + <rect x="0" y="0" rx="10" ry="10" width="200" height="200" stroke="black" + fill="none" /> + <foreignObject x="20" y="20" width="160" height="160"> + <p>@message</p> + </foreignObject> +</svg> + +<svg xmlns="http://www.w3.org/2000/svg"> + <foreignObject width="200" height="200"> + <label> + Two-way binding: + <input @bind="value" @bind:event="oninput" /> + </label> + </foreignObject> +</svg> + +<svg xmlns="http://www.w3.org/2000/svg"> + <foreignObject> + <Robot /> + </foreignObject> +</svg> + +@code { + private string message = "Lorem ipsum dolor sit amet, consectetur adipiscing " + + "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + + private string? value; +} +``` + +:::moniker-end + ## Whitespace rendering behavior +:::moniker range=">= aspnetcore-5.0" + +Unless the [`@preservewhitespace`](xref:mvc/views/razor#preservewhitespace) directive is used with a value of `true`, extra whitespace is removed by default if: + +* Leading or trailing within an element. +* Leading or trailing within a <xref:Microsoft.AspNetCore.Components.RenderFragment>/<xref:Microsoft.AspNetCore.Components.RenderFragment%601> parameter (for example, child content passed to another component). +* It precedes or follows a C# code block, such as `@if` or `@foreach`. + +Whitespace removal might affect the rendered output when using a CSS rule, such as `white-space: pre`. To disable this performance optimization and preserve the whitespace, take one of the following actions: + +* Add the `@preservewhitespace true` directive at the top of the Razor file (`.razor`) to apply the preference to a specific component. +* Add the `@preservewhitespace true` directive inside an `_Imports.razor` file to apply the preference to a subdirectory or to the entire project. + +In most cases, no action is required, as apps typically continue to behave normally (but faster). If stripping whitespace causes a rendering problem for a particular component, use `@preservewhitespace true` in that component to disable this optimization. + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + Whitespace is retained in a component's source markup. Whitespace-only text renders in the browser's DOM even when there's no visual effect. Consider the following component markup: @@ -3505,33 +1335,44 @@ Whitespace isn't preserved from the preceding markup: <img alt="Example image" src="img.png" /> ``` -## Generic type parameter support +:::moniker-end -The [`@typeparam`][11] directive declares a [generic type parameter](/dotnet/csharp/programming-guide/generics/generic-type-parameters) for the generated component class: +:::moniker range=">= aspnetcore-6.0" -```razor -@typeparam TItem +## Render static root Razor components + +A *root Razor component* is the first component loaded of any component hierarchy created by the app. + +In an app created from the Blazor Server project template, the `App` component (`App.razor`) is created as the default root component in `Pages/_Host.cshtml` using the [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper): + +```cshtml +<component type="typeof(App)" render-mode="ServerPrerendered" /> ``` -In the following example, the `ListGenericTypeItems1` component is generically typed as `TExample`. +In an app created from the Blazor WebAssembly project template, the `App` component (`App.razor`) is created as the default root component in `Program.cs`: -`Shared/ListGenericTypeItems1.razor`: +```csharp +builder.RootComponents.Add<App>("#app"); +``` -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/index/ListGenericTypeItems1.razor)] +In the preceding code, the CSS selector, `#app`, indicates that the `App` component is created for the `<div>` in `wwwroot/index.html` with an `id` of `app`: -The following `GenericTypeExample1` component renders two `ListGenericTypeItems1` components: +```html +<div id="app">...</app> +``` -* String or integer data is assigned to the `ExampleList` parameter of each component. -* Type `string` or `int` that matches the type of the assigned data is set for the type parameter (`TExample`) of each component. +MVC and Razor Pages apps can also use the [Component Tag Helper](xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper) to register statically-rendered Blazor WebAssembly root components: -`Pages/GenericTypeExample1.razor`: +```cshtml +<component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> +``` -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/index/GenericTypeExample1.razor)] +Statically-rendered components can only be added to the app. They can't be removed or updated afterwards. -For more information, see the following articles: +For more information, see the following resources: -* <xref:mvc/views/razor#typeparam> -* <xref:blazor/components/templated-components> +* <xref:mvc/views/tag-helpers/builtin-th/component-tag-helper> +* <xref:blazor/components/prerendering-and-integration> :::moniker-end @@ -3546,4 +1387,3 @@ For more information, see the following articles: [8]: <xref:mvc/views/razor#namespace> [9]: <xref:mvc/views/razor#page> [10]: <xref:mvc/views/razor#bind> -[11]: <xref:mvc/views/razor#typeparam> diff --git a/aspnetcore/blazor/components/index/_static/quote.png b/aspnetcore/blazor/components/index/_static/quote.png new file mode 100644 index 000000000000..047cf7b05be9 Binary files /dev/null and b/aspnetcore/blazor/components/index/_static/quote.png differ diff --git a/aspnetcore/blazor/components/js-spa-frameworks.md b/aspnetcore/blazor/components/js-spa-frameworks.md new file mode 100644 index 000000000000..96f871296df9 --- /dev/null +++ b/aspnetcore/blazor/components/js-spa-frameworks.md @@ -0,0 +1,351 @@ +--- +title: Use Razor components in JavaScript apps and SPA frameworks +author: guardrex +description: Learn how to create and use Razor components in JavaScript apps and SPA frameworks. +monikerRange: '>= aspnetcore-6.0' +ms.author: riande +ms.custom: mvc +ms.date: 02/25/2023 +uid: blazor/components/js-spa-frameworks +--- +# Use Razor components in JavaScript apps and SPA frameworks + +[!INCLUDE[](~/includes/not-latest-version.md)] + +This article covers how to render Razor components from JavaScript, use Blazor custom elements, and generate Angular and React components. + +## Render Razor components from JavaScript + +Razor components can be dynamically-rendered from JavaScript (JS) for existing JS apps. + +The example in this section renders the following Razor component into a page via JS. + +`Shared/Quote.razor`: + +```razor +<div class="m-5 p-5"> + <h2>Quote</h2> + <p>@Text</p> +</div> + +@code { + [Parameter] + public string? Text { get; set; } +} +``` + +In `Program.cs`, add the namespace for the location of the component. The following example assumes that the `Quote` component is in the app's `Shared` folder, and the app's namespace is `BlazorSample`: + +```csharp +using BlazorSample.Shared; +``` + +Call <xref:Microsoft.AspNetCore.Components.Web.JSComponentConfigurationExtensions.RegisterForJavaScript%2A> on the app's root component collection to register the a Razor component as a root component for JS rendering. + +<xref:Microsoft.AspNetCore.Components.Web.JSComponentConfigurationExtensions.RegisterForJavaScript%2A> includes an overload that accepts the name of a JS function that executes initialization logic (`javaScriptInitializer`). The JS function is called once per component registration immediately after the Blazor app starts and before any components are rendered. This function can be used for integration with JS technologies, such as HTML custom elements or a JS-based SPA framework. + +One or more initializer functions can be created and called by different component registrations. The typical use case is to reuse the same initializer function for multiple components, which is expected if the initializer function is configuring integration with custom elements or another JS-based SPA framework. + +> [!IMPORTANT] +> Don't confuse the `javaScriptInitializer` parameter of <xref:Microsoft.AspNetCore.Components.Web.JSComponentConfigurationExtensions.RegisterForJavaScript%2A> with [JavaScript initializers](xref:blazor/fundamentals/startup#javascript-initializers). The name of the parameter and the JS initializers feature is coincidental. + +The following example demonstrates the dynamic registration of the preceding `Quote` component with "`quote`" as the identifier. + +* In a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`: + + ```csharp + builder.Services.AddServerSideBlazor(options => + { + options.RootComponents.RegisterForJavaScript<Quote>(identifier: "quote", + javaScriptInitializer: "initializeComponent"); + }); + ``` + +* In a Blazor WebAssembly app, call <xref:Microsoft.AspNetCore.Components.Web.JSComponentConfigurationExtensions.RegisterForJavaScript%2A> on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`: + + ```csharp + builder.RootComponents.RegisterForJavaScript<Quote>(identifier: "quote", + javaScriptInitializer: "initializeComponent"); + ``` + +Attach the initializer function with `name` and `parameters` function parameters to the `window` object. For demonstration purposes, the following `initializeComponent` function logs the name and parameters of the registered component. + +`wwwroot/js/jsComponentInitializers.js`: + +```javascript +window.initializeComponent = (name, parameters) => { + console.log({ name: name, parameters: parameters }); +} +``` + +Render the component from JS into a container element using the registered identifier, passing component parameters as needed. + +In the following example: + +* The `Quote` component (`quote` identifier) is rendered into the `quoteContainer` element when the `showQuote` function is called. +* A quote string is passed to the component's `Text` parameter. + +`wwwroot/js/scripts.js`: + +```javascript +async function showQuote() { + let targetElement = document.getElementById('quoteContainer'); + await Blazor.rootComponents.add(targetElement, 'quote', + { + text: "Crow: I have my doubts that this movie is actually 'starring' " + + "anybody. More like, 'camera is generally pointed at.'" + }); +} +``` + +Load Blazor (`blazor.server.js` or `blazor.webassembly.js`) with the preceding scripts into the JS app: + +```html +<script src="_framework/blazor.{server|webassembly}.js"></script> +<script src="js/jsComponentInitializers.js"></script> +<script src="js/scripts.js"></script> +``` + +In HTML, place the target container element (`quoteContainer`). For the demonstration in this section, a button triggers rendering the `Quote` component by calling the `showQuote` JS function: + +```html +<button onclick="showQuote()">Show Quote</button> + +<div id="quoteContainer"></div> +``` + +On initialization before any components are rendered, the browser's developer tools console logs the `Quote` component's identifier (`name`) and parameters (`parameters`) when `initializeComponent` is called: + +```console +Object { name: "quote", parameters: (1) […] } + name: "quote" + parameters: Array [ {…} ] + 0: Object { name: "Text", type: "string" } + length: 1 +``` + +When the **:::no-loc text="Show Quote":::** button is selected, the `Quote` component is rendered with the quote stored in `Text` displayed: + +![Quote rendered in the browser](~/blazor/components/index/_static/quote.png) + +Quote ©1988-1999 Satellite of Love LLC: [*Mystery Science Theater 3000*](https://mst3k.com/) ([Trace Beaulieu (Crow)](https://www.imdb.com/name/nm0064546/)) + +> [!NOTE] +> `rootComponents.add` returns an instance of the component. Call `dispose` on the instance to release it: +> +> ```javascript +> const rootComponent = await window.Blazor.rootComponents.add(...); +> +> ... +> +> rootComponent.dispose(); +> ``` + +The preceding example dynamically renders the root component when the `showQuote()` JS function is called. To render a root component into a container element when Blazor starts, use a [JavaScript initializer](xref:blazor/fundamentals/startup#javascript-initializers) to render the component, as the following example demonstrates. + +The following example builds on the preceding example, using the `Quote` component, the root component registration in `Program.cs`, and the initialization of `jsComponentInitializers.js`. The `showQuote()` function (and the `script.js` file) aren't used. + +In HTML, place the target container element, `quoteContainer2` for this example: + +```html +<div id="quoteContainer2"></div> +``` + +Using a [JavaScript initializer](xref:blazor/fundamentals/startup#javascript-initializers), add the root component to the target container element. + +`wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js`: + +```javascript +export function afterStarted(blazor) { + let targetElement = document.getElementById('quoteContainer2'); + blazor.rootComponents.add(targetElement, 'quote', + { + text: "Crow: I have my doubts that this movie is actually 'starring' " + + "anybody. More like, 'camera is generally pointed at.'" + }); +} +``` + +> [!NOTE] +> For the call to `rootComponents.add`, use the `blazor` parameter (lowercase `b`) provided by `afterStarted`. Although the registration is valid when using the `Blazor` object (uppercase `B`), the preferred approach is to use the parameter. + +For an advanced example with additional features, see the example in the `BasicTestApp` of the ASP.NET Core reference source (`dotnet/aspnetcore` GitHub repository): + +* [`JavaScriptRootComponents.razor`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/JavaScriptRootComponents.razor) +* [`wwwroot/js/jsRootComponentInitializers.js`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsRootComponentInitializers.js) +* [`wwwroot/index.html`](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/wwwroot/index.html) + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +## Blazor custom elements + +:::moniker range=">= aspnetcore-7.0" + +Use Blazor custom elements to dynamically render Razor components from other SPA frameworks, such as Angular or React. + +Blazor custom elements: + +* Use standard HTML interfaces to implement custom HTML elements. +* Eliminate the need to manually manage the state and lifecycle of root Razor components using JavaScript APIs. +* Are useful for gradually introducing Razor components into existing projects written in other SPA frameworks. + +Custom elements don't support [child content](xref:blazor/components/index#child-content-render-fragments) or [templated components](xref:blazor/components/templated-components). + +### Element name + +Per the [HTML specification](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-core-concepts), custom element tag names must adopt kebab case: + +<span aria-hidden="true">❌</span><span class="visually-hidden">Invalid:</span> `mycounter` +<span aria-hidden="true">❌</span><span class="visually-hidden">Invalid:</span> `MY-COUNTER` +<span aria-hidden="true">❌</span><span class="visually-hidden">Invalid:</span> `MyCounter` +<span aria-hidden="true">✔️</span><span class="visually-hidden">Valid:</span> `my-counter` +<span aria-hidden="true">✔️</span><span class="visually-hidden">Valid:</span> `my-cool-counter` + +### Package + +Add a package reference for [`Microsoft.AspNetCore.Components.CustomElements`](https://www.nuget.org/packages/Microsoft.AspNetCore.Components.CustomElements) to the app's project file. + +[!INCLUDE[](~/includes/package-reference.md)] + +### Blazor Server registration + +To register a root component as a custom element in a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`. The following example registers the `Counter` component with the custom HTML element `my-counter`: + +```csharp +builder.Services.AddServerSideBlazor(options => +{ + options.RootComponents.RegisterCustomElement<Counter>("my-counter"); +}); +``` + +> [!NOTE] +> The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +### Blazor WebAssembly registration + +To register a root component as a custom element in a Blazor WebAssembly app, call `RegisterCustomElement` on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`. The following example registers the `Counter` component with the custom HTML element `my-counter`: + +```csharp +builder.RootComponents.RegisterCustomElement<Counter>("my-counter"); +``` + +> [!NOTE] +> The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +### Use the registered custom element + +Use the custom element with any web framework. For example, the preceding `my-counter` custom HTML element that renders the app's `Counter` component is used in a React app with the following markup: + +```html +<my-counter></my-counter> +``` + +For a complete example of how to create custom elements with Blazor, see the [`CustomElementsComponent` component](https://github.com/dotnet/aspnetcore/blob/main/src/Components/test/testassets/BasicTestApp/CustomElementsComponent.razor) in the reference source. + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +### Pass parameters + +Pass parameters to your Blazor component either as HTML attributes or as JavaScript properties on the DOM element. + +The following `Counter` component uses an `IncrementAmount` parameter to set the increment amount of the **:::no-loc text="Click me":::** button. + +`Pages/Counter.razor`: + +```razor +@page "/counter" + +<h1>Counter</h1> + +<p role="status">Current count: @currentCount</p> + +<button class="btn btn-primary" @onclick="IncrementCount">Click me</button> + +@code { + private int currentCount = 0; + + [Parameter] + public int IncrementAmount { get; set; } = 1; + + private void IncrementCount() + { + currentCount += IncrementAmount; + } +} +``` + +Render the `Counter` component with the custom element and pass a value to the `IncrementAmount` parameter as an HTML attribute. The attribute name adopts kebab-case syntax (`increment-amount`, not `IncrementAmount`): + +```html +<my-counter increment-amount="10"></my-counter> +``` + +Alternatively, you can set the parameter's value as a JavaScript property on the element object. The property name adopts camel case syntax (`incrementAmount`, not `IncrementAmount`): + +```javascript +const elem = document.querySelector("my-counter"); +elem.incrementAmount = 10; +``` + +You can update parameter values at any time using either attribute or property syntax. + +Supported parameter types: + +* Using JavaScript property syntax, you can pass objects of any JSON-serializable type. +* Using HTML attributes, you are limited to passing objects of string, boolean, or numerical types. + +:::moniker-end + +:::moniker range="< aspnetcore-7.0" + +*Experimental* support is available for building custom elements using the [`Microsoft.AspNetCore.Components.CustomElements` NuGet package](https://www.nuget.org/packages/microsoft.aspnetcore.components.customelements). Custom elements use standard HTML interfaces to implement custom HTML elements. + +> [!WARNING] +> Experimental features are provided for the purpose of exploring feature viability and may not ship in a stable version. + +Register a root component as a custom element: + +* In a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`: + + ```csharp + builder.Services.AddServerSideBlazor(options => + { + options.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); + }); + ``` + + > [!NOTE] + > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +* In a Blazor WebAssembly app, call `RegisterAsCustomElement` on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`: + + ```csharp + builder.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); + ``` + + > [!NOTE] + > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +Include the following `<script>` tag in the app's HTML ***before*** the Blazor script tag: + +```html +<script src="/_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script> +``` + +Use the custom element with any web framework. For example, the preceding counter custom element is used in a React app with the following markup: + +```html +<my-counter increment-amount={incrementAmount}></my-counter> +``` + +> [!WARNING] +> The custom elements feature is currently **experimental, unsupported, and subject to change or be removed at any time**. We welcome your feedback on how well this particular approach meets your requirements. + +:::moniker-end + +## Generate Angular and React components + +Generate framework-specific JavaScript (JS) components from Razor components for web frameworks, such as Angular or React. This capability isn't included with .NET, but is enabled by the support for rendering Razor components from JS. The [JS component generation sample on GitHub](https://github.com/aspnet/samples/tree/main/samples/aspnetcore/blazor/JSComponentGeneration) demonstrates how to generate Angular and React components from Razor components. See the GitHub sample app's `README.md` file for additional information. + +> [!WARNING] +> The Angular and React component features are currently **experimental, unsupported, and subject to change or be removed at any time**. We welcome your feedback on how well this particular approach meets your requirements. diff --git a/aspnetcore/blazor/components/layouts.md b/aspnetcore/blazor/components/layouts.md index 2be624be1120..a939d9328de9 100644 --- a/aspnetcore/blazor/components/layouts.md +++ b/aspnetcore/blazor/components/layouts.md @@ -5,14 +5,14 @@ description: Learn how to create reusable layout components for Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 03/13/2023 uid: blazor/components/layouts --- # ASP.NET Core Blazor layouts -This article explains how to create reusable layout components for Blazor apps. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains how to create reusable layout components for Blazor apps. Some app elements, such as menus, copyright messages, and company logos, are usually part of app's overall presentation. Placing a copy of the markup for these elements into all of the components of an app isn't efficient. Every time that one of these elements is updated, every component that uses the element must be updated. This approach is costly to maintain and can lead to inconsistent content if an update is missed. *Layouts* solve these problems. @@ -35,247 +35,104 @@ The following `DoctorWhoLayout` component shows the Razor template of a layout c `Shared/DoctorWhoLayout.razor`: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor" highlight="1,13"::: - -### `MainLayout` component - -In an app created from a [Blazor project template](xref:blazor/project-structure), the `MainLayout` component is the app's [default layout](#apply-a-default-layout-to-an-app). - -`Shared/MainLayout.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor"::: - -[Blazor's CSS isolation feature](xref:blazor/components/css-isolation) applies isolated CSS styles to the `MainLayout` component. By convention, the styles are provided by the accompanying stylesheet of the same name, `Shared/MainLayout.razor.css`. The ASP.NET Core framework implementation of the stylesheet is available for inspection in the [ASP.NET Core reference source (dotnet/aspnetcore GitHub repository)](https://github.com/dotnet/aspnetcore/blob/main/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.razor.css). - -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -## Apply a layout - -### Apply a layout to a component - -Use the [`@layout`](xref:mvc/views/razor#layout) Razor directive to apply a layout to a routable Razor component that has an [`@page`](xref:mvc/views/razor#page) directive. The compiler converts `@layout` into a <xref:Microsoft.AspNetCore.Components.LayoutAttribute> and applies the attribute to the component class. - -The content of the following `Episodes` component is inserted into the `DoctorWhoLayout` at the position of `@Body`. - -`Pages/Episodes.razor`: - -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor" highlight="2"::: - -The following rendered HTML markup is produced by the preceding `DoctorWhoLayout` and `Episodes` component. Extraneous markup doesn't appear in order to focus on the content provided by the two components involved: - -* The **Doctor Who™ Episode Database** heading (`<h1>...</h1>`) in the header (`<header>...</header>`), navigation bar (`<nav>...</nav>`), and trademark information element (`<div>...</div>`) in the footer (`<footer>...</footer>`) come from the `DoctorWhoLayout` component. -* The **Episodes** heading (`<h2>...</h2>`) and episode list (`<ul>...</ul>`) come from the `Episodes` component. - -```html -<body> - <div id="app"> - <header> - <h1>Doctor Who™ Episode Database</h1> - </header> - - <nav> - <a href="main-list">Main Episode List</a> - <a href="search">Search</a> - <a href="new">Add Episode</a> - </nav> - - <h2>Episodes</h2> - - <ul> - <li>...</li> - <li>...</li> - <li>...</li> - </ul> - - <footer> - Doctor Who is a registered trademark of the BBC. - https://www.doctorwho.tv/ - </footer> - </div> -</body> -``` - -Specifying the layout directly in a component overrides a *default layout*: - -* Set by an `@layout` directive imported from an `_Imports` component (`_Imports.razor`), as described in the following [Apply a layout to a folder of components](#apply-a-layout-to-a-folder-of-components) section. -* Set as the app's default layout, as described in the [Apply a default layout to an app](#apply-a-default-layout-to-an-app) section later in this article. +:::moniker range=">= aspnetcore-7.0" -### Apply a layout to a folder of components +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor" highlight="1,13"::: -Every folder of an app can optionally contain a template file named `_Imports.razor`. The compiler includes the directives specified in the imports file in all of the Razor templates in the same folder and recursively in all of its subfolders. Therefore, an `_Imports.razor` file containing `@layout DoctorWhoLayout` ensures that all of the components in a folder use the `DoctorWhoLayout` component. There's no need to repeatedly add `@layout DoctorWhoLayout` to all of the Razor components (`.razor`) within the folder and subfolders. +:::moniker-end -`_Imports.razor`: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```razor -@layout DoctorWhoLayout -... -``` +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor" highlight="1,13"::: -The `_Imports.razor` file is similar to the [_ViewImports.cshtml file for Razor views and pages](xref:mvc/views/layout#importing-shared-directives) but applied specifically to Razor component files. +:::moniker-end -Specifying a layout in `_Imports.razor` overrides a layout specified as the router's [default app layout](#apply-a-default-layout-to-an-app), which is described in the following section. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -> [!WARNING] -> Do **not** add a Razor `@layout` directive to the root `_Imports.razor` file, which results in an infinite loop of layouts. To control the default app layout, specify the layout in the `Router` component. For more information, see the following [Apply a default layout to an app](#apply-a-default-layout-to-an-app) section. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor" highlight="1,13"::: -> [!NOTE] -> The [`@layout`](xref:mvc/views/razor#layout) Razor directive only applies a layout to routable Razor components with an [`@page`](xref:mvc/views/razor#page) directive. +:::moniker-end -### Apply a default layout to an app +:::moniker range="< aspnetcore-5.0" -Specify the default app layout in the `App` component's <xref:Microsoft.AspNetCore.Components.Routing.Router> component. The following example from an app based on a [Blazor project template](xref:blazor/project-structure) sets the default layout to the `MainLayout` component. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor" highlight="1,13"::: -`App.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/layouts/App1.razor" highlight="3"::: +### `MainLayout` component -For more information on the <xref:Microsoft.AspNetCore.Components.Routing.Router> component, see <xref:blazor/fundamentals/routing>. +In an app created from a [Blazor project template](xref:blazor/project-structure), the `MainLayout` component is the app's [default layout](#apply-a-default-layout-to-an-app). Blazor's layout adopts the [:::no-loc text="Flexbox"::: layout model (MDN documentation)](https://developer.mozilla.org/docs/Glossary/Flexbox) ([W3C specification](https://www.w3.org/TR/css-flexbox-1/)). -Specifying the layout as a default layout in the `Router` component is a useful practice because you can override the layout on a per-component or per-folder basis, as described in the preceding sections of this article. We recommend using the `Router` component to set the app's default layout because it's the most general and flexible approach for using layouts. +`Shared/MainLayout.razor`: -### Apply a layout to arbitrary content (`LayoutView` component) +:::moniker range=">= aspnetcore-7.0" -To set a layout for arbitrary Razor template content, specify the layout with a <xref:Microsoft.AspNetCore.Components.LayoutView> component. You can use a <xref:Microsoft.AspNetCore.Components.LayoutView> in any Razor component. The following example sets a layout component named `ErrorLayout` for the `MainLayout` component's <xref:Microsoft.AspNetCore.Components.Routing.Router.NotFound> template (`<NotFound>...</NotFound>`). +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor"::: -`App.razor`: +:::moniker-end -```razor -<Router AppAssembly="@typeof(Program).Assembly"> - <Found Context="routeData"> - <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> - </Found> - <NotFound> - <LayoutView Layout="@typeof(ErrorLayout)"> - <h1>Page not found</h1> - <p>Sorry, there's nothing at this address.</p> - </LayoutView> - </NotFound> -</Router> -``` +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -## Nested layouts +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor"::: -A component can reference a layout that in turn references another layout. For example, nested layouts are used to create a multi-level menu structures. +:::moniker-end -The following example shows how to use nested layouts. The `Episodes` component shown in the [Apply a layout to a component](#apply-a-layout-to-a-component) section is the component to display. The component references the `DoctorWhoLayout` component. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -The following `DoctorWhoLayout` component is a modified version of the example shown earlier in this article. The header and footer elements are removed, and the layout references another layout, `ProductionsLayout`. The `Episodes` component is rendered where `@Body` appears in the `DoctorWhoLayout`. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor"::: -`Shared/DoctorWhoLayout.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor" highlight="2,12"::: +:::moniker range="< aspnetcore-5.0" -The `ProductionsLayout` component contains the top-level layout elements, where the header (`<header>...</header>`) and footer (`<footer>...</footer>`) elements now reside. The `DoctorWhoLayout` with the `Episodes` component is rendered where `@Body` appears. +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor"::: -`Shared/ProductionsLayout.razor`: +:::moniker-end -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor" highlight="13"::: +:::moniker range=">= aspnetcore-5.0" -The following rendered HTML markup is produced by the preceding nested layout. Extraneous markup doesn't appear in order to focus on the nested content provided by the three components involved: +[Blazor's CSS isolation feature](xref:blazor/components/css-isolation) applies isolated CSS styles to the `MainLayout` component. By convention, the styles are provided by the accompanying stylesheet of the same name, `Shared/MainLayout.razor.css`. The ASP.NET Core framework implementation of the stylesheet is available for inspection in the ASP.NET Core reference source (`dotnet/aspnetcore` GitHub repository): -* The header (`<header>...</header>`), production navigation bar (`<nav>...</nav>`), and footer (`<footer>...</footer>`) elements and their content come from the `ProductionsLayout` component. -* The **Doctor Who™ Episode Database** heading (`<h1>...</h1>`), episode navigation bar (`<nav>...</nav>`), and trademark information element (`<div>...</div>`) come from the `DoctorWhoLayout` component. -* The **Episodes** heading (`<h2>...</h2>`) and episode list (`<ul>...</ul>`) come from the `Episodes` component. +* [Blazor Server `MainLayout.razor.css`](https://github.com/dotnet/aspnetcore/blob/release/7.0/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Shared/MainLayout.razor.css) +* [Blazor WebAssembly `MainLayout.razor.css`](https://github.com/dotnet/aspnetcore/blob/release/7.0/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.razor.css) -```html -<body> - <div id="app"> - <header> - <h1>Productions</h1> - </header> +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - <nav> - <a href="main-production-list">Main Production List</a> - <a href="production-search">Search</a> - <a href="new-production">Add Production</a> - </nav> +:::moniker-end - <h1>Doctor Who™ Episode Database</h1> +## Apply a layout - <nav> - <a href="episode-main-list">Main Episode List</a> - <a href="episode-search">Search</a> - <a href="new-episode">Add Episode</a> - </nav> +### Apply a layout to a component - <h2>Episodes</h2> +Use the [`@layout`](xref:mvc/views/razor#layout) Razor directive to apply a layout to a routable Razor component that has an [`@page`](xref:mvc/views/razor#page) directive. The compiler converts `@layout` into a <xref:Microsoft.AspNetCore.Components.LayoutAttribute> and applies the attribute to the component class. - <ul> - <li>...</li> - <li>...</li> - <li>...</li> - </ul> +The content of the following `Episodes` component is inserted into the `DoctorWhoLayout` at the position of `@Body`. - <div> - Doctor Who is a registered trademark of the BBC. - https://www.doctorwho.tv/ - </div> +`Pages/Episodes.razor`: - <footer> - Footer of Productions Layout - </footer> - </div> -</body> -``` +:::moniker range=">= aspnetcore-7.0" -## Share a Razor Pages layout with integrated components +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor" highlight="2"::: -When routable components are integrated into a Razor Pages app, the app's shared layout can be used with the components. For more information, see <xref:blazor/components/prerendering-and-integration>. +:::moniker-end -## Additional resources +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -* <xref:mvc/views/layout> +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor" highlight="2"::: :::moniker-end :::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -Some app elements, such as menus, copyright messages, and company logos, are usually part of app's overall presentation. Placing a copy of the markup for these elements into all of the components of an app isn't efficient. Every time that one of these elements is updated, every component that uses the element must be updated. This approach is costly to maintain and can lead to inconsistent content if an update is missed. *Layouts* solve these problems. - -A Blazor layout is a Razor component that shares markup with components that reference it. Layouts can use [data binding](xref:blazor/components/data-binding), [dependency injection](xref:blazor/fundamentals/dependency-injection), and other features of components. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor" highlight="2"::: -## Layout components - -### Create a layout component - -To create a layout component: - -* Create a Razor component defined by a Razor template or C# code. Layout components based on a Razor template use the `.razor` file extension just like ordinary Razor components. Because layout components are shared across an app's components, they're usually placed in the app's `Shared` folder. However, layouts can be placed in any location accessible to the components that use it. For example, a layout can be placed in the same folder as the components that use it. -* Inherit the component from <xref:Microsoft.AspNetCore.Components.LayoutComponentBase>. The <xref:Microsoft.AspNetCore.Components.LayoutComponentBase> defines a <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body> property (<xref:Microsoft.AspNetCore.Components.RenderFragment> type) for the rendered content inside the layout. -* Use the Razor syntax `@Body` to specify the location in the layout markup where the content is rendered. - -> [!NOTE] -> For more information on <xref:Microsoft.AspNetCore.Components.RenderFragment>, see <xref:blazor/components/index#child-content-render-fragments>. - -The following `DoctorWhoLayout` component shows the Razor template of a layout component. The layout inherits <xref:Microsoft.AspNetCore.Components.LayoutComponentBase> and sets the `@Body` between the navigation bar (`<nav>...</nav>`) and the footer (`<footer>...</footer>`). - -`Shared/DoctorWhoLayout.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor?highlight=1,13)] - -### `MainLayout` component - -In an app created from a [Blazor project template](xref:blazor/project-structure), the `MainLayout` component is the app's [default layout](#apply-a-default-layout-to-an-app). - -`Shared/MainLayout.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor)] - -[Blazor's CSS isolation feature](xref:blazor/components/css-isolation) applies isolated CSS styles to the `MainLayout` component. By convention, the styles are provided by the accompanying stylesheet of the same name, `Shared/MainLayout.razor.css`. The ASP.NET Core framework implementation of the stylesheet is available for inspection in the [ASP.NET Core reference source (dotnet/aspnetcore GitHub repository)](https://github.com/dotnet/aspnetcore/blob/main/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.razor.css). - -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] - -## Apply a layout - -### Apply a layout to a component - -Use the [`@layout`](xref:mvc/views/razor#layout) Razor directive to apply a layout to a routable Razor component that has an [`@page`](xref:mvc/views/razor#page) directive. The compiler converts `@layout` into a <xref:Microsoft.AspNetCore.Components.LayoutAttribute> and applies the attribute to the component class. +:::moniker-end -The content of the following `Episodes` component is inserted into the `DoctorWhoLayout` at the position of `@Body`. +:::moniker range="< aspnetcore-5.0" -`Pages/Episodes.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor" highlight="2"::: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor?highlight=2)] +:::moniker-end The following rendered HTML markup is produced by the preceding `DoctorWhoLayout` and `Episodes` component. Extraneous markup doesn't appear in order to focus on the content provided by the two components involved: @@ -343,10 +200,32 @@ Specify the default app layout in the `App` component's <xref:Microsoft.AspNetCo `App.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/layouts/App1.razor?highlight=3)] +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/layouts/App1.razor" highlight="3"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/layouts/App1.razor" highlight="3"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/layouts/App1.razor" highlight="3"::: [!INCLUDE[](~/blazor/includes/prefer-exact-matches.md)] +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/layouts/App1.razor" highlight="3"::: + +:::moniker-end + For more information on the <xref:Microsoft.AspNetCore.Components.Routing.Router> component, see <xref:blazor/fundamentals/routing>. Specifying the layout as a default layout in the `Router` component is a useful practice because you can override the layout on a per-component or per-folder basis, as described in the preceding sections of this article. We recommend using the `Router` component to set the app's default layout because it's the most general and flexible approach for using layouts. @@ -358,9 +237,9 @@ To set a layout for arbitrary Razor template content, specify the layout with a `App.razor`: ```razor -<Router AppAssembly="@typeof(Program).Assembly"> - <Found Context="routeData"> - <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> +<Router ...> + <Found ...> + ... </Found> <NotFound> <LayoutView Layout="@typeof(ErrorLayout)"> @@ -371,8 +250,12 @@ To set a layout for arbitrary Razor template content, specify the layout with a </Router> ``` +:::moniker range="= aspnetcore-5.0" + [!INCLUDE[](~/blazor/includes/prefer-exact-matches.md)] +:::moniker-end + ## Nested layouts A component can reference a layout that in turn references another layout. For example, nested layouts are used to create a multi-level menu structures. @@ -383,224 +266,57 @@ The following `DoctorWhoLayout` component is a modified version of the example s `Shared/DoctorWhoLayout.razor`: -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor?highlight=2,12)] - -The `ProductionsLayout` component contains the top-level layout elements, where the header (`<header>...</header>`) and footer (`<footer>...</footer>`) elements now reside. The `DoctorWhoLayout` with the `Episodes` component is rendered where `@Body` appears. - -`Shared/ProductionsLayout.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor?highlight=13)] - -The following rendered HTML markup is produced by the preceding nested layout. Extraneous markup doesn't appear in order to focus on the nested content provided by the three components involved: - -* The header (`<header>...</header>`), production navigation bar (`<nav>...</nav>`), and footer (`<footer>...</footer>`) elements and their content come from the `ProductionsLayout` component. -* The **Doctor Who™ Episode Database** heading (`<h1>...</h1>`), episode navigation bar (`<nav>...</nav>`), and trademark information element (`<div>...</div>`) come from the `DoctorWhoLayout` component. -* The **Episodes** heading (`<h2>...</h2>`) and episode list (`<ul>...</ul>`) come from the `Episodes` component. - -```html -<body> - <div id="app"> - <header> - <h1>Productions</h1> - </header> - - <nav> - <a href="main-production-list">Main Production List</a> - <a href="production-search">Search</a> - <a href="new-production">Add Production</a> - </nav> +:::moniker range=">= aspnetcore-7.0" - <h1>Doctor Who™ Episode Database</h1> - - <nav> - <a href="episode-main-list">Main Episode List</a> - <a href="episode-search">Search</a> - <a href="new-episode">Add Episode</a> - </nav> - - <h2>Episodes</h2> - - <ul> - <li>...</li> - <li>...</li> - <li>...</li> - </ul> +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor" highlight="2,12"::: - <div> - Doctor Who is a registered trademark of the BBC. - https://www.doctorwho.tv/ - </div> +:::moniker-end - <footer> - Footer of Productions Layout - </footer> - </div> -</body> -``` +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -## Share a Razor Pages layout with integrated components +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor" highlight="2,12"::: -When routable components are integrated into a Razor Pages app, the app's shared layout can be used with the components. For more information, see <xref:blazor/components/prerendering-and-integration>. +:::moniker-end -## Additional resources +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -* <xref:mvc/views/layout> +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor" highlight="2,12"::: :::moniker-end :::moniker range="< aspnetcore-5.0" -Some app elements, such as menus, copyright messages, and company logos, are usually part of app's overall presentation. Placing a copy of the markup for these elements into all of the components of an app isn't efficient. Every time that one of these elements is updated, every component that uses the element must be updated. This approach is costly to maintain and can lead to inconsistent content if an update is missed. *Layouts* solve these problems. - -A Blazor layout is a Razor component that shares markup with components that reference it. Layouts can use [data binding](xref:blazor/components/data-binding), [dependency injection](xref:blazor/fundamentals/dependency-injection), and other features of components. - -## Layout components - -### Create a layout component - -To create a layout component: - -* Create a Razor component defined by a Razor template or C# code. Layout components based on a Razor template use the `.razor` file extension just like ordinary Razor components. Because layout components are shared across an app's components, they're usually placed in the app's `Shared` folder. However, layouts can be placed in any location accessible to the components that use it. For example, a layout can be placed in the same folder as the components that use it. -* Inherit the component from <xref:Microsoft.AspNetCore.Components.LayoutComponentBase>. The <xref:Microsoft.AspNetCore.Components.LayoutComponentBase> defines a <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body> property (<xref:Microsoft.AspNetCore.Components.RenderFragment> type) for the rendered content inside the layout. -* Use the Razor syntax `@Body` to specify the location in the layout markup where the content is rendered. - -> [!NOTE] -> For more information on <xref:Microsoft.AspNetCore.Components.RenderFragment>, see <xref:blazor/components/index#child-content-render-fragments>. - -The following `DoctorWhoLayout` component shows the Razor template of a layout component. The layout inherits <xref:Microsoft.AspNetCore.Components.LayoutComponentBase> and sets the `@Body` between the navigation bar (`<nav>...</nav>`) and the footer (`<footer>...</footer>`). - -`Shared/DoctorWhoLayout.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor?highlight=1,13)] - -### `MainLayout` component - -In an app created from a [Blazor project template](xref:blazor/project-structure), the `MainLayout` component is the app's [default layout](#apply-a-default-layout-to-an-app). - -`Shared/MainLayout.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor)] - -## Apply a layout - -### Apply a layout to a component - -Use the [`@layout`](xref:mvc/views/razor#layout) Razor directive to apply a layout to a routable Razor component that has an [`@page`](xref:mvc/views/razor#page) directive. The compiler converts `@layout` into a <xref:Microsoft.AspNetCore.Components.LayoutAttribute> and applies the attribute to the component class. - -The content of the following `Episodes` component is inserted into the `DoctorWhoLayout` at the position of `@Body`. - -`Pages/Episodes.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor?highlight=2)] - -The following rendered HTML markup is produced by the preceding `DoctorWhoLayout` and `Episodes` component. Extraneous markup doesn't appear in order to focus on the content provided by the two components involved: - -* The **Doctor Who™ Episode Database** heading (`<h1>...</h1>`) in the header (`<header>...</header>`), navigation bar (`<nav>...</nav>`), and trademark information element (`<div>...</div>`) in the footer (`<footer>...</footer>`) come from the `DoctorWhoLayout` component. -* The **Episodes** heading (`<h2>...</h2>`) and episode list (`<ul>...</ul>`) come from the `Episodes` component. - -```html -<body> - <div id="app"> - <header> - <h1>Doctor Who™ Episode Database</h1> - </header> - - <nav> - <a href="main-list">Main Episode List</a> - <a href="search">Search</a> - <a href="new">Add Episode</a> - </nav> - - <h2>Episodes</h2> - - <ul> - <li>...</li> - <li>...</li> - <li>...</li> - </ul> - - <footer> - Doctor Who is a registered trademark of the BBC. - https://www.doctorwho.tv/ - </footer> - </div> -</body> -``` - -Specifying the layout directly in a component overrides a *default layout*: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor" highlight="2,12"::: -* Set by an `@layout` directive imported from an `_Imports` component (`_Imports.razor`), as described in the following [Apply a layout to a folder of components](#apply-a-layout-to-a-folder-of-components) section. -* Set as the app's default layout, as described in the [Apply a default layout to an app](#apply-a-default-layout-to-an-app) section later in this article. - -### Apply a layout to a folder of components - -Every folder of an app can optionally contain a template file named `_Imports.razor`. The compiler includes the directives specified in the imports file in all of the Razor templates in the same folder and recursively in all of its subfolders. Therefore, an `_Imports.razor` file containing `@layout DoctorWhoLayout` ensures that all of the components in a folder use the `DoctorWhoLayout` component. There's no need to repeatedly add `@layout DoctorWhoLayout` to all of the Razor components (`.razor`) within the folder and subfolders. - -`_Imports.razor`: - -```razor -@layout DoctorWhoLayout -... -``` - -The `_Imports.razor` file is similar to the [_ViewImports.cshtml file for Razor views and pages](xref:mvc/views/layout#importing-shared-directives) but applied specifically to Razor component files. - -Specifying a layout in `_Imports.razor` overrides a layout specified as the router's [default app layout](#apply-a-default-layout-to-an-app), which is described in the following section. - -> [!WARNING] -> Do **not** add a Razor `@layout` directive to the root `_Imports.razor` file, which results in an infinite loop of layouts. To control the default app layout, specify the layout in the `Router` component. For more information, see the following [Apply a default layout to an app](#apply-a-default-layout-to-an-app) section. - -The [`@layout`](xref:mvc/views/razor#layout) Razor directive only applies a layout to routable Razor components with an [`@page`](xref:mvc/views/razor#page) directive. - -### Apply a default layout to an app - -Specify the default app layout in the `App` component's <xref:Microsoft.AspNetCore.Components.Routing.Router> component. The following example from an app based on a [Blazor project template](xref:blazor/project-structure) sets the default layout to the `MainLayout` component. - -`App.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/layouts/App1.razor?highlight=3)] - -For more information on the <xref:Microsoft.AspNetCore.Components.Routing.Router> component, see <xref:blazor/fundamentals/routing>. +:::moniker-end -Specifying the layout as a default layout in the `Router` component is a useful practice because you can override the layout on a per-component or per-folder basis, as described in the preceding sections of this article. We recommend using the `Router` component to set the app's default layout because it's the most general and flexible approach for using layouts. +The `ProductionsLayout` component contains the top-level layout elements, where the header (`<header>...</header>`) and footer (`<footer>...</footer>`) elements now reside. The `DoctorWhoLayout` with the `Episodes` component is rendered where `@Body` appears. -### Apply a layout to arbitrary content (`LayoutView` component) +`Shared/ProductionsLayout.razor`: -To set a layout for arbitrary Razor template content, specify the layout with a <xref:Microsoft.AspNetCore.Components.LayoutView> component. You can use a <xref:Microsoft.AspNetCore.Components.LayoutView> in any Razor component. The following example sets a layout component named `ErrorLayout` for the `MainLayout` component's <xref:Microsoft.AspNetCore.Components.Routing.Router.NotFound> template (`<NotFound>...</NotFound>`). +:::moniker range=">= aspnetcore-7.0" -`App.razor`: +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor" highlight="13"::: -```razor -<Router AppAssembly="@typeof(Program).Assembly"> - <Found Context="routeData"> - <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> - </Found> - <NotFound> - <LayoutView Layout="@typeof(ErrorLayout)"> - <h1>Page not found</h1> - <p>Sorry, there's nothing at this address.</p> - </LayoutView> - </NotFound> -</Router> -``` +:::moniker-end -## Nested layouts +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -A component can reference a layout that in turn references another layout. For example, nested layouts are used to create a multi-level menu structures. +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor" highlight="13"::: -The following example shows how to use nested layouts. The `Episodes` component shown in the [Apply a layout to a component](#apply-a-layout-to-a-component) section is the component to display. The component references the `DoctorWhoLayout` component. +:::moniker-end -The following `DoctorWhoLayout` component is a modified version of the example shown earlier in this article. The header and footer elements are removed, and the layout references another layout, `ProductionsLayout`. The `Episodes` component is rendered where `@Body` appears in the `DoctorWhoLayout`. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -`Shared/DoctorWhoLayout.razor`: +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor" highlight="13"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor?highlight=2,12)] +:::moniker-end -The `ProductionsLayout` component contains the top-level layout elements, where the header (`<header>...</header>`) and footer (`<footer>...</footer>`) elements now reside. The `DoctorWhoLayout` with the `Episodes` component is rendered where `@Body` appears. +:::moniker range="< aspnetcore-5.0" -`Shared/ProductionsLayout.razor`: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor" highlight="13"::: -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor?highlight=13)] +:::moniker-end The following rendered HTML markup is produced by the preceding nested layout. Extraneous markup doesn't appear in order to focus on the nested content provided by the three components involved: @@ -624,7 +340,7 @@ The following rendered HTML markup is produced by the preceding nested layout. E <h1>Doctor Who™ Episode Database</h1> <nav> - <a href="episode-main-list">Main Episode List</a> + <a href="main-episode-list">Main Episode List</a> <a href="episode-search">Search</a> <a href="new-episode">Add Episode</a> </nav> @@ -653,8 +369,15 @@ The following rendered HTML markup is produced by the preceding nested layout. E When routable components are integrated into a Razor Pages app, the app's shared layout can be used with the components. For more information, see <xref:blazor/components/prerendering-and-integration>. -## Additional resources +:::moniker range=">= aspnetcore-8.0" -* <xref:mvc/views/layout> +## Sections + +To control the content in a layout from a child Razor component, see <xref:blazor/components/sections>. :::moniker-end + +## Additional resources + +* <xref:mvc/views/layout> +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) diff --git a/aspnetcore/blazor/components/lifecycle.md b/aspnetcore/blazor/components/lifecycle.md index 9288d7f05d57..459574b2f077 100644 --- a/aspnetcore/blazor/components/lifecycle.md +++ b/aspnetcore/blazor/components/lifecycle.md @@ -5,20 +5,24 @@ description: Learn about the ASP.NET Core Razor component lifecycle and how to u monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/lifecycle --- # ASP.NET Core Razor component lifecycle -This article explains the ASP.NET Core Razor component lifecycle and how to use lifecycle events. +[!INCLUDE[](~/includes/not-latest-version.md)] -:::moniker range=">= aspnetcore-6.0" +This article explains the ASP.NET Core Razor component lifecycle and how to use lifecycle events. The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering. +This article simplifies component lifecycle event processing in order to clarify complex framework logic. You may need to access the [`ComponentBase` reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs) to integrate custom event processing with Blazor's lifecycle event processing. Code comments in the reference source include additional remarks on lifecycle event processing that don't appear in this article or in the [API documentation](/dotnet/api/). Blazor's lifecycle event processing has changed over time and is subject to change without notice each release. + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + ## Lifecycle events -The following diagrams illustrate Razor component lifecycle events. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article. +The following simplified diagrams illustrate Razor component lifecycle event processing. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article. Component lifecycle events: @@ -32,6 +36,8 @@ Component lifecycle events: > [!NOTE] > Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the [Handle incomplete async actions at render](#handle-incomplete-async-actions-at-render) section later in this article. +A parent component renders before its children components because rendering is what determines which children are present. If synchronous parent component initialization is used, the parent initialization is guaranteed to complete first. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running. + ![Component lifecycle events of a Razor component in Blazor](~/blazor/components/lifecycle/_static/lifecycle1.png) Document Object Model (DOM) event processing: @@ -69,22 +75,68 @@ If event handlers are provided in developer code, unhook them on disposal. For m In the following example, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A?displayProperty=nameWithType> assigns the `Param` parameter's value to `value` if parsing a route parameter for `Param` is successful. When `value` isn't `null`, the value is displayed by the component. -Although [route parameter matching is case insensitive](xref:blazor/fundamentals/routing#route-parameters), <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> only matches case sensitive parameter names in the route template. The following example requires the use of `/{Param?}` in the route template in order to get the value with <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A>, not `/{param?}`. If `/{param?}` is used in this scenario, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> returns `false` and `message` isn't set to either `message` string. +Although [route parameter matching is case insensitive](xref:blazor/fundamentals/routing#route-parameters), <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> only matches case-sensitive parameter names in the route template. The following example requires the use of `/{Param?}` in the route template in order to get the value with <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A>, not `/{param?}`. If `/{param?}` is used in this scenario, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> returns `false` and `message` isn't set to either `message` string. `Pages/SetParamsAsync.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor"::: + +:::moniker-end + ## Component initialization (`OnInitialized{Async}`) <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> are invoked when the component is initialized after having received its initial parameters in <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A>. +If synchronous parent component initialization is used, the parent initialization is guaranteed to complete before child component initialization. If asynchronous parent component initialization is used, the completion order of parent and child component initialization can't be determined because it depends on the initialization code running. + For a synchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A>: `Pages/OnInit.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor" highlight="8"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor" highlight="8"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor" highlight="8"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor" highlight="8"::: + +:::moniker-end + To perform an asynchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> and use the [`await`](/dotnet/csharp/language-reference/operators/await) operator: ```csharp @@ -101,7 +153,7 @@ Blazor apps that prerender their content on the server call <xref:Microsoft.AspN To prevent developer code in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> from running twice when prerendering, see the [Stateful reconnection after prerendering](#stateful-reconnection-after-prerendering) section. Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. -While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the [Detect when the app is prerendering](#detect-when-the-app-is-prerendering) section. +While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the [Prerendering with JavaScript interop](#prerendering-with-javascript-interop) section. If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. @@ -123,13 +175,39 @@ For the following example component, navigate to the component's page at a URL: * With a start date that's received by `StartDate`: `/on-parameters-set/2021-03-19` * Without a start date, where `StartDate` is assigned a value of the current local time: `/on-parameters-set` -`Pages/OnParamsSet.razor`: +:::moniker range=">= aspnetcore-5.0" > [!NOTE] > In a component route, it isn't possible to both constrain a <xref:System.DateTime> parameter with the [route constraint `datetime`](xref:blazor/fundamentals/routing#route-constraints) and [make the parameter optional](xref:blazor/fundamentals/routing#route-parameters). Therefore, the following `OnParamsSet` component uses two [`@page`](xref:mvc/views/razor#page) directives to handle routing with and without a supplied date segment in the URL. +:::moniker-end + +`Pages/OnParamsSet.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor"::: + +:::moniker-end + Asynchronous work when applying parameters and property values must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> lifecycle event: ```csharp @@ -154,8 +232,30 @@ The `firstRender` parameter for <xref:Microsoft.AspNetCore.Components.ComponentB `Pages/AfterRender.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor"::: + +:::moniker-end + Asynchronous work immediately after rendering must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> lifecycle event: ```csharp @@ -193,8 +293,30 @@ In the `FetchData` component of the Blazor templates, <xref:Microsoft.AspNetCore `Pages/FetchData.razor` in the Blazor Server template: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_Server/Pages/lifecycle/FetchData.razor" highlight="9,21,25"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_Server/Pages/lifecycle/FetchData.razor" highlight="9,21,25"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_Server/Pages/lifecycle/FetchData.razor" highlight="9,21,25"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_Server/Pages/lifecycle/FetchData.razor" highlight="9,21,25"::: + +:::moniker-end + ## Handle errors For information on handling errors during lifecycle method execution, see <xref:blazor/fundamentals/handle-errors>. @@ -212,13 +334,35 @@ The following code demonstrates an updated `WeatherForecastService` in a templat `WeatherForecastService.cs`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_Server/lifecycle/WeatherForecastService.cs"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_Server/lifecycle/WeatherForecastService.cs"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_Server/lifecycle/WeatherForecastService.cs"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_Server/lifecycle/WeatherForecastService.cs"::: + +:::moniker-end + For more information on the <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode>, see <xref:blazor/fundamentals/signalr#render-mode-blazor-server>. Although the content in this section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. -## Detect when the app is prerendering +## Prerendering with JavaScript interop [!INCLUDE[](~/blazor/includes/prerendering.md)] @@ -230,6 +374,29 @@ Components shouldn't need to implement <xref:System.IDisposable> and <xref:Syste Developer code must ensure that <xref:System.IAsyncDisposable> implementations don't take a long time to complete. +### Disposal of JavaScript interop object references + +Examples throughout the [JavaScript (JS) interop articles](xref:blazor/js-interop/index) demonstrate typical object disposal patterns: + +* When calling JS from .NET, as described in <xref:blazor/js-interop/call-javascript-from-dotnet>, dispose any created <xref:Microsoft.JSInterop.IJSObjectReference>/<xref:Microsoft.JSInterop.IJSInProcessObjectReference>/`JSObjectReference` either from .NET or from JS to avoid leaking JS memory. + +* When calling .NET from JS, as described in <xref:blazor/js-interop/call-dotnet-from-javascript>, dispose of a created <xref:Microsoft.JSInterop.DotNetObjectReference> either from .NET or from JS to avoid leaking .NET memory. + +JS interop object references are implemented as a map keyed by an identifier on the side of the JS interop call that creates the reference. When object disposal is initiated from either the .NET or JS side, Blazor removes the entry from the map, and the object can be garbage collected as long as no other strong reference to the object is present. + +At a minimum, always dispose objects created on the .NET side to avoid leaking .NET managed memory. + +### Document Object Model (DOM) cleanup tasks during component disposal + +Don't execute JS interop code for DOM cleanup tasks during component disposal. Instead, use the [`MutationObserver`](https://developer.mozilla.org/docs/Web/API/MutationObserver) pattern in JavaScript on the client for the following reasons: + +* The component may have been removed from the DOM by the time your cleanup code executes in `Dispose{Async}`. +* In a Blazor Server app, the Blazor renderer may have been disposed by the framework by the time your cleanup code executes in `Dispose{Async}`. + +The [`MutationObserver`](https://developer.mozilla.org/docs/Web/API/MutationObserver) pattern allows you to run a function when an element is removed from the DOM. + +For guidance on <xref:Microsoft.JSInterop.JSDisconnectedException> in Blazor Server apps when a circuit is disconnected, see <xref:blazor/js-interop/call-javascript-from-dotnet#javascript-interop-calls-without-a-circuit> or <xref:blazor/js-interop/call-dotnet-from-javascript#javascript-interop-calls-without-a-circuit>. For general JavaScript interop error handling guidance, see the *JavaScript interop* section in <xref:blazor/fundamentals/handle-errors>. <!-- AUTHOR NOTE: The JavaScript interop section isn't linked because the section title changed across versions of the doc. Prior to 6.0, the section appears twice, once for Blazor Server and once for Blazor WebAssembly, each with the hosting model name in the section name. --> + ### Synchronous `IDisposable` For synchronous disposal tasks, use <xref:System.IDisposable.Dispose%2A?displayProperty=nameWithType>. @@ -259,8 +426,30 @@ If a single object requires disposal, a lambda can be used to dispose of the obj `Pages/CounterWithTimerDisposal1.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor" highlight="3,11,28"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor" highlight="3,11,28"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor" highlight="3,11,28"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor" highlight="3,11,28"::: + +:::moniker-end + > [!NOTE] > In the preceding example, the call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is wrapped by a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType> because the callback is invoked outside of Blazor's synchronization context. For more information, see <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system>. @@ -268,8 +457,30 @@ If the object is created in a lifecycle method, such as [`OnInitialized`/`OnInit `Pages/CounterWithTimerDisposal2.razor`: +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor" highlight="15,29"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + :::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor" highlight="15,29"::: +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor" highlight="15,29"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor" highlight="15,29"::: + +:::moniker-end + For more information, see: * [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) @@ -324,7 +535,7 @@ These are unusual scenarios. For objects that are implemented correctly and beha ### Event handlers -Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-validation) examples show how to unsubscribe an event handler in the `Dispose` method: +Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-and-input-components) examples show how to unsubscribe an event handler in the `Dispose` method: * Private field and lambda approach @@ -433,7 +644,7 @@ When [anonymous functions](/dotnet/csharp/programming-guide/statements-expressio } ``` - The full example of the preceding code with anonymous lambda expressions appears in the <xref:blazor/forms-validation#validator-components> article. + The full example of the preceding code with anonymous lambda expressions appears in the <xref:blazor/forms-and-input-components#validator-components> article. For more information, see [Cleaning up unmanaged resources](/dotnet/standard/garbage-collection/unmanaged) and the topics that follow it on implementing the `Dispose` and `DisposeAsync` methods. @@ -462,921 +673,30 @@ In the following example: `Pages/BackgroundWork.razor`: -:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor"::: - -## Blazor Server reconnection events +:::moniker range=">= aspnetcore-7.0" -The component lifecycle events covered in this article operate separately from [Blazor Server's reconnection event handlers](xref:blazor/fundamentals/signalr#reflect-the-connection-state-in-the-ui-blazor-server). When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see <xref:blazor/fundamentals/signalr>. +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor"::: :::moniker-end -:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" - -The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering. - -## Lifecycle events - -The following diagrams illustrate Razor component lifecycle events. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article. - -Component lifecycle events: - -1. If the component is rendering for the first time on a request: - * Create the component's instance. - * Perform property injection. Run [`SetParametersAsync`](#when-parameters-are-set-setparametersasync). - * Call [`OnInitialized{Async}`](#component-initialization-oninitializedasync). If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. -1. Call [`OnParametersSet{Async}`](#after-parameters-are-set-onparameterssetasync). If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. -1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s. - -> [!NOTE] -> Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the [Handle incomplete async actions at render](#handle-incomplete-async-actions-at-render) section later in this article. - -![Component lifecycle events of a Razor component in Blazor](~/blazor/components/lifecycle/_static/lifecycle1.png) - -Document Object Model (DOM) event processing: - -1. The event handler is run. -1. If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. -1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s. - -![Document Object Model (DOM) event processing](~/blazor/components/lifecycle/_static/lifecycle2.png) - -The `Render` lifecycle: - -1. Avoid further rendering operations on the component: - * After the first render. - * When [`ShouldRender`](xref:blazor/components/rendering#suppress-ui-refreshing-shouldrender) is `false`. -1. Build the render tree diff (difference) and render the component. -1. Await the DOM to update. -1. Call [`OnAfterRender{Async}`](#after-component-render-onafterrenderasync). - -![Render lifecycle](~/blazor/components/lifecycle/_static/lifecycle3.png) - -Developer calls to [`StateHasChanged`](#state-changes-statehaschanged) result in a render. For more information, see <xref:blazor/components/rendering>. - -## When parameters are set (`SetParametersAsync`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> sets parameters supplied by the component's parent in the render tree or from route parameters. - -The method's <xref:Microsoft.AspNetCore.Components.ParameterView> parameter contains the set of [component parameter](xref:blazor/components/index#component-parameters) values for the component each time <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> is called. By overriding the <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> method, developer code can interact directly with <xref:Microsoft.AspNetCore.Components.ParameterView>'s parameters. - -The default implementation of <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> sets the value of each property with the [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) or [`[CascadingParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute) that has a corresponding value in the <xref:Microsoft.AspNetCore.Components.ParameterView>. Parameters that don't have a corresponding value in <xref:Microsoft.AspNetCore.Components.ParameterView> are left unchanged. - -If [`base.SetParametersAsync`](xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A) isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class. - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -In the following example, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A?displayProperty=nameWithType> assigns the `Param` parameter's value to `value` if parsing a route parameter for `Param` is successful. When `value` isn't `null`, the value is displayed by the component. - -Although [route parameter matching is case insensitive](xref:blazor/fundamentals/routing#route-parameters), <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> only matches case sensitive parameter names in the route template. The following example requires the use of `/{Param?}` in the route template in order to get the value with <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A>, not `/{param?}`. If `/{param?}` is used in this scenario, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> returns `false` and `message` isn't set to either `message` string. - -`Pages/SetParamsAsync.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor)] - -## Component initialization (`OnInitialized{Async}`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> are invoked when the component is initialized after having received its initial parameters in <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A>. - -For a synchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A>: - -`Pages/OnInit.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor?highlight=8)] - -To perform an asynchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> and use the [`await`](/dotnet/csharp/language-reference/operators/await) operator: +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -```csharp -protected override async Task OnInitializedAsync() -{ - await ... -} -``` - -Blazor apps that prerender their content on the server call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> *twice*: +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor"::: -* Once when the component is initially rendered statically as part of the page. -* A second time when the browser renders the component. +:::moniker-end -To prevent developer code in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> from running twice when prerendering, see the [Stateful reconnection after prerendering](#stateful-reconnection-after-prerendering) section. Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" -While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the [Detect when the app is prerendering](#detect-when-the-app-is-prerendering) section. +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor"::: -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. +:::moniker-end -## After parameters are set (`OnParametersSet{Async}`) +:::moniker range="< aspnetcore-5.0" -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> are called: +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor"::: -* After the component is initialized in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A>. +:::moniker-end -* When the parent component rerenders and supplies: - - * Known or primitive immutable types when at least one parameter has changed. - * Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present. - - For more information on rendering conventions, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. - -For the following example component, navigate to the component's page at a URL: - -* With a start date that's received by `StartDate`: `/on-parameters-set/2021-03-19` -* Without a start date, where `StartDate` is assigned a value of the current local time: `/on-parameters-set` - -`Pages/OnParamsSet.razor`: - -> [!NOTE] -> In a component route, it isn't possible to both constrain a <xref:System.DateTime> parameter with the [route constraint `datetime`](xref:blazor/fundamentals/routing#route-constraints) and [make the parameter optional](xref:blazor/fundamentals/routing#route-parameters). Therefore, the following `OnParamsSet` component uses two [`@page`](xref:mvc/views/razor#page) directives to handle routing with and without a supplied date segment in the URL. - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor)] - -Asynchronous work when applying parameters and property values must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> lifecycle event: - -```csharp -protected override async Task OnParametersSetAsync() -{ - await ... -} -``` - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -For more information on route parameters and constraints, see <xref:blazor/fundamentals/routing>. - -## After component render (`OnAfterRender{Async}`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements. - -The `firstRender` parameter for <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A>: - -* Is set to `true` the first time that the component instance is rendered. -* Can be used to ensure that initialization work is only performed once. - -`Pages/AfterRender.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor)] - -Asynchronous work immediately after rendering must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> lifecycle event: - -```csharp -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender) - { - await ... - } -} -``` - -Even if you return a <xref:System.Threading.Tasks.Task> from <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A>, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned <xref:System.Threading.Tasks.Task> completes. - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> *aren't called during the prerendering process on the server*. The methods are called when the component is rendered interactively after prerendering. When the app prerenders: - -1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> aren't called. -1. When the Blazor script (`blazor.webassembly.js` or `blazor.server.js`) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> **are** called because the app isn't in the prerendering phase any longer. - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -## State changes (`StateHasChanged`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> notifies the component that its state has changed. When applicable, calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> causes the component to be rerendered. - -<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called automatically for <xref:Microsoft.AspNetCore.Components.EventCallback> methods. For more information on event callbacks, see <xref:blazor/components/event-handling#eventcallback>. - -For more information on component rendering and when to call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>, including when to invoke it with <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType>, see <xref:blazor/components/rendering>. - -## Handle incomplete async actions at render - -Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be `null` or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are `null`. - -In the `FetchData` component of the Blazor templates, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> is overridden to asynchronously receive forecast data (`forecasts`). When `forecasts` is `null`, a loading message is displayed to the user. After the `Task` returned by <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> completes, the component is rerendered with the updated state. - -`Pages/FetchData.razor` in the Blazor Server template: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_Server/Pages/lifecycle/FetchData.razor?highlight=9,21,25)] - -## Handle errors - -For information on handling errors during lifecycle method execution, see <xref:blazor/fundamentals/handle-errors>. - -## Stateful reconnection after prerendering - -In a Blazor Server app when <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode> is <xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.ServerPrerendered>, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered *again* and interactive. If the [`OnInitialized{Async}`](#component-initialization-oninitializedasync) lifecycle method for initializing the component is present, the method is executed *twice*: - -* When the component is prerendered statically. -* After the server connection has been established. - -This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering. - -The following code demonstrates an updated `WeatherForecastService` in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited <xref:System.Threading.Tasks.Task.Delay%2A> (`await Task.Delay(...)`) simulates a short delay before returning data from the `GetForecastAsync` method. - -`WeatherForecastService.cs`: - -[!code-csharp[](~/blazor/samples/5.0/BlazorSample_Server/lifecycle/WeatherForecastService.cs)] - -For more information on the <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode>, see <xref:blazor/fundamentals/signalr#render-mode-blazor-server>. - -Although the content in this section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. - -## Detect when the app is prerendering - -[!INCLUDE[](~/blazor/includes/prerendering.md)] - -## Component disposal with `IDisposable` and `IAsyncDisposable` - -If a component implements <xref:System.IDisposable>, <xref:System.IAsyncDisposable>, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during [component initialization](#component-initialization-oninitializedasync). - -Components shouldn't need to implement <xref:System.IDisposable> and <xref:System.IAsyncDisposable> simultaneously. If both are implemented, the framework only executes the asynchronous overload. - -Developer code must ensure that <xref:System.IAsyncDisposable> implementations don't take a long time to complete. - -### Synchronous `IDisposable` - -For synchronous disposal tasks, use <xref:System.IDisposable.Dispose%2A?displayProperty=nameWithType>. - -The following component: - -* Implements <xref:System.IDisposable> with the [`@implements`](xref:mvc/views/razor#implements) Razor directive. -* Disposes of `obj`, which is an unmanaged type that implements <xref:System.IDisposable>. -* A null check is performed because `obj` is created in a lifecycle method (not shown). - -```razor -@implements IDisposable - -... - -@code { - ... - - public void Dispose() - { - obj?.Dispose(); - } -} -``` - -If a single object requires disposal, a lambda can be used to dispose of the object when <xref:System.IDisposable.Dispose%2A> is called. The following example appears in the <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system> article and demonstrates the use of a lambda expression for the disposal of a <xref:System.Timers.Timer>. - -`Pages/CounterWithTimerDisposal1.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor?highlight=3,11,28)] - -> [!NOTE] -> In the preceding example, the call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is wrapped by a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType> because the callback is invoked outside of Blazor's synchronization context. For more information, see <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system>. - -If the object is created in a lifecycle method, such as [`OnInitialized`/`OnInitializedAsync`](#component-initialization-oninitializedasync), check for `null` before calling `Dispose`. - -`Pages/CounterWithTimerDisposal2.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor?highlight=15,29)] - -For more information, see: - -* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) -* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) - -### Asynchronous `IAsyncDisposable` - -For asynchronous disposal tasks, use <xref:System.IAsyncDisposable.DisposeAsync%2A?displayProperty=nameWithType>. - -The following component: - -* Implements <xref:System.IAsyncDisposable> with the [`@implements`](xref:mvc/views/razor#implements) Razor directive. -* Disposes of `obj`, which is an unmanaged type that implements <xref:System.IAsyncDisposable>. -* A null check is performed because `obj` is created in a lifecycle method (not shown). - -```razor -@implements IAsyncDisposable - -... - -@code { - ... - - public async ValueTask DisposeAsync() - { - if (obj is not null) - { - await obj.DisposeAsync(); - } - } -} -``` - -For more information, see: - -* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) -* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) - -### Assignment of `null` to disposed objects - -Usually, there's no need to assign `null` to disposed objects after calling <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>. Rare cases for assigning `null` include the following: - -* If the object's type is poorly implemented and doesn't tolerate repeat calls to <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>, assign `null` after disposal to gracefully skip further calls to <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>. -* If a long-lived process continues to hold a reference to a disposed object, assigning `null` allows the [garbage collector](/dotnet/standard/garbage-collection/fundamentals) to free the object in spite of the long-lived process holding a reference to it. - -These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning `null` to disposed objects. In the rare cases where an object must be assigned `null`, we recommend documenting the reason and seeking a solution that prevents the need to assign `null`. - -### `StateHasChanged` - -> [!NOTE] -> Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in `Dispose` isn't supported. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported. - -### Event handlers - -Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-validation) examples show how to unsubscribe an event handler in the `Dispose` method: - -* Private field and lambda approach - - ```razor - @implements IDisposable - - <EditForm EditContext="@editContext"> - ... - <button type="submit" disabled="@formInvalid">Submit</button> - </EditForm> - - @code { - // ... - private EventHandler<FieldChangedEventArgs> fieldChanged; - - protected override void OnInitialized() - { - editContext = new(model); - - fieldChanged = (_, __) => - { - // ... - }; - - editContext.OnFieldChanged += fieldChanged; - } - - public void Dispose() - { - editContext.OnFieldChanged -= fieldChanged; - } - } - ``` - -* Private method approach - - ```razor - @implements IDisposable - - <EditForm EditContext="@editContext"> - ... - <button type="submit" disabled="@formInvalid">Submit</button> - </EditForm> - - @code { - // ... - - protected override void OnInitialized() - { - editContext = new(model); - editContext.OnFieldChanged += HandleFieldChanged; - } - - private void HandleFieldChanged(object sender, FieldChangedEventArgs e) - { - // ... - } - - public void Dispose() - { - editContext.OnFieldChanged -= HandleFieldChanged; - } - } - ``` - -For more information, see the [Component disposal with `IDisposable` and `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -### Anonymous functions, methods, and expressions - -When [anonymous functions](/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-functions), methods, or expressions, are used, it isn't necessary to implement <xref:System.IDisposable> and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem **when the object exposing the event outlives the lifetime of the component registering the delegate**. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show. - -* Anonymous lambda method approach (explicit disposal not required): - - ```csharp - private void HandleFieldChanged(object sender, FieldChangedEventArgs e) - { - formInvalid = !editContext.Validate(); - StateHasChanged(); - } - - protected override void OnInitialized() - { - editContext = new(starship); - editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e); - } - ``` - -* Anonymous lambda expression approach (explicit disposal not required): - - ```csharp - private ValidationMessageStore messageStore; - - [CascadingParameter] - private EditContext CurrentEditContext { get; set; } - - protected override void OnInitialized() - { - ... - - messageStore = new(CurrentEditContext); - - CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); - CurrentEditContext.OnFieldChanged += (s, e) => - messageStore.Clear(e.FieldIdentifier); - } - ``` - - The full example of the preceding code with anonymous lambda expressions appears in the <xref:blazor/forms-validation#validator-components> article. - -For more information, see [Cleaning up unmanaged resources](/dotnet/standard/garbage-collection/unmanaged) and the topics that follow it on implementing the `Dispose` and `DisposeAsync` methods. - -## Cancelable background work - -Components often perform long-running background work, such as making network calls (<xref:System.Net.Http.HttpClient>) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component. - -Other reasons why background work items might require cancellation include: - -* An executing background task was started with faulty input data or processing parameters. -* The current set of executing background work items must be replaced with a new set of work items. -* The priority of currently executing tasks must be changed. -* The app must be shut down for server redeployment. -* Server resources become limited, necessitating the rescheduling of background work items. - -To implement a cancelable background work pattern in a component: - -* Use a <xref:System.Threading.CancellationTokenSource> and <xref:System.Threading.CancellationToken>. -* On [disposal of the component](#component-disposal-with-idisposable-and-iasyncdisposable) and at any point cancellation is desired by manually canceling the token, call [`CancellationTokenSource.Cancel`](xref:System.Threading.CancellationTokenSource.Cancel%2A) to signal that the background work should be cancelled. -* After the asynchronous call returns, call <xref:System.Threading.CancellationToken.ThrowIfCancellationRequested%2A> on the token. - -In the following example: - -* `await Task.Delay(5000, cts.Token);` represents long-running asynchronous background work. -* `BackgroundResourceMethod` represents a long-running background method that shouldn't start if the `Resource` is disposed before the method is called. - -`Pages/BackgroundWork.razor`: - -[!code-razor[](~/blazor/samples/5.0/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor)] - -## Blazor Server reconnection events - -The component lifecycle events covered in this article operate separately from [Blazor Server's reconnection event handlers](xref:blazor/fundamentals/signalr#reflect-the-connection-state-in-the-ui-blazor-server). When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see <xref:blazor/fundamentals/signalr>. - -:::moniker-end - -:::moniker range="< aspnetcore-5.0" - -The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering. - -## Lifecycle events - -The following diagrams illustrate Razor component lifecycle events. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article. - -Component lifecycle events: - -1. If the component is rendering for the first time on a request: - * Create the component's instance. - * Perform property injection. Run [`SetParametersAsync`](#when-parameters-are-set-setparametersasync). - * Call [`OnInitialized{Async}`](#component-initialization-oninitializedasync). If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. -1. Call [`OnParametersSet{Async}`](#after-parameters-are-set-onparameterssetasync). If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. -1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s. - -> [!NOTE] -> Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the [Handle incomplete async actions at render](#handle-incomplete-async-actions-at-render) section later in this article. - -![Component lifecycle events of a Razor component in Blazor](~/blazor/components/lifecycle/_static/lifecycle1.png) - -Document Object Model (DOM) event processing: - -1. The event handler is run. -1. If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. -1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s. - -![Document Object Model (DOM) event processing](~/blazor/components/lifecycle/_static/lifecycle2.png) - -The `Render` lifecycle: - -1. Avoid further rendering operations on the component: - * After the first render. - * When [`ShouldRender`](xref:blazor/components/rendering#suppress-ui-refreshing-shouldrender) is `false`. -1. Build the render tree diff (difference) and render the component. -1. Await the DOM to update. -1. Call [`OnAfterRender{Async}`](#after-component-render-onafterrenderasync). - -![Render lifecycle](~/blazor/components/lifecycle/_static/lifecycle3.png) - -Developer calls to [`StateHasChanged`](#state-changes-statehaschanged) result in a render. For more information, see <xref:blazor/components/rendering>. - -## When parameters are set (`SetParametersAsync`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> sets parameters supplied by the component's parent in the render tree or from route parameters. - -The method's <xref:Microsoft.AspNetCore.Components.ParameterView> parameter contains the set of [component parameter](xref:blazor/components/index#component-parameters) values for the component each time <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> is called. By overriding the <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> method, developer code can interact directly with <xref:Microsoft.AspNetCore.Components.ParameterView>'s parameters. - -The default implementation of <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> sets the value of each property with the [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) or [`[CascadingParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute) that has a corresponding value in the <xref:Microsoft.AspNetCore.Components.ParameterView>. Parameters that don't have a corresponding value in <xref:Microsoft.AspNetCore.Components.ParameterView> are left unchanged. - -If [`base.SetParametersAsync`](xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A) isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class. - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -In the following example, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A?displayProperty=nameWithType> assigns the `Param` parameter's value to `value` if parsing a route parameter for `Param` is successful. When `value` isn't `null`, the value is displayed by the component. - -Although [route parameter matching is case insensitive](xref:blazor/fundamentals/routing#route-parameters), <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> only matches case sensitive parameter names in the route template. The following example requires the use of `/{Param?}` in the route template in order to get the value with <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A>, not `/{param?}`. If `/{param?}` is used in this scenario, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> returns `false` and `message` isn't set to either `message` string. - -`Pages/SetParamsAsync.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor)] - -## Component initialization (`OnInitialized{Async}`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> are invoked when the component is initialized after having received its initial parameters in <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A>. - -For a synchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A>: - -`Pages/OnInit.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor?highlight=8)] - -To perform an asynchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> and use the [`await`](/dotnet/csharp/language-reference/operators/await) operator: - -```csharp -protected override async Task OnInitializedAsync() -{ - await ... -} -``` - -Blazor apps that prerender their content on the server call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> *twice*: - -* Once when the component is initially rendered statically as part of the page. -* A second time when the browser renders the component. - -To prevent developer code in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> from running twice when prerendering, see the [Stateful reconnection after prerendering](#stateful-reconnection-after-prerendering) section. Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. - -While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the [Detect when the app is prerendering](#detect-when-the-app-is-prerendering) section. - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -## After parameters are set (`OnParametersSet{Async}`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> are called: - -* After the component is initialized in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A>. - -* When the parent component rerenders and supplies: - - * Known or primitive immutable types when at least one parameter has changed. - * Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present. - - For more information on rendering conventions, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. - -For the following example component, navigate to the component's page at a URL: - -* With a start date that's received by `StartDate`: `/on-parameters-set/2021-03-19` -* Without a start date, where `StartDate` is assigned a value of the current local time: `/on-parameters-set` - -`Pages/OnParamsSet.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor)] - -Asynchronous work when applying parameters and property values must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> lifecycle event: - -```csharp -protected override async Task OnParametersSetAsync() -{ - await ... -} -``` - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -For more information on route parameters and constraints, see <xref:blazor/fundamentals/routing>. - -## After component render (`OnAfterRender{Async}`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements. - -The `firstRender` parameter for <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A>: - -* Is set to `true` the first time that the component instance is rendered. -* Can be used to ensure that initialization work is only performed once. - -`Pages/AfterRender.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor)] - -Asynchronous work immediately after rendering must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> lifecycle event: - -```csharp -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender) - { - await ... - } -} -``` - -Even if you return a <xref:System.Threading.Tasks.Task> from <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A>, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned <xref:System.Threading.Tasks.Task> completes. - -<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> *aren't called during the prerendering process on the server*. The methods are called when the component is rendered interactively after prerendering. When the app prerenders: - -1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> aren't called. -1. When the Blazor script (`blazor.webassembly.js` or `blazor.server.js`) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> **are** called because the app isn't in the prerendering phase any longer. - -If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -## State changes (`StateHasChanged`) - -<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> notifies the component that its state has changed. When applicable, calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> causes the component to be rerendered. - -<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called automatically for <xref:Microsoft.AspNetCore.Components.EventCallback> methods. For more information on event callbacks, see <xref:blazor/components/event-handling#eventcallback>. - -For more information on component rendering and when to call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>, including when to invoke it with <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType>, see <xref:blazor/components/rendering>. - -## Handle incomplete async actions at render - -Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be `null` or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are `null`. - -In the `FetchData` component of the Blazor templates, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> is overridden to asynchronously receive forecast data (`forecasts`). When `forecasts` is `null`, a loading message is displayed to the user. After the `Task` returned by <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> completes, the component is rerendered with the updated state. - -`Pages/FetchData.razor` in the Blazor Server template: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_Server/Pages/lifecycle/FetchData.razor?highlight=9,21,25)] - -## Handle errors - -For information on handling errors during lifecycle method execution, see <xref:blazor/fundamentals/handle-errors>. - -## Stateful reconnection after prerendering - -In a Blazor Server app when <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode> is <xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.ServerPrerendered>, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered *again* and interactive. If the [`OnInitialized{Async}`](#component-initialization-oninitializedasync) lifecycle method for initializing the component is present, the method is executed *twice*: - -* When the component is prerendered statically. -* After the server connection has been established. - -This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering. - -The following code demonstrates an updated `WeatherForecastService` in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited <xref:System.Threading.Tasks.Task.Delay%2A> (`await Task.Delay(...)`) simulates a short delay before returning data from the `GetForecastAsync` method. - -`WeatherForecastService.cs`: - -[!code-csharp[](~/blazor/samples/3.1/BlazorSample_Server/lifecycle/WeatherForecastService.cs)] - -For more information on the <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode>, see <xref:blazor/fundamentals/signalr#render-mode-blazor-server>. - -Although the content in this section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. - -## Detect when the app is prerendering - -[!INCLUDE[](~/blazor/includes/prerendering.md)] - -## Component disposal with `IDisposable` and `IAsyncDisposable` - -If a component implements <xref:System.IDisposable>, <xref:System.IAsyncDisposable>, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during [component initialization](#component-initialization-oninitializedasync). - -Components shouldn't need to implement <xref:System.IDisposable> and <xref:System.IAsyncDisposable> simultaneously. If both are implemented, the framework only executes the asynchronous overload. - -Developer code must ensure that <xref:System.IAsyncDisposable> implementations don't take a long time to complete. - -### Synchronous `IDisposable` - -For synchronous disposal tasks, use <xref:System.IDisposable.Dispose%2A?displayProperty=nameWithType>. - -The following component: - -* Implements <xref:System.IDisposable> with the [`@implements`](xref:mvc/views/razor#implements) Razor directive. -* Disposes of `obj`, which is an unmanaged type that implements <xref:System.IDisposable>. -* A null check is performed because `obj` is created in a lifecycle method (not shown). - -```razor -@implements IDisposable - -... - -@code { - ... - - public void Dispose() - { - obj?.Dispose(); - } -} -``` - -If a single object requires disposal, a lambda can be used to dispose of the object when <xref:System.IDisposable.Dispose%2A> is called. The following example appears in the <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system> article and demonstrates the use of a lambda expression for the disposal of a <xref:System.Timers.Timer>. - -`Pages/CounterWithTimerDisposal1.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor?highlight=3,11,28)] - -> [!NOTE] -> In the preceding example, the call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is wrapped by a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType> because the callback is invoked outside of Blazor's synchronization context. For more information, see <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system>. - -If the object is created in a lifecycle method, such as [`OnInitialized`/`OnInitializedAsync`](#component-initialization-oninitializedasync), check for `null` before calling `Dispose`. - -`Pages/CounterWithTimerDisposal2.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor?highlight=15,29)] - -For more information, see: - -* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) -* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) - -### Asynchronous `IAsyncDisposable` - -For asynchronous disposal tasks, use <xref:System.IAsyncDisposable.DisposeAsync%2A?displayProperty=nameWithType>. - -The following component: - -* Implements <xref:System.IAsyncDisposable> with the [`@implements`](xref:mvc/views/razor#implements) Razor directive. -* Disposes of `obj`, which is an unmanaged type that implements <xref:System.IAsyncDisposable>. -* A null check is performed because `obj` is created in a lifecycle method (not shown). - -```razor -@implements IAsyncDisposable - -... - -@code { - ... - - public async ValueTask DisposeAsync() - { - if (obj is not null) - { - await obj.DisposeAsync(); - } - } -} -``` - -For more information, see: - -* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) -* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) - -### Assignment of `null` to disposed objects - -Usually, there's no need to assign `null` to disposed objects after calling <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>. Rare cases for assigning `null` include the following: - -* If the object's type is poorly implemented and doesn't tolerate repeat calls to <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>, assign `null` after disposal to gracefully skip further calls to <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>. -* If a long-lived process continues to hold a reference to a disposed object, assigning `null` allows the [garbage collector](/dotnet/standard/garbage-collection/fundamentals) to free the object in spite of the long-lived process holding a reference to it. - -These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning `null` to disposed objects. In the rare cases where an object must be assigned `null`, we recommend documenting the reason and seeking a solution that prevents the need to assign `null`. - -### `StateHasChanged` - -> [!NOTE] -> Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in `Dispose` isn't supported. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported. - -### Event handlers - -Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-validation) examples show how to unsubscribe an event handler in the `Dispose` method: - -* Private field and lambda approach - - ```razor - @implements IDisposable - - <EditForm EditContext="@editContext"> - ... - <button type="submit" disabled="@formInvalid">Submit</button> - </EditForm> - - @code { - // ... - private EventHandler<FieldChangedEventArgs> fieldChanged; - - protected override void OnInitialized() - { - editContext = new EditContext(model); - - fieldChanged = (_, __) => - { - // ... - }; - - editContext.OnFieldChanged += fieldChanged; - } - - public void Dispose() - { - editContext.OnFieldChanged -= fieldChanged; - } - } - ``` - -* Private method approach - - ```razor - @implements IDisposable - - <EditForm EditContext="@editContext"> - ... - <button type="submit" disabled="@formInvalid">Submit</button> - </EditForm> - - @code { - // ... - - protected override void OnInitialized() - { - editContext = new EditContext(model); - editContext.OnFieldChanged += HandleFieldChanged; - } - - private void HandleFieldChanged(object sender, FieldChangedEventArgs e) - { - // ... - } - - public void Dispose() - { - editContext.OnFieldChanged -= HandleFieldChanged; - } - } - ``` - -For more information, see the [Component disposal with `IDisposable` and `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. - -### Anonymous functions, methods, and expressions - -When [anonymous functions](/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-functions), methods, or expressions, are used, it isn't necessary to implement <xref:System.IDisposable> and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem **when the object exposing the event outlives the lifetime of the component registering the delegate**. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show. - -* Anonymous lambda method approach (explicit disposal not required): - - ```csharp - private void HandleFieldChanged(object sender, FieldChangedEventArgs e) - { - formInvalid = !editContext.Validate(); - StateHasChanged(); - } - - protected override void OnInitialized() - { - editContext = new EditContext(starship); - editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e); - } - ``` - -* Anonymous lambda expression approach (explicit disposal not required): - - ```csharp - private ValidationMessageStore messageStore; - - [CascadingParameter] - private EditContext CurrentEditContext { get; set; } - - protected override void OnInitialized() - { - ... - - messageStore = new ValidationMessageStore(CurrentEditContext); - - CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); - CurrentEditContext.OnFieldChanged += (s, e) => - messageStore.Clear(e.FieldIdentifier); - } - ``` - - The full example of the preceding code with anonymous lambda expressions appears in the <xref:blazor/forms-validation#validator-components> article. - -For more information, see [Cleaning up unmanaged resources](/dotnet/standard/garbage-collection/unmanaged) and the topics that follow it on implementing the `Dispose` and `DisposeAsync` methods. - -## Cancelable background work - -Components often perform long-running background work, such as making network calls (<xref:System.Net.Http.HttpClient>) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component. - -Other reasons why background work items might require cancellation include: - -* An executing background task was started with faulty input data or processing parameters. -* The current set of executing background work items must be replaced with a new set of work items. -* The priority of currently executing tasks must be changed. -* The app must be shut down for server redeployment. -* Server resources become limited, necessitating the rescheduling of background work items. - -To implement a cancelable background work pattern in a component: - -* Use a <xref:System.Threading.CancellationTokenSource> and <xref:System.Threading.CancellationToken>. -* On [disposal of the component](#component-disposal-with-idisposable-and-iasyncdisposable) and at any point cancellation is desired by manually canceling the token, call [`CancellationTokenSource.Cancel`](xref:System.Threading.CancellationTokenSource.Cancel%2A) to signal that the background work should be cancelled. -* After the asynchronous call returns, call <xref:System.Threading.CancellationToken.ThrowIfCancellationRequested%2A> on the token. - -In the following example: - -* `await Task.Delay(5000, cts.Token);` represents long-running asynchronous background work. -* `BackgroundResourceMethod` represents a long-running background method that shouldn't start if the `Resource` is disposed before the method is called. - -`Pages/BackgroundWork.razor`: - -[!code-razor[](~/blazor/samples/3.1/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor)] - -## Blazor Server reconnection events +## Blazor Server reconnection events The component lifecycle events covered in this article operate separately from [Blazor Server's reconnection event handlers](xref:blazor/fundamentals/signalr#reflect-the-connection-state-in-the-ui-blazor-server). When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see <xref:blazor/fundamentals/signalr>. - -:::moniker-end diff --git a/aspnetcore/blazor/components/overwriting-parameters.md b/aspnetcore/blazor/components/overwriting-parameters.md new file mode 100644 index 000000000000..3980c3bb5cec --- /dev/null +++ b/aspnetcore/blazor/components/overwriting-parameters.md @@ -0,0 +1,154 @@ +--- +title: Avoid overwriting parameters in ASP.NET Core Blazor +author: guardrex +description: Learn how to avoid overwriting parameters in Blazor apps during rerendering. +monikerRange: '>= aspnetcore-3.1' +ms.author: riande +ms.custom: mvc +ms.date: 05/15/2023 +uid: blazor/components/overwriting-parameters +--- +# Avoid overwriting parameters in ASP.NET Core Blazor + +The Blazor framework generally imposes safe parent-to-child parameter assignment: + +* Parameters aren't overwritten unexpectedly. +* Side effects are minimized. For example, additional renders are avoided because they may create infinite rendering loops. + +A child component receives new parameter values that possibly overwrite existing values when the parent component rerenders. Accidentally overwriting parameter values in a child component often occurs when developing the component with one or more data-bound parameters and the developer writes directly to a parameter in the child: + +* The child component is rendered with one or more parameter values from the parent component. +* The child writes directly to the value of a parameter. +* The parent component rerenders and overwrites the value of the child's parameter. + +The potential for overwriting parameter values extends into the child component's property `set` accessors, too. + +> [!IMPORTANT] +> Our general guidance is not to create components that directly write to their own parameters after the component is rendered for the first time. + +Consider the following `Expander` component that: + +* Renders child content. +* Toggles showing child content with a component parameter (`Expanded`). + +After the following `Expander` component demonstrates an overwritten parameter, a modified `Expander` component is shown to demonstrate the correct approach for this scenario. The following examples can be placed in a local sample app to experience the behaviors described. + +`Shared/Expander.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/overwriting-parameters/BadExpander.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/overwriting-parameters/BadExpander.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/overwriting-parameters/BadExpander.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/overwriting-parameters/BadExpander.razor"::: + +:::moniker-end + +The `Expander` component is added to the following `ExpanderExample` parent component that may call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>: + +* Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in developer code notifies a component that its state has changed and typically triggers component rerendering to update the UI. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is covered in more detail later in <xref:blazor/components/lifecycle> and <xref:blazor/components/rendering>. +* The button's `@onclick` directive attribute attaches an event handler to the button's `onclick` event. Event handling is covered in more detail later in <xref:blazor/components/event-handling>. + +`Pages/ExpanderExample.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/overwriting-parameters/ExpanderExample.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Pages/overwriting-parameters/ExpanderExample.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Pages/overwriting-parameters/ExpanderExample.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/overwriting-parameters/ExpanderExample.razor"::: + +:::moniker-end + +Initially, the `Expander` components behave independently when their `Expanded` properties are toggled. The child components maintain their states as expected. + +If <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called in a parent component, the Blazor framework rerenders child components if their parameters might have changed: + +* For a group of parameter types that Blazor explicitly checks, Blazor rerenders a child component if it detects that any of the parameters have changed. +* For unchecked parameter types, Blazor rerenders the child component *regardless of whether or not the parameters have changed*. Child content falls into this category of parameter types because child content is of type <xref:Microsoft.AspNetCore.Components.RenderFragment>, which is a delegate that refers to other mutable objects. + +For the `ExpanderExample` component: + +* The first `Expander` component sets child content in a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment>, so a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component automatically rerenders the component and potentially overwrites the value of `Expanded` to its initial value of `true`. +* The second `Expander` component doesn't set child content. Therefore, a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment> doesn't exist. A call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component doesn't automatically rerender the child component, so the component's `Expanded` value isn't overwritten. + +To maintain state in the preceding scenario, use a *private field* in the `Expander` component to maintain its toggled state. + +The following revised `Expander` component: + +* Accepts the `Expanded` component parameter value from the parent. +* Assigns the component parameter value to a *private field* (`expanded`) in the [`OnInitialized` event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). +* Uses the private field to maintain its internal toggle state, which demonstrates how to avoid writing directly to a parameter. + +> [!NOTE] +> The advice in this section extends to similar logic in component parameter `set` accessors, which can result in similar undesirable side effects. + +`Shared/Expander.razor`: + +:::moniker range=">= aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/overwriting-parameters/Expander.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" + +:::code language="razor" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/Shared/overwriting-parameters/Expander.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0" + +:::code language="razor" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/Shared/overwriting-parameters/Expander.razor"::: + +:::moniker-end + +:::moniker range="< aspnetcore-5.0" + +:::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Shared/overwriting-parameters/Expander.razor"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + +For two-way parent-child binding examples, see <xref:blazor/components/data-binding#binding-with-component-parameters>. For additional information, see [Blazor Two Way Binding Error (dotnet/aspnetcore #24599)](https://github.com/dotnet/aspnetcore/issues/24599). + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +For additional information, see [Blazor Two Way Binding Error (dotnet/aspnetcore #24599)](https://github.com/dotnet/aspnetcore/issues/24599). + +:::moniker-end + +For more information on change detection, including information on the exact types that Blazor checks, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. diff --git a/aspnetcore/blazor/components/prerendering-and-integration.md b/aspnetcore/blazor/components/prerendering-and-integration.md index f17201161c30..35644bb2940d 100644 --- a/aspnetcore/blazor/components/prerendering-and-integration.md +++ b/aspnetcore/blazor/components/prerendering-and-integration.md @@ -5,15 +5,920 @@ description: Learn about Razor component integration scenarios for Blazor apps, monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 11/09/2021 +ms.date: 11/08/2022 uid: blazor/components/prerendering-and-integration zone_pivot_groups: blazor-hosting-models --- # Prerender and integrate ASP.NET Core Razor components +[!INCLUDE[](~/includes/not-latest-version.md)] + This article explains Razor component integration scenarios for Blazor apps, including prerendering of Razor components on the server. -:::moniker range=">= aspnetcore-6.0" +> [!IMPORTANT] +> Framework changes across ASP.NET Core releases led to different sets of instructions in this article. Before using this article's guidance, confirm that the document version selector on this page matches the version of ASP.NET Core that you intend to use for your app. + +:::moniker range=">= aspnetcore-7.0" + +:::zone pivot="webassembly" + +Razor components can be integrated into Razor Pages and MVC apps in a hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-studio-solution-file-sln). When the page or view is rendered, components can be prerendered at the same time. + +Prerendering can improve [Search Engine Optimization (SEO)](https://developer.mozilla.org/docs/Glossary/SEO) by rendering content for the initial HTTP response that search engines can use to calculate page rank. + +## Solution configuration + +### Prerendering configuration + +To set up prerendering for a hosted Blazor WebAssembly app: + +1. Host the Blazor WebAssembly app in an ASP.NET Core app. A standalone Blazor WebAssembly app can be added to an ASP.NET Core solution, or you can use a hosted Blazor WebAssembly app created from the [Blazor WebAssembly project template](xref:blazor/tooling) with the hosted option: + + * Visual Studio: In the **Additional information** dialog, select the **ASP.NET Core Hosted** checkbox when creating the Blazor WebAssembly app. In this article's examples, the solution is named `BlazorHosted`. + * Visual Studio Code/.NET CLI command shell: `dotnet new blazorwasm -ho` (use the `-ho|--hosted` option). Use the `-o|--output {LOCATION}` option to create a folder for the solution and set the solution's project namespaces. In this article's examples, the solution is named `BlazorHosted` (`dotnet new blazorwasm -ho -o BlazorHosted`). + + For the examples in this article, the hosted solution's name (assembly name) is `BlazorHosted`. The client project's namespace is `BlazorHosted.Client`, and the server project's namespace is `BlazorHosted.Server`. + +1. **Delete** the `wwwroot/index.html` file from the Blazor WebAssembly **:::no-loc text="Client":::** project. + +1. In the **:::no-loc text="Client":::** project, **delete** the following lines in `Program.cs`: + + ```diff + - builder.RootComponents.Add<App>("#app"); + - builder.RootComponents.Add<HeadOutlet>("head::after"); + ``` + +1. Add `_Host.cshtml` file to the **:::no-loc text="Server":::** project's `Pages` folder. You can obtain the files from a project created from the Blazor Server template using Visual Studio or using the .NET CLI with the `dotnet new blazorserver -o BlazorServer` command in a command shell (the `-o BlazorServer` option creates a folder for the project). After placing the files into the **:::no-loc text="Server":::** project's `Pages` folder, make the following changes to the files. + + Make the following changes to the `_Host.cshtml` file: + + * Update the `Pages` namespace at the top of the file to match the namespace of the **:::no-loc text="Server":::** app's pages. The `{APP NAMESPACE}` placeholder in the following example represents the namespace of the donor app's pages that provided the `_Host.cshtml` file: + + Delete: + + ```diff + - @namespace {APP NAMESPACE}.Pages + ``` + + Add: + + ```razor + @namespace BlazorHosted.Server.Pages + ``` + + * Add an [`@using`](xref:mvc/views/razor#using) directive for the **:::no-loc text="Client":::** project at the top of the file: + + ```razor + @using BlazorHosted.Client + ``` + + * Update the stylesheet links to point to the WebAssembly project's stylesheets. In the following example, the client project's namespace is `BlazorHosted.Client`. The `{APP NAMESPACE}` placeholder represents the namespace of the donor app that provided the `_Host.cshtml` file. Update the Component Tag Helper (`<component>` tag) for the `HeadOutlet` component to prerender the component. + + Delete: + + ```diff + - <link href="css/site.css" rel="stylesheet" /> + - <link href="{APP NAMESPACE}.styles.css" rel="stylesheet" /> + - <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> + ``` + + Add: + + ```cshtml + <link href="css/app.css" rel="stylesheet" /> + <link href="BlazorHosted.Client.styles.css" rel="stylesheet" /> + <component type="typeof(HeadOutlet)" render-mode="WebAssemblyPrerendered" /> + ``` + + > [!NOTE] + > Leave the `<link>` element that requests the Bootstrap stylesheet (`css/bootstrap/bootstrap.min.css`) in place. + + * Update the Blazor script source to use the client-side Blazor WebAssembly script: + + Delete: + + ```diff + - <script src="_framework/blazor.server.js"></script> + ``` + + Add: + + ```html + <script src="_framework/blazor.webassembly.js"></script> + ``` + + * Update the `render-mode` of the [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) to prerender the root `App` component with <xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>: + + Delete: + + ```diff + - <component type="typeof(App)" render-mode="ServerPrerendered" /> + ``` + + Add: + + ```cshtml + <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> + ``` + + > [!IMPORTANT] + > Prerendering isn't supported for authentication endpoints (`/authentication/` path segment). For more information, see <xref:blazor/security/webassembly/additional-scenarios#prerendering-with-authentication>. + +1. In the `Program.cs` file of the **:::no-loc text="Server":::** project, change the fallback endpoint from the `index.html` file to the `_Host.cshtml` page: + + Delete: + + ```diff + - app.MapFallbackToFile("index.html"); + ``` + + Add: + + ```csharp + app.MapFallbackToPage("/_Host"); + ``` + +1. If the **:::no-loc text="Client":::** and **:::no-loc text="Server":::** projects use one or more common services during prerendering, factor the service registrations into a method that can be called from both projects. For more information, see <xref:blazor/fundamentals/dependency-injection#register-common-services-in-a-hosted-blazor-webassembly-solution>. + +1. Run the **:::no-loc text="Server":::** project. The hosted Blazor WebAssembly app is prerendered by the **:::no-loc text="Server":::** project for clients. + +### Configuration for embedding Razor components into pages and views + +The following sections and examples for embedding Razor components from the **:::no-loc text="Client":::** Blazor WebAssembly app into pages and views of the server app require additional configuration. + +The **:::no-loc text="Server":::** project must have the following files and folders. + +Razor Pages: + +* `Pages/Shared/_Layout.cshtml` +* `Pages/Shared/_Layout.cshtml.css` +* `Pages/_ViewImports.cshtml` +* `Pages/_ViewStart.cshtml` + +MVC: + +* `Views/Shared/_Layout.cshtml` +* `Views/Shared/_Layout.cshtml.css` +* `Views/_ViewImports.cshtml` +* `Views/_ViewStart.cshtml` + +The preceding files can be obtained by generating an app from the ASP.NET Core project templates using: + +* Visual Studio's new project creation tools. +* Opening a command shell and executing `dotnet new webapp -o {PROJECT NAME}` (Razor Pages) or `dotnet new mvc -o {PROJECT NAME}` (MVC). The option `-o|--output` with a value for the `{PROJECT NAME}` placeholder provides a name for the app and creates a folder for the app. + +Update the namespaces in the imported `_ViewImports.cshtml` file to match those in use by the **:::no-loc text="Server":::** project receiving the files. + +`Pages/_ViewImports.cshtml` (Razor Pages): + +```razor +@using BlazorHosted.Server +@namespace BlazorHosted.Server.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +``` + +`Views/_ViewImports.cshtml` (MVC): + +```razor +@using BlazorHosted.Server +@using BlazorHosted.Server.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +``` + +Update the imported layout file, which is `Pages/Shared/_Layout.cshtml` for Razor Pages or `Views/Shared/_Layout.cshtml` for MVC. + +First, delete the title and the stylesheet from the donor project, which is `RPDonor.styles.css` in the following example. The `{PROJECT NAME}` placeholder represents the donor project's app name. + +```diff +- <title>@ViewData["Title"] - {PROJECT NAME} +- +``` + +Include the **:::no-loc text="Client":::** project's styles in the layout file. In the following example, the **:::no-loc text="Client":::** project's namespace is `BlazorHosted.Client`. The `` element can be updated at the same time. + +Place the following lines in the `<head>` content of the layout file: + +```cshtml +<title>@ViewData["Title"] - BlazorHosted + + + +``` + +The imported layout contains two `Home` (`Index` page) and `Privacy` navigation links. To make the `Home` links point to the hosted Blazor WebAssembly app, change the hyperlinks: + +```diff +- {PROJECT NAME} ++ BlazorHosted +``` + +```diff +- Home ++ Home +``` + +In an MVC layout file: + +```diff +- {PROJECT NAME} ++ BlazorHosted +``` + +```diff +- Home ++ Home +``` + +Update the `