Skip to content

Commit 9b1eda6

Browse files
authored
Merge pull request #34590 from dotnet/main
2 parents 7bc1ba9 + cf4cd9d commit 9b1eda6

File tree

5 files changed

+252
-38
lines changed

5 files changed

+252
-38
lines changed

aspnetcore/blazor/components/index.md

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -727,45 +727,54 @@ The `Heading` component example shown in this section doesn't have an [`@page`][
727727

728728
## Component parameters
729729

730-
*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.
730+
*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).
731731

732-
`PanelBody.cs`:
732+
In the following `ParameterChild` component, component parameters include:
733733

734-
:::moniker range=">= aspnetcore-9.0"
734+
* Built-in reference types.
735735

736-
:::code language="csharp" source="~/../blazor-samples/9.0/BlazorSample_BlazorWebApp/PanelBody.cs":::
736+
* <xref:System.String?displayProperty=fullName> to pass a title in `Title`.
737+
* <xref:System.Int32?displayProperty=fullName> to pass a count in `Count`.
737738

738-
:::moniker-end
739+
* A user-defined reference type (`PanelBody`) to pass a Bootstrap card body in `Body`.
739740

740-
:::moniker range=">= aspnetcore-8.0 < aspnetcore-9.0"
741+
`PanelBody.cs`:
741742

742-
:::code language="csharp" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/PanelBody.cs":::
743+
:::moniker range=">= aspnetcore-9.0"
743744

744-
:::moniker-end
745+
:::code language="csharp" source="~/../blazor-samples/9.0/BlazorSample_BlazorWebApp/PanelBody.cs":::
745746

746-
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
747+
:::moniker-end
747748

748-
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/PanelBody.cs":::
749+
:::moniker range=">= aspnetcore-8.0 < aspnetcore-9.0"
749750

750-
:::moniker-end
751+
:::code language="csharp" source="~/../blazor-samples/8.0/BlazorSample_BlazorWebApp/PanelBody.cs":::
751752

752-
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
753+
:::moniker-end
753754

754-
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/PanelBody.cs":::
755+
:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0"
755756

756-
:::moniker-end
757+
:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/PanelBody.cs":::
757758

758-
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
759+
:::moniker-end
759760

760-
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/PanelBody.cs":::
761+
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
761762

762-
:::moniker-end
763+
:::code language="csharp" source="~/../blazor-samples/6.0/BlazorSample_WebAssembly/PanelBody.cs":::
763764

764-
:::moniker range="< aspnetcore-5.0"
765+
:::moniker-end
765766

766-
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/PanelBody.cs":::
767+
:::moniker range=">= aspnetcore-5.0 < aspnetcore-6.0"
767768

768-
:::moniker-end
769+
:::code language="csharp" source="~/../blazor-samples/5.0/BlazorSample_WebAssembly/PanelBody.cs":::
770+
771+
:::moniker-end
772+
773+
:::moniker range="< aspnetcore-5.0"
774+
775+
:::code language="csharp" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/PanelBody.cs":::
776+
777+
:::moniker-end
769778

770779
`ParameterChild.razor`:
771780

@@ -808,7 +817,7 @@ The `Heading` component example shown in this section doesn't have an [`@page`][
808817
> [!WARNING]
809818
> 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>.
810819
811-
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:
820+
The component parameters of the `ParameterChild` component can be set by arguments in the HTML tag that renders an instance of the `ParameterChild` component. The following `ParameterParent` component renders two `ParameterChild` components:
812821

813822
* The first `ParameterChild` component is rendered without supplying parameter arguments.
814823
* 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.
@@ -864,21 +873,21 @@ The `Title` and `Body` component parameters of the `ParameterChild` component ar
864873
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.
865874

866875
> [!NOTE]
867-
> For clarity, rendered CSS style classes aren't shown in the following rendered HTML markup.
876+
> For clarity, most of the rendered CSS style classes and some elements aren't shown in the following rendered HTML markup. The main concept demonstrated by the following example is that the parent component assigned values to the child component using its component parameters.
868877
869878
```html
870879
<h1>Child component (without attribute values)</h1>
871880

872-
<div>
873-
<div>Set By Child</div>
874-
<div>Set by child.</div>
881+
<div>Set By Child</div>
882+
<div style="font-style:normal">
883+
<p>Card content set by child.</p>
875884
</div>
876885

877886
<h1>Child component (with attribute values)</h1>
878887

879-
<div>
880-
<div>Set by Parent</div>
881-
<div>Set by parent.</div>
888+
<div>Set by Parent</div>
889+
<div style="font-style:italic">
890+
<p>Set by parent.</p>
882891
</div>
883892
```
884893

@@ -893,12 +902,14 @@ The following `ParameterParent2` component displays four instances of the preced
893902
* 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).
894903
* The `panelData` object's `Title` property.
895904

905+
The fifth `ParameterChild` component instance also sets the `Count` parameter. Note how a `string`-typed parameter requires an `@` prefix to ensure that an expression isn't treated as a string literal. However, `Count` is a nullable integer (<xref:System.Int32?displayProperty=fullName>), so `Count` can receive the value of `count` without an `@` prefix. You can establish an alternative code convention that requires developers in your organization to always prefix with `@`. Either way, we merely recommend that you adopt a consistent approach for how component parameters are passed in Razor markup.
906+
896907
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.
897908

898909
Throughout the documentation, code examples:
899910

900911
* Always use quotes. Example: `Value="this"`.
901-
* Don't use the `@` prefix with nonliterals unless required. 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.
912+
* Don't use the `@` prefix with nonliterals unless required. Example: `Count="count"`, where `count` is a number-typed variable. `Count="@count"` is a valid stylistic approach, but the documentation and examples don't adopt the convention.
902913
* Always avoid `@` for literals, outside of Razor expressions. Example: `IsFixed="true"`. This includes keywords (for example, `this`) and `null`, but you can choose to use them if you wish. For example, `IsFixed="@true"` is uncommon but supported.
903914

904915
:::moniker range=">= aspnetcore-9.0"
@@ -947,21 +958,21 @@ Throughout the documentation, code examples:
947958
> Correct (`Title` is a string parameter, `Count` is a number-typed parameter):
948959
>
949960
> ```razor
950-
> <ParameterChild Title="@title" Count="ct" />
961+
> <ParameterChild Title="@title" Count="count" />
951962
> ```
952963
>
953964
> ```razor
954-
> <ParameterChild Title="@title" Count="@ct" />
965+
> <ParameterChild Title="@title" Count="@count" />
955966
> ```
956967
>
957968
> Incorrect:
958969
>
959970
> ```razor
960-
> <ParameterChild @Title="@title" @Count="ct" />
971+
> <ParameterChild @Title="@title" @Count="count" />
961972
> ```
962973
>
963974
> ```razor
964-
> <ParameterChild @Title="@title" @Count="@ct" />
975+
> <ParameterChild @Title="@title" @Count="@count" />
965976
> ```
966977
967978
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:

aspnetcore/blazor/security/account-confirmation-and-password-recovery.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,18 @@ Register the `AuthMessageSenderOptions` configuration instance in the `Program`
4343
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
4444
```
4545

46-
## Configure a user secret for the provider's security key
46+
## Configure a secret for the email provider's security key
47+
48+
Receive the email provider's security key from the provider and use it in the following guidance.
49+
50+
Use either or both of the following approaches to supply the secret to the app:
51+
52+
* [Secret Manager tool](#secret-manager-tool): The Secret Manager tool stores private data on the local machine and is only used during local development.
53+
* [Azure Key Vault](#azure-key-vault): You can store the secret in a key vault for use in any environment, including for the Development environment when working locally. Some developers prefer to use key vaults for staging and production deployments and use the [Secret Manager tool](#secret-manager-tool) for local development.
54+
55+
We strongly recommend that you avoid storing secrets in project code or configuration files. Use secure authentication flows, such as either or both of the approaches in this section.
56+
57+
### Secret Manager tool
4758

4859
If the project has already been initialized for the [Secret Manager tool](xref:security/app-secrets), it will already have an app secrets identifier (`<AppSecretsId>`) in its project file (`.csproj`). In Visual Studio, you can tell if the app secrets ID is present by looking at the **Properties** panel when the project is selected in **Solution Explorer**. If the app hasn't been initialized, execute the following command in a command shell opened to the project's directory. In Visual Studio, you can use the Developer PowerShell command prompt.
4960

@@ -63,6 +74,91 @@ For more information, see <xref:security/app-secrets>.
6374

6475
[!INCLUDE[](~/blazor/security/includes/secure-authentication-flows.md)]
6576

77+
### Azure Key Vault
78+
79+
[Azure Key Vault](https://azure.microsoft.com/products/key-vault/) provides a safe approach for providing the app's client secret to the app.
80+
81+
To create a key vault and set a secret, see [About Azure Key Vault secrets (Azure documentation)](/azure/key-vault/secrets/about-secrets), which cross-links resources to get started with Azure Key Vault. To implement the code in this section, record the key vault URI and the secret name from Azure when you create the key vault and secret. When you set the access policy for the secret in the **Access policies** panel:
82+
83+
* Only the **Get** secret permission is required.
84+
* Select the application as the **Principal** for the secret.
85+
86+
Confirm in the Azure or Entra portal that the app has been granted access to the secret that you created for the email provider key.
87+
88+
> [!IMPORTANT]
89+
> A key vault secret is created with an expiration date. Be sure to track when a key vault secret is going to expire and create a new secret for the app prior to that date passing.
90+
91+
Add the following `AzureHelper` class to the server project. The `GetKeyVaultSecret` method retrieves a secret from a key vault. Adjust the namespace (`BlazorSample.Helpers`) to match your project namespace scheme.
92+
93+
`Helpers/AzureHelper.cs`:
94+
95+
```csharp
96+
using Azure;
97+
using Azure.Identity;
98+
using Azure.Security.KeyVault.Secrets;
99+
100+
namespace BlazorSample.Helpers;
101+
102+
public static class AzureHelper
103+
{
104+
public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
105+
{
106+
DefaultAzureCredentialOptions options = new()
107+
{
108+
// Specify the tenant ID to use the dev credentials when running the app locally
109+
// in Visual Studio.
110+
VisualStudioTenantId = tenantId,
111+
SharedTokenCacheTenantId = tenantId
112+
};
113+
114+
var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
115+
var secret = client.GetSecretAsync(secretName).Result;
116+
117+
return secret.Value.Value;
118+
}
119+
}
120+
```
121+
122+
Where services are registered in the server project's `Program` file, obtain and bind the secret with [Options configuration](xref:fundamentals/configuration/options):
123+
124+
```csharp
125+
var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
126+
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
127+
128+
var emailAuthKey = AzureHelper.GetKeyVaultSecret(
129+
tenantId, vaultUri, "EmailAuthKey");
130+
131+
var authMessageSenderOptions =
132+
new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
133+
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
134+
.Bind(authMessageSenderOptions);
135+
```
136+
137+
If you wish to control the environment where the preceding code operates, for example to avoid running the code locally because you've opted to use the [Secret Manager tool](#secret-manager-tool) for local development, you can wrap the preceding code in a conditional statement that checks the environment:
138+
139+
```csharp
140+
if (!context.HostingEnvironment.IsDevelopment())
141+
{
142+
...
143+
}
144+
```
145+
146+
In the `AzureAd` section of `appsettings.json` in the server project, confirm the presence of the app's Entra ID `TenantId` and add the following `VaultUri` configuration key and value, if it isn't already present:
147+
148+
```json
149+
"VaultUri": "{VAULT URI}"
150+
```
151+
152+
In the preceding example, the `{VAULT URI}` placeholder is the key vault URI. Include the trailing slash on the URI.
153+
154+
Example:
155+
156+
```json
157+
"VaultUri": "https://contoso.vault.azure.net/"
158+
```
159+
160+
Configuration is used to facilitate supplying dedicated key vaults and secret names based on the app's environmental configuration files. For example, you can supply different configuration values for `appsettings.Development.json` in development, `appsettings.Staging.json` when staging, and `appsettings.Production.json` for the production deployment. For more information, see <xref:blazor/fundamentals/configuration>.
161+
66162
## Implement `IEmailSender`
67163

68164
The following example is based on Mailchimp's Transactional API using [Mandrill.net](https://www.nuget.org/packages/Mandrill.net). For a different provider, refer to their documentation on how to implement sending an email message.

aspnetcore/blazor/security/blazor-web-app-with-entra.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ To create a key vault and set a client secret, see [About Azure Key Vault secret
156156
> [!IMPORTANT]
157157
> A key vault secret is created with an expiration date. Be sure to track when a key vault secret is going to expire and create a new secret for the app prior to that date passing.
158158
159-
The following `GetKeyVaultSecret` method retrieves a secret from a key vault. Add this method to the server project. Adjust the namespace (`BlazorSample.Helpers`) to match your project namespace scheme.
159+
Add the following `AzureHelper` class to the server project. The `GetKeyVaultSecret` method retrieves a secret from a key vault. Adjust the namespace (`BlazorSample.Helpers`) to match your project namespace scheme.
160160

161161
`Helpers/AzureHelper.cs`:
162162

0 commit comments

Comments
 (0)